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