Move RESPONSE_BLOCK_SIZE #define here from soup-private.h
[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-auth.h"
17 #include "soup-session.h"
18 #include "soup-connection.h"
19 #include "soup-connection-ntlm.h"
20 #include "soup-marshal.h"
21 #include "soup-message-queue.h"
22 #include "soup-uri.h"
23
24 typedef struct {
25         SoupUri    *root_uri;
26         guint       error;
27
28         GSList     *connections;      /* CONTAINS: SoupConnection */
29         guint       num_conns;
30
31         GHashTable *auth_realms;      /* path -> scheme:realm */
32         GHashTable *auths;            /* scheme:realm -> SoupAuth */
33 } SoupSessionHost;
34
35 struct SoupSessionPrivate {
36         SoupUri *proxy_uri;
37         guint max_conns, max_conns_per_host;
38         gboolean use_ntlm;
39
40         SoupMessageQueue *queue;
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 static void     free_host      (SoupSessionHost *host, SoupSession *session);
52
53 static gboolean run_queue (SoupSession *session, gboolean try_pruning);
54
55 #define SOUP_SESSION_MAX_CONNS_DEFAULT 10
56 #define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 4
57
58 #define PARENT_TYPE G_TYPE_OBJECT
59 static GObjectClass *parent_class;
60
61 enum {
62         AUTHENTICATE,
63         REAUTHENTICATE,
64         LAST_SIGNAL
65 };
66
67 static guint signals[LAST_SIGNAL] = { 0 };
68
69 enum {
70   PROP_0,
71
72   PROP_PROXY_URI,
73   PROP_MAX_CONNS,
74   PROP_MAX_CONNS_PER_HOST,
75   PROP_USE_NTLM,
76
77   LAST_PROP
78 };
79
80 static void set_property (GObject *object, guint prop_id,
81                           const GValue *value, GParamSpec *pspec);
82 static void get_property (GObject *object, guint prop_id,
83                           GValue *value, GParamSpec *pspec);
84
85 static void
86 init (GObject *object)
87 {
88         SoupSession *session = SOUP_SESSION (object);
89
90         session->priv = g_new0 (SoupSessionPrivate, 1);
91         session->priv->queue = soup_message_queue_new ();
92         session->priv->hosts = g_hash_table_new (host_uri_hash,
93                                                  host_uri_equal);
94         session->priv->conns = g_hash_table_new (NULL, NULL);
95
96         session->priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT;
97         session->priv->max_conns_per_host = SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT;
98 }
99
100 static gboolean
101 foreach_free_host (gpointer key, gpointer host, gpointer session)
102 {
103         free_host (host, session);
104         return TRUE;
105 }
106
107 static void
108 cleanup_hosts (SoupSession *session)
109 {
110         g_hash_table_foreach_remove (session->priv->hosts,
111                                      foreach_free_host, session);
112 }
113
114 static void
115 dispose (GObject *object)
116 {
117         SoupSession *session = SOUP_SESSION (object);
118
119         soup_session_abort (session);
120         cleanup_hosts (session);
121
122         G_OBJECT_CLASS (parent_class)->dispose (object);
123 }
124
125 static void
126 finalize (GObject *object)
127 {
128         SoupSession *session = SOUP_SESSION (object);
129
130         soup_message_queue_destroy (session->priv->queue);
131         g_hash_table_destroy (session->priv->hosts);
132         g_hash_table_destroy (session->priv->conns);
133         g_free (session->priv);
134
135         G_OBJECT_CLASS (parent_class)->finalize (object);
136 }
137
138 static void
139 class_init (GObjectClass *object_class)
140 {
141         parent_class = g_type_class_ref (PARENT_TYPE);
142
143         /* virtual method override */
144         object_class->dispose = dispose;
145         object_class->finalize = finalize;
146         object_class->set_property = set_property;
147         object_class->get_property = get_property;
148
149         /* signals */
150         signals[AUTHENTICATE] =
151                 g_signal_new ("authenticate",
152                               G_OBJECT_CLASS_TYPE (object_class),
153                               G_SIGNAL_RUN_FIRST,
154                               G_STRUCT_OFFSET (SoupSessionClass, authenticate),
155                               NULL, NULL,
156                               soup_marshal_NONE__OBJECT_STRING_STRING_POINTER_POINTER,
157                               G_TYPE_NONE, 5,
158                               SOUP_TYPE_MESSAGE,
159                               G_TYPE_STRING,
160                               G_TYPE_STRING,
161                               G_TYPE_POINTER,
162                               G_TYPE_POINTER);
163         signals[REAUTHENTICATE] =
164                 g_signal_new ("reauthenticate",
165                               G_OBJECT_CLASS_TYPE (object_class),
166                               G_SIGNAL_RUN_FIRST,
167                               G_STRUCT_OFFSET (SoupSessionClass, reauthenticate),
168                               NULL, NULL,
169                               soup_marshal_NONE__OBJECT_STRING_STRING_POINTER_POINTER,
170                               G_TYPE_NONE, 5,
171                               SOUP_TYPE_MESSAGE,
172                               G_TYPE_STRING,
173                               G_TYPE_STRING,
174                               G_TYPE_POINTER,
175                               G_TYPE_POINTER);
176
177         /* properties */
178         g_object_class_install_property (
179                 object_class, PROP_PROXY_URI,
180                 g_param_spec_pointer (SOUP_SESSION_PROXY_URI,
181                                       "Proxy URI",
182                                       "The HTTP Proxy to use for this session",
183                                       G_PARAM_READWRITE));
184         g_object_class_install_property (
185                 object_class, PROP_MAX_CONNS,
186                 g_param_spec_int (SOUP_SESSION_MAX_CONNS,
187                                   "Max Connection Count",
188                                   "The maximum number of connections that the session can open at once",
189                                   1,
190                                   G_MAXINT,
191                                   10,
192                                   G_PARAM_READWRITE));
193         g_object_class_install_property (
194                 object_class, PROP_MAX_CONNS_PER_HOST,
195                 g_param_spec_int (SOUP_SESSION_MAX_CONNS_PER_HOST,
196                                   "Max Per-Host Connection Count",
197                                   "The maximum number of connections that the session can open at once to a given host",
198                                   1,
199                                   G_MAXINT,
200                                   4,
201                                   G_PARAM_READWRITE));
202         g_object_class_install_property (
203                 object_class, PROP_USE_NTLM,
204                 g_param_spec_boolean (SOUP_SESSION_USE_NTLM,
205                                       "Use NTLM",
206                                       "Whether or not to use NTLM authentication",
207                                       FALSE,
208                                       G_PARAM_READWRITE));
209 }
210
211 SOUP_MAKE_TYPE (soup_session, SoupSession, class_init, init, PARENT_TYPE)
212
213 SoupSession *
214 soup_session_new (void)
215 {
216         return g_object_new (SOUP_TYPE_SESSION, NULL);
217 }
218
219 SoupSession *
220 soup_session_new_with_options (const char *optname1, ...)
221 {
222         SoupSession *session;
223         va_list ap;
224
225         va_start (ap, optname1);
226         session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION, optname1, ap);
227         va_end (ap);
228
229         return session;
230 }
231
232 static void
233 set_property (GObject *object, guint prop_id,
234               const GValue *value, GParamSpec *pspec)
235 {
236         SoupSession *session = SOUP_SESSION (object);
237         gpointer pval;
238
239         switch (prop_id) {
240         case PROP_PROXY_URI:
241                 if (session->priv->proxy_uri)
242                         soup_uri_free (session->priv->proxy_uri);
243                 pval = g_value_get_pointer (value);
244                 session->priv->proxy_uri = pval ? soup_uri_copy (pval) : NULL;
245
246                 soup_session_abort (session);
247                 cleanup_hosts (session);
248                 break;
249         case PROP_MAX_CONNS:
250                 session->priv->max_conns = g_value_get_int (value);
251                 break;
252         case PROP_MAX_CONNS_PER_HOST:
253                 session->priv->max_conns_per_host = g_value_get_int (value);
254                 break;
255         case PROP_USE_NTLM:
256                 session->priv->use_ntlm = g_value_get_boolean (value);
257                 break;
258         default:
259                 break;
260         }
261 }
262
263 static void
264 get_property (GObject *object, guint prop_id,
265               GValue *value, GParamSpec *pspec)
266 {
267         SoupSession *session = SOUP_SESSION (object);
268
269         switch (prop_id) {
270         case PROP_PROXY_URI:
271                 g_value_set_pointer (value, session->priv->proxy_uri ?
272                                      soup_uri_copy (session->priv->proxy_uri) :
273                                      NULL);
274                 break;
275         case PROP_MAX_CONNS:
276                 g_value_set_int (value, session->priv->max_conns);
277                 break;
278         case PROP_MAX_CONNS_PER_HOST:
279                 g_value_set_int (value, session->priv->max_conns_per_host);
280                 break;
281         case PROP_USE_NTLM:
282                 g_value_set_boolean (value, session->priv->use_ntlm);
283                 break;
284         default:
285                 break;
286         }
287 }
288
289
290 /* Hosts */
291 static guint
292 host_uri_hash (gconstpointer key)
293 {
294         const SoupUri *uri = key;
295
296         return (uri->protocol << 16) + uri->port + g_str_hash (uri->host);
297 }
298
299 static gboolean
300 host_uri_equal (gconstpointer v1, gconstpointer v2)
301 {
302         const SoupUri *one = v1;
303         const SoupUri *two = v2;
304
305         if (one->protocol != two->protocol)
306                 return FALSE;
307         if (one->port != two->port)
308                 return FALSE;
309
310         return strcmp (one->host, two->host) == 0;
311 }
312
313 static SoupSessionHost *
314 get_host_for_message (SoupSession *session, SoupMessage *msg)
315 {
316         SoupSessionHost *host;
317         const SoupUri *source = soup_message_get_uri (msg);
318
319         host = g_hash_table_lookup (session->priv->hosts, source);
320         if (host)
321                 return host;
322
323         host = g_new0 (SoupSessionHost, 1);
324         host->root_uri = soup_uri_copy_root (source);
325
326         g_hash_table_insert (session->priv->hosts, host->root_uri, host);
327         return host;
328 }
329
330 static void
331 free_realm (gpointer path, gpointer scheme_realm, gpointer data)
332 {
333         g_free (path);
334         g_free (scheme_realm);
335 }
336
337 static void
338 free_auth (gpointer scheme_realm, gpointer auth, gpointer data)
339 {
340         g_free (scheme_realm);
341         g_object_unref (auth);
342 }
343
344 static void
345 free_host (SoupSessionHost *host, SoupSession *session)
346 {
347         while (host->connections) {
348                 SoupConnection *conn = host->connections->data;
349
350                 host->connections = g_slist_remove (host->connections, conn);
351                 soup_connection_disconnect (conn);
352         }
353
354         if (host->auth_realms) {
355                 g_hash_table_foreach (host->auth_realms, free_realm, NULL);
356                 g_hash_table_destroy (host->auth_realms);
357         }
358         if (host->auths) {
359                 g_hash_table_foreach (host->auths, free_auth, NULL);
360                 g_hash_table_destroy (host->auths);
361         }
362
363         soup_uri_free (host->root_uri);
364 }       
365
366 /* Authentication */
367
368 static SoupAuth *
369 lookup_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
370 {
371         SoupSessionHost *host;
372         char *path, *dir;
373         const char *realm, *const_path;
374
375         if (proxy) {
376                 host = session->priv->proxy_host;
377                 const_path = "/";
378         } else {
379                 host = get_host_for_message (session, msg);
380                 const_path = soup_message_get_uri (msg)->path;
381         }
382         g_return_val_if_fail (host != NULL, NULL);
383
384         if (!host->auth_realms)
385                 return NULL;
386
387         path = g_strdup (const_path);
388         dir = path;
389         do {
390                 realm = g_hash_table_lookup (host->auth_realms, path);
391                 if (realm)
392                         break;
393
394                 dir = strrchr (path, '/');
395                 if (dir)
396                         *dir = '\0';
397         } while (dir);
398
399         g_free (path);
400         if (realm)
401                 return g_hash_table_lookup (host->auths, realm);
402         else
403                 return NULL;
404 }
405
406 static void
407 invalidate_auth (SoupSessionHost *host, SoupAuth *auth)
408 {
409         char *realm;
410         gpointer key, value;
411
412         realm = g_strdup_printf ("%s:%s",
413                                  soup_auth_get_scheme_name (auth),
414                                  soup_auth_get_realm (auth));
415
416         if (g_hash_table_lookup_extended (host->auths, realm, &key, &value) &&
417             auth == (SoupAuth *)value) {
418                 g_hash_table_remove (host->auths, realm);
419                 g_free (key);
420                 g_object_unref (auth);
421         }
422         g_free (realm);
423 }
424
425 static gboolean
426 authenticate_auth (SoupSession *session, SoupAuth *auth,
427                    SoupMessage *msg, gboolean prior_auth_failed)
428 {
429         const SoupUri *uri = soup_message_get_uri (msg);
430         char *username = NULL, *password = NULL;
431
432         if (uri->passwd && !prior_auth_failed) {
433                 soup_auth_authenticate (auth, uri->user, uri->passwd);
434                 return TRUE;
435         }
436
437         g_signal_emit (session, signals[prior_auth_failed ? REAUTHENTICATE : AUTHENTICATE], 0,
438                        msg, soup_auth_get_scheme_name (auth),
439                        soup_auth_get_realm (auth),
440                        &username, &password);
441         if (username || password)
442                 soup_auth_authenticate (auth, username, password);
443         if (username)
444                 g_free (username);
445         if (password) {
446                 memset (password, 0, strlen (password));
447                 g_free (password);
448         }
449
450         return soup_auth_is_authenticated (auth);
451 }
452
453 static gboolean
454 update_auth_internal (SoupSession *session, SoupMessage *msg,
455                       const GSList *headers, gboolean proxy,
456                       gboolean got_unauthorized)
457 {
458         SoupSessionHost *host;
459         SoupAuth *new_auth, *prior_auth, *old_auth;
460         gpointer old_path, old_realm;
461         const SoupUri *msg_uri;
462         const char *path;
463         char *realm;
464         GSList *pspace, *p;
465         gboolean prior_auth_failed = FALSE;
466
467         host = get_host_for_message (session, msg);
468         g_return_val_if_fail (host != NULL, FALSE);
469
470         /* Try to construct a new auth from the headers; if we can't,
471          * there's no way we'll be able to authenticate.
472          */
473         msg_uri = soup_message_get_uri (msg);
474         new_auth = soup_auth_new_from_header_list (headers);
475         if (!new_auth)
476                 return FALSE;
477
478         /* See if this auth is the same auth we used last time */
479         prior_auth = lookup_auth (session, msg, proxy);
480         if (prior_auth &&
481             G_OBJECT_TYPE (prior_auth) == G_OBJECT_TYPE (new_auth) &&
482             !strcmp (soup_auth_get_realm (prior_auth),
483                      soup_auth_get_realm (new_auth))) {
484                 if (!got_unauthorized) {
485                         /* The user is just trying to preauthenticate
486                          * using information we already have, so
487                          * there's nothing more that needs to be done.
488                          */
489                         g_object_unref (new_auth);
490                         return TRUE;
491                 }
492
493                 /* The server didn't like the username/password we
494                  * provided before. Invalidate it and note this fact.
495                  */
496                 invalidate_auth (host, prior_auth);
497                 prior_auth_failed = TRUE;
498         }
499
500         if (!host->auth_realms) {
501                 host->auth_realms = g_hash_table_new (g_str_hash, g_str_equal);
502                 host->auths = g_hash_table_new (g_str_hash, g_str_equal);
503         }
504
505         /* Record where this auth realm is used */
506         realm = g_strdup_printf ("%s:%s",
507                                  soup_auth_get_scheme_name (new_auth),
508                                  soup_auth_get_realm (new_auth));
509         pspace = soup_auth_get_protection_space (new_auth, msg_uri);
510         for (p = pspace; p; p = p->next) {
511                 path = p->data;
512                 if (g_hash_table_lookup_extended (host->auth_realms, path,
513                                                   &old_path, &old_realm)) {
514                         g_hash_table_remove (host->auth_realms, old_path);
515                         g_free (old_path);
516                         g_free (old_realm);
517                 }
518
519                 g_hash_table_insert (host->auth_realms,
520                                      g_strdup (path), g_strdup (realm));
521         }
522         soup_auth_free_protection_space (new_auth, pspace);
523
524         /* Now, make sure the auth is recorded. (If there's a
525          * pre-existing auth, we keep that rather than the new one,
526          * since the old one might already be authenticated.)
527          */
528         old_auth = g_hash_table_lookup (host->auths, realm);
529         if (old_auth) {
530                 g_free (realm);
531                 g_object_unref (new_auth);
532                 new_auth = old_auth;
533         } else 
534                 g_hash_table_insert (host->auths, realm, new_auth);
535
536         /* If we need to authenticate, try to do it. */
537         if (!soup_auth_is_authenticated (new_auth)) {
538                 return authenticate_auth (session, new_auth,
539                                           msg, prior_auth_failed);
540         }
541
542         /* Otherwise we're good. */
543         return TRUE;
544 }
545
546 static void
547 connection_authenticate (SoupConnection *conn, SoupMessage *msg,
548                          const char *auth_type, const char *auth_realm,
549                          char **username, char **password, gpointer session)
550 {
551         g_signal_emit (session, signals[AUTHENTICATE], 0,
552                        msg, auth_type, auth_realm, username, password);
553 }
554
555 static void
556 connection_reauthenticate (SoupConnection *conn, SoupMessage *msg,
557                            const char *auth_type, const char *auth_realm,
558                            char **username, char **password,
559                            gpointer user_data)
560 {
561         g_signal_emit (conn, signals[REAUTHENTICATE], 0,
562                        msg, auth_type, auth_realm, username, password);
563 }
564
565
566 static void
567 authorize_handler (SoupMessage *msg, gpointer user_data)
568 {
569         SoupSession *session = user_data;
570         const GSList *headers;
571         gboolean proxy;
572
573         if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
574                 headers = soup_message_get_header_list (msg->response_headers,
575                                                         "Proxy-Authenticate");
576                 proxy = TRUE;
577         } else {
578                 headers = soup_message_get_header_list (msg->response_headers,
579                                                         "WWW-Authenticate");
580                 proxy = FALSE;
581         }
582         if (!headers)
583                 return;
584
585         if (update_auth_internal (session, msg, headers, proxy, TRUE))
586                 soup_session_requeue_message (session, msg);
587 }
588
589 static void
590 redirect_handler (SoupMessage *msg, gpointer user_data)
591 {
592         SoupSession *session = user_data;
593         const char *new_loc;
594         SoupUri *new_uri;
595
596         new_loc = soup_message_get_header (msg->response_headers, "Location");
597         if (!new_loc)
598                 return;
599         new_uri = soup_uri_new (new_loc);
600         if (!new_uri)
601                 goto INVALID_REDIRECT;
602
603         soup_message_set_uri (msg, new_uri);
604         soup_uri_free (new_uri);
605
606         soup_session_requeue_message (session, msg);
607         return;
608
609  INVALID_REDIRECT:
610         soup_message_set_status_full (msg,
611                                       SOUP_STATUS_MALFORMED,
612                                       "Invalid Redirect URL");
613 }
614
615 static void
616 request_finished (SoupMessage *req, gpointer user_data)
617 {
618         if (!SOUP_MESSAGE_IS_STARTING (req))
619                 req->status = SOUP_MESSAGE_STATUS_FINISHED;
620 }
621
622 static void
623 final_finished (SoupMessage *req, gpointer user_data)
624 {
625         SoupSession *session = user_data;
626
627         if (!SOUP_MESSAGE_IS_STARTING (req)) {
628                 soup_message_queue_remove_message (session->priv->queue, req);
629
630                 g_signal_handlers_disconnect_by_func (req, request_finished, session);
631                 g_signal_handlers_disconnect_by_func (req, final_finished, session);
632                 g_object_unref (req);
633         }
634
635         run_queue (session, FALSE);
636 }
637
638 static void
639 add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
640 {
641         const char *header = proxy ? "Proxy-Authorization" : "Authorization";
642         SoupAuth *auth;
643         char *token;
644
645         soup_message_remove_header (msg->request_headers, header);
646
647         auth = lookup_auth (session, msg, proxy);
648         if (!auth)
649                 return;
650         if (!soup_auth_is_authenticated (auth) &&
651             !authenticate_auth (session, auth, msg, FALSE))
652                 return;
653
654         token = soup_auth_get_authorization (auth, msg);
655         if (token) {
656                 soup_message_add_header (msg->request_headers, header, token);
657                 g_free (token);
658         }
659 }
660
661 static void
662 send_request (SoupSession *session, SoupMessage *req, SoupConnection *conn)
663 {
664         req->status = SOUP_MESSAGE_STATUS_RUNNING;
665
666         add_auth (session, req, FALSE);
667         if (session->priv->proxy_uri)
668                 add_auth (session, req, TRUE);
669         soup_connection_send_request (conn, req);
670 }
671
672 static void
673 find_oldest_connection (gpointer key, gpointer host, gpointer data)
674 {
675         SoupConnection *conn = key, **oldest = data;
676
677         /* Don't prune a connection that hasn't even been used yet. */
678         if (soup_connection_last_used (conn) == 0)
679                 return;
680
681         if (!*oldest || (soup_connection_last_used (conn) <
682                          soup_connection_last_used (*oldest)))
683                 *oldest = conn;
684 }
685
686 static gboolean
687 try_prune_connection (SoupSession *session)
688 {
689         SoupConnection *oldest = NULL;
690
691         g_hash_table_foreach (session->priv->conns, find_oldest_connection,
692                               &oldest);
693         if (oldest) {
694                 soup_connection_disconnect (oldest);
695                 g_object_unref (oldest);
696                 return TRUE;
697         } else
698                 return FALSE;
699 }
700
701 static void connection_closed (SoupConnection *conn, SoupSession *session);
702
703 static void
704 cleanup_connection (SoupSession *session, SoupConnection *conn)
705 {
706         SoupSessionHost *host =
707                 g_hash_table_lookup (session->priv->conns, conn);
708
709         g_return_if_fail (host != NULL);
710
711         g_hash_table_remove (session->priv->conns, conn);
712         g_signal_handlers_disconnect_by_func (conn, connection_closed, session);
713         session->priv->num_conns--;
714
715         host->connections = g_slist_remove (host->connections, conn);
716         host->num_conns--;
717 }
718
719 static void
720 connection_closed (SoupConnection *conn, SoupSession *session)
721 {
722         cleanup_connection (session, conn);
723
724         /* Run the queue in case anyone was waiting for a connection
725          * to be closed.
726          */
727         run_queue (session, FALSE);
728 }
729
730 static void
731 got_connection (SoupConnection *conn, guint status, gpointer user_data)
732 {
733         SoupSession *session = user_data;
734         SoupSessionHost *host = g_hash_table_lookup (session->priv->conns, conn);
735
736         g_return_if_fail (host != NULL);
737
738         if (status == SOUP_STATUS_OK) {
739                 host->connections = g_slist_prepend (host->connections, conn);
740                 run_queue (session, FALSE);
741                 return;
742         }
743
744         /* We failed */
745         cleanup_connection (session, conn);
746         g_object_unref (conn);
747
748         if (host->connections) {
749                 /* Something went wrong this time, but we have at
750                  * least one open connection to this host. So just
751                  * leave the message in the queue so it can use that
752                  * connection once it's free.
753                  */
754                 return;
755         }
756
757         /* Flush any queued messages for this host */
758         host->error = status;
759         run_queue (session, FALSE);
760
761         if (status != SOUP_STATUS_CANT_RESOLVE &&
762             status != SOUP_STATUS_CANT_RESOLVE_PROXY) {
763                 /* If the error was "can't resolve", then it's not likely
764                  * to improve. But if it was something else, it may have
765                  * been transient, so we clear the error so the user can
766                  * try again later.
767                  */
768                 host->error = 0;
769         }
770 }
771
772 static gboolean
773 run_queue (SoupSession *session, gboolean try_pruning)
774 {
775         SoupMessageQueueIter iter;
776         SoupMessage *msg;
777         SoupConnection *conn;
778         SoupSessionHost *host;
779         gboolean skipped_any = FALSE, started_any = FALSE;
780         GSList *conns;
781
782         /* FIXME: prefer CONNECTING messages */
783
784  try_again:
785         for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
786
787                 if (!SOUP_MESSAGE_IS_STARTING (msg))
788                         continue;
789
790                 host = get_host_for_message (session, msg);
791
792                 /* If the hostname is known to be bad, fail right away */
793                 if (host->error) {
794                         soup_message_set_status (msg, host->error);
795                         soup_message_finished (msg);
796                 }
797
798                 /* If there is an idle connection, use it */
799                 for (conns = host->connections; conns; conns = conns->next) {
800                         if (!soup_connection_is_in_use (conns->data))
801                                 break;
802                 }
803                 if (conns) {
804                         send_request (session, msg, conns->data);
805                         started_any = TRUE;
806                         continue;
807                 }
808
809                 if (msg->status == SOUP_MESSAGE_STATUS_CONNECTING) {
810                         /* We already started a connection for this
811                          * message, so don't start another one.
812                          */
813                         continue;
814                 }
815
816                 /* If we have the max number of per-host connections
817                  * or total connections open, we'll have to wait.
818                  */
819                 if (host->num_conns >= session->priv->max_conns_per_host)
820                         continue;
821                 else if (session->priv->num_conns >= session->priv->max_conns) {
822                         /* In this case, closing an idle connection
823                          * somewhere else would let us open one here.
824                          */
825                         skipped_any = TRUE;
826                         continue;
827                 }
828
829                 /* Otherwise, open a new connection */
830                 conn = g_object_new (
831                         (session->priv->use_ntlm ?
832                          SOUP_TYPE_CONNECTION_NTLM : SOUP_TYPE_CONNECTION),
833                         SOUP_CONNECTION_DEST_URI, host->root_uri,
834                         SOUP_CONNECTION_PROXY_URI, session->priv->proxy_uri,
835                         NULL);
836                 g_signal_connect (conn, "authenticate",
837                                   G_CALLBACK (connection_authenticate),
838                                   session);
839                 g_signal_connect (conn, "reauthenticate",
840                                   G_CALLBACK (connection_reauthenticate),
841                                   session);
842
843                 soup_connection_connect_async (conn, got_connection, session);
844                 g_signal_connect (conn, "disconnected",
845                                   G_CALLBACK (connection_closed), session);
846                 g_hash_table_insert (session->priv->conns, conn, host);
847                 session->priv->num_conns++;
848
849                 /* Increment the host's connection count, but don't add
850                  * this connection to the list yet, since it's not ready.
851                  */
852                 host->num_conns++;
853
854                 /* Mark the request as connecting, so we don't try to
855                  * open another new connection for it next time around.
856                  */
857                 msg->status = SOUP_MESSAGE_STATUS_CONNECTING;
858
859                 started_any = TRUE;
860         }
861
862         if (try_pruning && skipped_any && !started_any) {
863                 /* We didn't manage to start any message, but there is
864                  * at least one message in the queue that could be
865                  * sent if we pruned an idle connection from some
866                  * other server.
867                  */
868                 if (try_prune_connection (session)) {
869                         try_pruning = FALSE;
870                         goto try_again;
871                 }
872         }
873
874         return started_any;
875 }
876
877 static void
878 queue_message (SoupSession *session, SoupMessage *req, gboolean requeue)
879 {
880         req->status = SOUP_MESSAGE_STATUS_QUEUED;
881         if (!requeue) {
882                 soup_message_queue_append (session->priv->queue, req);
883                 run_queue (session, TRUE);
884         }
885 }
886
887 /**
888  * soup_session_queue_message:
889  * @session: a #SoupSession
890  * @req: the message to queue
891  * @callback: a #SoupMessageCallbackFn which will be called after the
892  * message completes or when an unrecoverable error occurs.
893  * @user_data: a pointer passed to @callback.
894  * 
895  * Queues the message @req for sending. All messages are processed
896  * while the glib main loop runs. If @req has been processed before,
897  * any resources related to the time it was last sent are freed.
898  *
899  * Upon message completion, the callback specified in @callback will
900  * be invoked. If after returning from this callback the message has
901  * not been requeued, @req will be unreffed.
902  */
903 void
904 soup_session_queue_message (SoupSession *session, SoupMessage *req,
905                             SoupMessageCallbackFn callback, gpointer user_data)
906 {
907         g_return_if_fail (SOUP_IS_SESSION (session));
908         g_return_if_fail (SOUP_IS_MESSAGE (req));
909
910         g_signal_connect (req, "finished",
911                           G_CALLBACK (request_finished), session);
912         if (callback) {
913                 g_signal_connect (req, "finished",
914                                   G_CALLBACK (callback), user_data);
915         }
916         g_signal_connect_after (req, "finished",
917                                 G_CALLBACK (final_finished), session);
918
919         soup_message_add_status_code_handler  (req, SOUP_STATUS_UNAUTHORIZED,
920                                                SOUP_HANDLER_POST_BODY,
921                                                authorize_handler, session);
922         soup_message_add_status_code_handler  (req,
923                                                SOUP_STATUS_PROXY_UNAUTHORIZED,
924                                                SOUP_HANDLER_POST_BODY,
925                                                authorize_handler, session);
926
927         if (!(soup_message_get_flags (req) & SOUP_MESSAGE_NO_REDIRECT)) {
928                 soup_message_add_status_class_handler (
929                         req, SOUP_STATUS_CLASS_REDIRECT,
930                         SOUP_HANDLER_POST_BODY,
931                         redirect_handler, session);
932         }
933
934         queue_message (session, req, FALSE);
935 }
936
937 /**
938  * soup_session_requeue_message:
939  * @session: a #SoupSession
940  * @req: the message to requeue
941  *
942  * This causes @req to be placed back on the queue to be attempted
943  * again.
944  **/
945 void
946 soup_session_requeue_message (SoupSession *session, SoupMessage *req)
947 {
948         g_return_if_fail (SOUP_IS_SESSION (session));
949         g_return_if_fail (SOUP_IS_MESSAGE (req));
950
951         queue_message (session, req, TRUE);
952 }
953
954
955 /**
956  * soup_session_send_message:
957  * @session: a #SoupSession
958  * @req: the message to send
959  * 
960  * Synchronously send @req. This call will not return until the
961  * transfer is finished successfully or there is an unrecoverable
962  * error.
963  *
964  * @req is not freed upon return.
965  *
966  * Return value: the HTTP status code of the response
967  */
968 guint
969 soup_session_send_message (SoupSession *session, SoupMessage *req)
970 {
971         g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
972         g_return_val_if_fail (SOUP_IS_MESSAGE (req), SOUP_STATUS_MALFORMED);
973
974         /* Balance out the unref that final_finished will do */
975         g_object_ref (req);
976
977         soup_session_queue_message (session, req, NULL, NULL);
978
979         while (req->status != SOUP_MESSAGE_STATUS_FINISHED &&
980                !SOUP_STATUS_IS_TRANSPORT_ERROR (req->status_code))
981                 g_main_iteration (TRUE);
982
983         return req->status_code;
984 }
985
986 /**
987  * soup_session_abort:
988  * @session: the session
989  *
990  * Cancels all pending requests in @session.
991  **/
992 void
993 soup_session_abort (SoupSession *session)
994 {
995         SoupMessageQueueIter iter;
996         SoupMessage *msg;
997
998         for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
999                 soup_message_queue_remove (session->priv->queue, &iter);
1000                 soup_message_cancel (msg);
1001         }
1002 }