Move a bunch of logic here from soup-context. Now the session keeps track
[platform/upstream/libsoup.git] / libsoup / soup-session.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-session.c
4  *
5  * Copyright (C) 2000-2003, Ximian, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <unistd.h>
13 #include <string.h>
14 #include <stdlib.h>
15
16 #include "soup-session.h"
17 #include "soup-connection.h"
18 #include "soup-message-private.h"
19 #include "soup-message-queue.h"
20 #include "soup-private.h"
21
22 typedef struct {
23         SoupUri    *root_uri;
24         guint       error;
25
26         GSList     *connections;      /* CONTAINS: SoupConnection */
27         guint       num_conns;
28
29         GHashTable *auth_realms;      /* path -> scheme:realm */
30         GHashTable *auths;            /* scheme:realm -> SoupAuth */
31
32         GHashTable *ntlm_auths;       /* SoupConnection -> SoupAuth */
33 } SoupSessionHost;
34
35 struct SoupSessionPrivate {
36         SoupUri *proxy_uri;
37         guint max_conns, max_conns_per_host;
38
39         SoupMessageQueue *queue;
40         guint queue_idle_tag;
41
42         GHashTable *hosts; /* SoupUri -> SoupSessionHost */
43         GHashTable *conns; /* SoupConnection -> SoupSessionHost */
44         guint num_conns;
45
46         SoupSessionHost *proxy_host;
47 };
48
49 static guint    host_uri_hash  (gconstpointer key);
50 static gboolean host_uri_equal (gconstpointer v1, gconstpointer v2);
51
52 static gboolean run_queue (SoupSession *session, gboolean try_pruning);
53
54 #define SOUP_SESSION_MAX_CONNS_DEFAULT 10
55 #define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 4
56
57 #define PARENT_TYPE G_TYPE_OBJECT
58 static GObjectClass *parent_class;
59
60 static void
61 init (GObject *object)
62 {
63         SoupSession *session = SOUP_SESSION (object);
64
65         session->priv = g_new0 (SoupSessionPrivate, 1);
66         session->priv->queue = soup_message_queue_new ();
67         session->priv->hosts = g_hash_table_new (host_uri_hash,
68                                                  host_uri_equal);
69         session->priv->conns = g_hash_table_new (NULL, NULL);
70
71         session->priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT;
72         session->priv->max_conns_per_host = SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT;
73 }
74
75 static void
76 finalize (GObject *object)
77 {
78         SoupSession *session = SOUP_SESSION (object);
79         SoupMessageQueueIter iter;
80         SoupMessage *msg;
81
82         if (session->priv->queue_idle_tag)
83                 g_source_remove (session->priv->queue_idle_tag);
84
85         for (msg = soup_message_queue_first (session->priv->queue, &iter); msg;
86              msg = soup_message_queue_next (session->priv->queue, &iter)) {
87                 soup_message_queue_remove (session->priv->queue, &iter);
88                 soup_message_cancel (msg);
89         }
90         soup_message_queue_destroy (session->priv->queue);
91
92         g_free (session->priv);
93
94         G_OBJECT_CLASS (parent_class)->finalize (object);
95 }
96
97 static void
98 class_init (GObjectClass *object_class)
99 {
100         parent_class = g_type_class_ref (PARENT_TYPE);
101
102         /* virtual method override */
103         object_class->finalize = finalize;
104 }
105
106 SOUP_MAKE_TYPE (soup_session, SoupSession, class_init, init, PARENT_TYPE)
107
108 SoupSession *
109 soup_session_new (void)
110 {
111         return g_object_new (SOUP_TYPE_SESSION, NULL);
112 }
113
114 SoupSession *
115 soup_session_new_with_proxy (const SoupUri *proxy_uri)
116 {
117         SoupSession *session;
118
119         session = soup_session_new ();
120         if (proxy_uri)
121                 session->priv->proxy_uri = soup_uri_copy (proxy_uri);
122
123         return session;
124 }
125
126 SoupSession *
127 soup_session_new_full (const SoupUri *proxy_uri,
128                        guint max_conns, guint max_per_host)
129 {
130         SoupSession *session;
131
132         session = soup_session_new_with_proxy (proxy_uri);
133         session->priv->max_conns = max_conns;
134         session->priv->max_conns_per_host = max_conns_per_host;
135
136         return session;
137 }
138
139
140 /* Hosts */
141 static guint
142 host_uri_hash (gconstpointer key)
143 {
144         const SoupUri *uri = key;
145
146         return (uri->protocol << 16) + uri->port + g_str_hash (uri->host);
147 }
148
149 static gboolean
150 host_uri_equal (gconstpointer v1, gconstpointer v2)
151 {
152         const SoupUri *one = v1;
153         const SoupUri *two = v2;
154
155         if (one->protocol != two->protocol)
156                 return FALSE;
157         if (one->port != two->port)
158                 return FALSE;
159
160         return strcmp (one->host, two->host) == 0;
161 }
162
163 static SoupSessionHost *
164 get_host_for_message (SoupSession *session, SoupMessage *msg)
165 {
166         SoupSessionHost *host;
167         const SoupUri *source = soup_message_get_uri (msg);
168
169         host = g_hash_table_lookup (session->priv->hosts, source);
170         if (host)
171                 return host;
172
173         host = g_new0 (SoupSessionHost, 1);
174         host->root_uri = g_new0 (SoupUri, 1);
175         host->root_uri->protocol = source->protocol;
176         host->root_uri->host = g_strdup (source->host);
177         host->root_uri->port = source->port;
178
179         g_hash_table_insert (session->priv->hosts, host->root_uri, host);
180         return host;
181 }
182
183
184 /* Authentication */
185
186 static SoupAuth *
187 lookup_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
188 {
189         SoupSessionHost *host;
190         char *path, *dir;
191         const char *realm, *const_path;
192
193         if (proxy) {
194                 host = session->priv->proxy_host;
195                 const_path = "/";
196         } else {
197                 host = get_host_for_message (session, msg);
198                 const_path = soup_message_get_uri (msg)->path;
199         }
200         g_return_val_if_fail (host != NULL, NULL);
201
202         if (!host->auth_realms)
203                 return NULL;
204
205         path = g_strdup (const_path);
206         dir = path;
207         do {
208                 realm = g_hash_table_lookup (host->auth_realms, path);
209                 if (realm)
210                         break;
211
212                 dir = strrchr (path, '/');
213                 if (dir)
214                         *dir = '\0';
215         } while (dir);
216
217         g_free (path);
218         if (realm)
219                 return g_hash_table_lookup (host->auths, realm);
220         else
221                 return NULL;
222 }
223
224 static void
225 invalidate_auth (SoupSessionHost *host, SoupAuth *auth)
226 {
227         char *realm;
228         gpointer key, value;
229
230         /* Try to just clean up the auth without removing it. */
231         if (soup_auth_invalidate (auth))
232                 return;
233
234         /* Nope, need to remove it completely */
235         realm = g_strdup_printf ("%s:%s",
236                                  soup_auth_get_scheme_name (auth),
237                                  soup_auth_get_realm (auth));
238
239         if (g_hash_table_lookup_extended (host->auths, realm, &key, &value) &&
240             auth == (SoupAuth *)value) {
241                 g_hash_table_remove (host->auths, realm);
242                 g_free (key);
243                 g_object_unref (auth);
244         }
245         g_free (realm);
246 }
247
248 static gboolean
249 authenticate_auth (SoupAuth *auth, SoupMessage *msg)
250 {
251         const SoupUri *uri = soup_message_get_uri (msg);
252
253         if (!uri->user && soup_auth_fn) {
254                 (*soup_auth_fn) (soup_auth_get_scheme_name (auth),
255                                  (SoupUri *) uri,
256                                  soup_auth_get_realm (auth), 
257                                  soup_auth_fn_user_data);
258         }
259
260         if (!uri->user)
261                 return FALSE;
262
263         soup_auth_authenticate (auth, uri->user, uri->passwd);
264         return TRUE;
265 }
266
267 static gboolean
268 update_auth_internal (SoupSession *session, SoupMessage *msg,
269                       const GSList *headers, gboolean proxy,
270                       gboolean prior_auth_failed)
271 {
272         SoupSessionHost *host;
273         SoupAuth *new_auth, *prior_auth, *old_auth;
274         gpointer old_path, old_realm;
275         const SoupUri *msg_uri;
276         const char *path;
277         char *realm;
278         GSList *pspace, *p;
279
280         host = get_host_for_message (session, msg);
281         g_return_val_if_fail (host != NULL, FALSE);
282
283         /* Try to construct a new auth from the headers; if we can't,
284          * there's no way we'll be able to authenticate.
285          */
286         msg_uri = soup_message_get_uri (msg);
287         new_auth = soup_auth_new_from_header_list (headers, msg_uri->authmech);
288         if (!new_auth)
289                 return FALSE;
290
291         /* See if this auth is the same auth we used last time */
292         prior_auth = lookup_auth (session, msg, proxy);
293         if (prior_auth &&
294             G_OBJECT_TYPE (prior_auth) == G_OBJECT_TYPE (new_auth) &&
295             !strcmp (soup_auth_get_realm (prior_auth),
296                      soup_auth_get_realm (new_auth))) {
297                 g_object_unref (new_auth);
298                 if (prior_auth_failed) {
299                         /* The server didn't like the username/password
300                          * we provided before.
301                          */
302                         invalidate_auth (host, prior_auth);
303                         return FALSE;
304                 } else {
305                         /* The user is trying to preauthenticate using
306                          * information we already have, so there's nothing
307                          * that needs to be done.
308                          */
309                         return TRUE;
310                 }
311         }
312
313         if (!host->auth_realms) {
314                 host->auth_realms = g_hash_table_new (g_str_hash, g_str_equal);
315                 host->auths = g_hash_table_new (g_str_hash, g_str_equal);
316         }
317
318         /* Record where this auth realm is used */
319         realm = g_strdup_printf ("%s:%s",
320                                  soup_auth_get_scheme_name (new_auth),
321                                  soup_auth_get_realm (new_auth));
322         pspace = soup_auth_get_protection_space (new_auth, msg_uri);
323         for (p = pspace; p; p = p->next) {
324                 path = p->data;
325                 if (g_hash_table_lookup_extended (host->auth_realms, path,
326                                                   &old_path, &old_realm)) {
327                         g_hash_table_remove (host->auth_realms, old_path);
328                         g_free (old_path);
329                         g_free (old_realm);
330                 }
331
332                 g_hash_table_insert (host->auth_realms,
333                                      g_strdup (path), g_strdup (realm));
334         }
335         soup_auth_free_protection_space (new_auth, pspace);
336
337         /* Now, make sure the auth is recorded. (If there's a
338          * pre-existing auth, we keep that rather than the new one,
339          * since the old one might already be authenticated.)
340          */
341         old_auth = g_hash_table_lookup (host->auths, realm);
342         if (old_auth) {
343                 g_free (realm);
344                 g_object_unref (new_auth);
345                 new_auth = old_auth;
346         } else 
347                 g_hash_table_insert (host->auths, realm, new_auth);
348
349         /* Try to authenticate if needed. */
350         if (!soup_auth_is_authenticated (new_auth))
351                 return authenticate_auth (new_auth, msg);
352
353         return TRUE;
354 }
355
356 static void
357 authorize_handler (SoupMessage *msg, gpointer user_data)
358 {
359         SoupSession *session = user_data;
360         const GSList *headers;
361         gboolean proxy;
362
363         if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
364                 headers = soup_message_get_header_list (msg->response_headers,
365                                                         "Proxy-Authenticate");
366                 proxy = TRUE;
367         } else {
368                 headers = soup_message_get_header_list (msg->response_headers,
369                                                         "WWW-Authenticate");
370                 proxy = FALSE;
371         }
372
373         if (update_auth_internal (session, msg, headers, proxy, TRUE))
374                 soup_session_requeue_message (session, msg);
375 }
376
377 static void
378 redirect_handler (SoupMessage *msg, gpointer user_data)
379 {
380         SoupSession *session = user_data;
381         const char *new_loc;
382         const SoupUri *old_uri;
383         SoupUri *new_uri;
384
385         new_loc = soup_message_get_header (msg->response_headers, "Location");
386         if (!new_loc)
387                 return;
388         new_uri = soup_uri_new (new_loc);
389         if (!new_uri)
390                 goto INVALID_REDIRECT;
391
392         old_uri = soup_message_get_uri (msg);
393
394         /* Copy auth info from original URI. */
395         if (old_uri->user && !new_uri->user)
396                 soup_uri_set_auth (new_uri,
397                                    old_uri->user,
398                                    old_uri->passwd,
399                                    old_uri->authmech);
400
401         soup_message_set_uri (msg, new_uri);
402         soup_uri_free (new_uri);
403
404         soup_session_requeue_message (session, msg);
405         return;
406
407  INVALID_REDIRECT:
408         soup_message_set_status_full (msg,
409                                       SOUP_STATUS_MALFORMED,
410                                       "Invalid Redirect URL");
411 }
412
413 static void
414 request_finished (SoupMessage *req, gpointer user_data)
415 {
416         SoupSession *session = user_data;
417
418         soup_message_queue_remove_message (session->priv->queue, req);
419         req->priv->status = SOUP_MESSAGE_STATUS_FINISHED;
420 }
421
422 static void
423 final_finished (SoupMessage *req, gpointer session)
424 {
425         if (!SOUP_MESSAGE_IS_STARTING (req)) {
426                 g_signal_handlers_disconnect_by_func (req, request_finished, session);
427                 g_signal_handlers_disconnect_by_func (req, final_finished, session);
428                 g_object_unref (req);
429         }
430 }
431
432 static void
433 add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
434 {
435         const char *header = proxy ? "Proxy-Authorization" : "Authorization";
436         SoupAuth *auth;
437         char *token;
438
439         soup_message_remove_header (msg->request_headers, header);
440
441         auth = lookup_auth (session, msg, proxy);
442         if (!auth)
443                 return;
444         if (!soup_auth_is_authenticated (auth) &&
445             !authenticate_auth (auth, msg))
446                 return;
447
448         token = soup_auth_get_authorization (auth, msg);
449         if (token) {
450                 soup_message_add_header (msg->request_headers, header, token);
451                 g_free (token);
452         }
453 }
454
455 static void
456 send_request (SoupSession *session, SoupMessage *req, SoupConnection *conn)
457 {
458         req->priv->status = SOUP_MESSAGE_STATUS_RUNNING;
459
460         add_auth (session, req, FALSE);
461         if (session->priv->proxy_uri)
462                 add_auth (session, req, TRUE);
463         soup_connection_send_request (conn, req);
464 }
465
466 static void
467 find_oldest_connection (gpointer key, gpointer host, gpointer data)
468 {
469         SoupConnection *conn = key, **oldest = data;
470
471         if (!oldest || (soup_connection_last_used (conn) <
472                         soup_connection_last_used (*oldest)))
473                 *oldest = conn;
474 }
475
476 static gboolean
477 try_prune_connection (SoupSession *session)
478 {
479         SoupConnection *oldest = NULL;
480
481         g_hash_table_foreach (session->priv->conns, find_oldest_connection,
482                               &oldest);
483         if (oldest) {
484                 soup_connection_disconnect (oldest);
485                 g_object_unref (oldest);
486                 return TRUE;
487         } else
488                 return FALSE;
489 }
490
491 static void connection_closed (SoupConnection *conn, SoupSession *session);
492
493 static void
494 cleanup_connection (SoupSession *session, SoupConnection *conn)
495 {
496         SoupSessionHost *host =
497                 g_hash_table_lookup (session->priv->conns, conn);
498
499         g_return_if_fail (host != NULL);
500
501         g_hash_table_remove (session->priv->conns, conn);
502         g_signal_handlers_disconnect_by_func (conn, connection_closed, session);
503         session->priv->num_conns--;
504
505         host->connections = g_slist_remove (host->connections, conn);
506         host->num_conns--;
507 }
508
509 static void
510 connection_closed (SoupConnection *conn, SoupSession *session)
511 {
512         cleanup_connection (session, conn);
513
514         /* Run the queue in case anyone was waiting for a connection
515          * to be closed.
516          */
517         run_queue (session, FALSE);
518 }
519
520 static void
521 got_connection (SoupConnection *conn, guint status, gpointer user_data)
522 {
523         SoupSession *session = user_data;
524         SoupSessionHost *host = g_hash_table_lookup (session->priv->conns, conn);
525
526         g_return_if_fail (host != NULL);
527
528         if (status == SOUP_STATUS_OK) {
529                 host->connections = g_slist_prepend (host->connections, conn);
530                 run_queue (session, FALSE);
531                 return;
532         }
533
534         /* We failed */
535         cleanup_connection (session, conn);
536         g_object_unref (conn);
537
538         if (host->connections) {
539                 /* Something went wrong this time, but we have at
540                  * least one open connection to this host. So just
541                  * leave the message in the queue so it can use that
542                  * connection once it's free.
543                  */
544                 return;
545         }
546
547         /* Flush any queued messages for this host */
548         host->error = status;
549         run_queue (session, FALSE);
550
551         if (status != SOUP_STATUS_CANT_RESOLVE &&
552             status != SOUP_STATUS_CANT_RESOLVE_PROXY) {
553                 /* If the error was "can't resolve", then it's not likely
554                  * to improve. But if it was something else, it may have
555                  * been transient, so we clear the error so the user can
556                  * try again later.
557                  */
558                 host->error = 0;
559         }
560 }
561
562 static gboolean
563 run_queue (SoupSession *session, gboolean try_pruning)
564 {
565         SoupMessageQueueIter iter;
566         SoupMessage *msg;
567         SoupConnection *conn;
568         SoupSessionHost *host;
569         gboolean skipped_any = FALSE, started_any = FALSE;
570         GSList *conns;
571
572         /* FIXME: prefer CONNECTING messages */
573
574  try_again:
575         for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
576
577                 if (!SOUP_MESSAGE_IS_STARTING (msg))
578                         continue;
579
580                 host = get_host_for_message (session, msg);
581
582                 /* If the hostname is known to be bad, fail right away */
583                 if (host->error) {
584                         soup_message_set_status (msg, host->error);
585                         soup_message_finished (msg);
586                 }
587
588                 /* If there is an idle connection, use it */
589                 for (conns = host->connections; conns; conns = conns->next) {
590                         if (!soup_connection_is_in_use (conns->data))
591                                 break;
592                 }
593                 if (conns) {
594                         send_request (session, msg, conns->data);
595                         started_any = TRUE;
596                         continue;
597                 }
598
599                 if (msg->priv->status == SOUP_MESSAGE_STATUS_CONNECTING) {
600                         /* We already started a connection for this
601                          * message, so don't start another one.
602                          */
603                         continue;
604                 }
605
606                 /* If we have the max number of per-host connections
607                  * or total connections open, we'll have to wait.
608                  */
609                 if (host->num_conns >= session->priv->max_conns_per_host)
610                         continue;
611                 else if (session->priv->num_conns >= session->priv->max_conns) {
612                         /* In this case, closing an idle connection
613                          * somewhere else would let us open one here.
614                          */
615                         skipped_any = TRUE;
616                         continue;
617                 }
618
619                 /* Otherwise, open a new connection */
620                 if (session->priv->proxy_uri &&
621                     host->root_uri->protocol == SOUP_PROTOCOL_HTTPS) {
622                         conn = soup_connection_new_tunnel (
623                                 session->priv->proxy_uri, host->root_uri,
624                                 got_connection, session);
625                 } else if (session->priv->proxy_uri) {
626                         conn = soup_connection_new_proxy (
627                                 session->priv->proxy_uri,
628                                 got_connection, session);
629                 } else {
630                         conn = soup_connection_new (host->root_uri,
631                                                     got_connection, session);
632                 }
633
634                 g_signal_connect (conn, "disconnected",
635                                   G_CALLBACK (connection_closed), session);
636                 g_hash_table_insert (session->priv->conns, conn, host);
637                 session->priv->num_conns++;
638
639                 /* Increment the host's connection count, but don't add
640                  * this connection to the list yet, since it's not ready.
641                  */
642                 host->num_conns++;
643
644                 /* Mark the request as connecting, so we don't try to
645                  * open another new connection for it next time around.
646                  */
647                 msg->priv->status = SOUP_MESSAGE_STATUS_CONNECTING;
648
649                 started_any = TRUE;
650         }
651
652         if (try_pruning && skipped_any && !started_any) {
653                 /* We didn't manage to start any message, but there is
654                  * at least one message in the queue that could be
655                  * sent if we pruned an idle connection from some
656                  * other server.
657                  */
658                 if (try_prune_connection (session)) {
659                         try_pruning = FALSE;
660                         goto try_again;
661                 }
662         }
663
664         return started_any;
665 }
666
667 static gboolean
668 idle_run_queue (gpointer user_data)
669 {
670         SoupSession *session = user_data;
671
672         session->priv->queue_idle_tag = 0;
673         run_queue (session, TRUE);
674         return FALSE;
675 }
676
677 static void
678 queue_message (SoupSession *session, SoupMessage *req, gboolean requeue)
679 {
680         soup_message_prepare (req);
681
682         req->priv->status = SOUP_MESSAGE_STATUS_QUEUED;
683         if (!requeue)
684                 soup_message_queue_append (session->priv->queue, req);
685
686         if (!session->priv->queue_idle_tag) {
687                 session->priv->queue_idle_tag =
688                         g_idle_add (idle_run_queue, session);
689         }
690 }
691
692 /**
693  * soup_session_queue_message:
694  * @session: a #SoupSession
695  * @req: the message to queue
696  * @callback: a #SoupCallbackFn which will be called after the message
697  * completes or when an unrecoverable error occurs.
698  * @user_data: a pointer passed to @callback.
699  * 
700  * Queues the message @req for sending. All messages are processed
701  * while the glib main loop runs. If @req has been processed before,
702  * any resources related to the time it was last sent are freed.
703  *
704  * Upon message completion, the callback specified in @callback will
705  * be invoked. If after returning from this callback the message has
706  * not been requeued, @req will be unreffed.
707  */
708 void
709 soup_session_queue_message (SoupSession *session, SoupMessage *req,
710                             SoupCallbackFn callback, gpointer user_data)
711 {
712         g_return_if_fail (SOUP_IS_SESSION (session));
713         g_return_if_fail (SOUP_IS_MESSAGE (req));
714
715         g_signal_connect (req, "finished",
716                           G_CALLBACK (request_finished), session);
717         if (callback) {
718                 g_signal_connect (req, "finished",
719                                   G_CALLBACK (callback), user_data);
720         }
721         g_signal_connect_after (req, "finished",
722                                 G_CALLBACK (final_finished), session);
723
724         soup_message_add_status_code_handler  (req, SOUP_STATUS_UNAUTHORIZED,
725                                                SOUP_HANDLER_POST_BODY,
726                                                authorize_handler, session);
727         soup_message_add_status_code_handler  (req,
728                                                SOUP_STATUS_PROXY_UNAUTHORIZED,
729                                                SOUP_HANDLER_POST_BODY,
730                                                authorize_handler, session);
731
732         if (!(req->priv->msg_flags & SOUP_MESSAGE_NO_REDIRECT)) {
733                 soup_message_add_status_class_handler (
734                         req, SOUP_STATUS_CLASS_REDIRECT,
735                         SOUP_HANDLER_POST_BODY,
736                         redirect_handler, session);
737         }
738
739         queue_message (session, req, FALSE);
740 }
741
742 /**
743  * soup_session_requeue_message:
744  * @session: a #SoupSession
745  * @req: the message to requeue
746  *
747  * This causes @req to be placed back on the queue to be attempted
748  * again.
749  **/
750 void
751 soup_session_requeue_message (SoupSession *session, SoupMessage *req)
752 {
753         g_return_if_fail (SOUP_IS_SESSION (session));
754         g_return_if_fail (SOUP_IS_MESSAGE (req));
755
756         queue_message (session, req, TRUE);
757 }
758
759
760 /**
761  * soup_session_send_message:
762  * @session: a #SoupSession
763  * @req: the message to send
764  * 
765  * Synchronously send @req. This call will not return until the
766  * transfer is finished successfully or there is an unrecoverable
767  * error.
768  *
769  * @req is not freed upon return.
770  *
771  * Return value: the HTTP status code of the response
772  */
773 guint
774 soup_session_send_message (SoupSession *session, SoupMessage *req)
775 {
776         g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
777         g_return_val_if_fail (SOUP_IS_MESSAGE (req), SOUP_STATUS_MALFORMED);
778
779         /* Balance out the unref that final_finished will do */
780         g_object_ref (req);
781
782         soup_session_queue_message (session, req, NULL, NULL);
783
784         while (1) {
785                 g_main_iteration (TRUE);
786
787                 if (req->priv->status == SOUP_MESSAGE_STATUS_FINISHED ||
788                     SOUP_STATUS_IS_TRANSPORT (req->status_code))
789                         break;
790         }
791
792         return req->status_code;
793 }