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