Only set up a tunnel if the destination protocol is HTTPS.
[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 gboolean
245 safe_uri_equal (const SoupUri *a, const SoupUri *b)
246 {
247         if (!a && !b)
248                 return TRUE;
249
250         if (a && !b || b && !a)
251                 return FALSE;
252
253         return soup_uri_equal (a, b);
254 }
255
256 static void
257 set_property (GObject *object, guint prop_id,
258               const GValue *value, GParamSpec *pspec)
259 {
260         SoupSession *session = SOUP_SESSION (object);
261         gpointer pval;
262         gboolean need_abort = FALSE;
263
264         switch (prop_id) {
265         case PROP_PROXY_URI:
266                 pval = g_value_get_pointer (value);
267
268                 if (!safe_uri_equal (session->priv->proxy_uri, pval))
269                         need_abort = TRUE;
270
271                 if (session->priv->proxy_uri)
272                         soup_uri_free (session->priv->proxy_uri);
273
274                 session->priv->proxy_uri = pval ? soup_uri_copy (pval) : NULL;
275
276                 if (need_abort) {
277                         soup_session_abort (session);
278                         cleanup_hosts (session);
279                 }
280
281                 break;
282         case PROP_MAX_CONNS:
283                 session->priv->max_conns = g_value_get_int (value);
284                 break;
285         case PROP_MAX_CONNS_PER_HOST:
286                 session->priv->max_conns_per_host = g_value_get_int (value);
287                 break;
288         case PROP_USE_NTLM:
289                 session->priv->use_ntlm = g_value_get_boolean (value);
290                 break;
291         case PROP_SSL_CA_FILE:
292                 g_free (session->priv->ssl_ca_file);
293                 session->priv->ssl_ca_file = g_strdup (g_value_get_string (value));
294                 break;
295         default:
296                 break;
297         }
298 }
299
300 static void
301 get_property (GObject *object, guint prop_id,
302               GValue *value, GParamSpec *pspec)
303 {
304         SoupSession *session = SOUP_SESSION (object);
305
306         switch (prop_id) {
307         case PROP_PROXY_URI:
308                 g_value_set_pointer (value, session->priv->proxy_uri ?
309                                      soup_uri_copy (session->priv->proxy_uri) :
310                                      NULL);
311                 break;
312         case PROP_MAX_CONNS:
313                 g_value_set_int (value, session->priv->max_conns);
314                 break;
315         case PROP_MAX_CONNS_PER_HOST:
316                 g_value_set_int (value, session->priv->max_conns_per_host);
317                 break;
318         case PROP_USE_NTLM:
319                 g_value_set_boolean (value, session->priv->use_ntlm);
320                 break;
321         case PROP_SSL_CA_FILE:
322                 g_value_set_string (value, session->priv->ssl_ca_file);
323                 break;
324         default:
325                 break;
326         }
327 }
328
329
330 /* Hosts */
331 static guint
332 host_uri_hash (gconstpointer key)
333 {
334         const SoupUri *uri = key;
335
336         return (uri->protocol << 16) + uri->port + g_str_hash (uri->host);
337 }
338
339 static gboolean
340 host_uri_equal (gconstpointer v1, gconstpointer v2)
341 {
342         const SoupUri *one = v1;
343         const SoupUri *two = v2;
344
345         if (one->protocol != two->protocol)
346                 return FALSE;
347         if (one->port != two->port)
348                 return FALSE;
349
350         return strcmp (one->host, two->host) == 0;
351 }
352
353 static SoupSessionHost *
354 soup_session_host_new (SoupSession *session, const SoupUri *source_uri)
355 {
356         SoupSessionHost *host;
357
358         host = g_new0 (SoupSessionHost, 1);
359         host->root_uri = soup_uri_copy_root (source_uri);
360
361         if (host->root_uri->protocol == SOUP_PROTOCOL_HTTPS &&
362             !session->priv->ssl_creds) {
363                 session->priv->ssl_creds =
364                         soup_ssl_get_client_credentials (session->priv->ssl_ca_file);
365         }
366
367         return host;
368 }
369
370 static SoupSessionHost *
371 get_host_for_message (SoupSession *session, SoupMessage *msg)
372 {
373         SoupSessionHost *host;
374         const SoupUri *source = soup_message_get_uri (msg);
375
376         host = g_hash_table_lookup (session->priv->hosts, source);
377         if (host)
378                 return host;
379
380         host = soup_session_host_new (session, source);
381
382         g_hash_table_insert (session->priv->hosts, host->root_uri, host);
383
384         return host;
385 }
386
387 static SoupSessionHost *
388 get_proxy_host (SoupSession *session)
389 {
390         if (session->priv->proxy_host)
391                 return session->priv->proxy_host;
392
393         session->priv->proxy_host =
394                 soup_session_host_new (session, session->priv->proxy_uri);
395
396         return session->priv->proxy_host;
397 }
398
399 static void
400 free_realm (gpointer path, gpointer scheme_realm, gpointer data)
401 {
402         g_free (path);
403         g_free (scheme_realm);
404 }
405
406 static void
407 free_auth (gpointer scheme_realm, gpointer auth, gpointer data)
408 {
409         g_free (scheme_realm);
410         g_object_unref (auth);
411 }
412
413 static void
414 free_host (SoupSessionHost *host, SoupSession *session)
415 {
416         while (host->connections) {
417                 SoupConnection *conn = host->connections->data;
418
419                 host->connections = g_slist_remove (host->connections, conn);
420                 soup_connection_disconnect (conn);
421         }
422
423         if (host->auth_realms) {
424                 g_hash_table_foreach (host->auth_realms, free_realm, NULL);
425                 g_hash_table_destroy (host->auth_realms);
426         }
427         if (host->auths) {
428                 g_hash_table_foreach (host->auths, free_auth, NULL);
429                 g_hash_table_destroy (host->auths);
430         }
431
432         soup_uri_free (host->root_uri);
433 }       
434
435 /* Authentication */
436
437 static SoupAuth *
438 lookup_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
439 {
440         SoupSessionHost *host;
441         char *path, *dir;
442         const char *realm, *const_path;
443
444         if (proxy) {
445                 host = get_proxy_host (session);
446                 const_path = "/";
447         } else {
448                 host = get_host_for_message (session, msg);
449                 const_path = soup_message_get_uri (msg)->path;
450         }
451         g_return_val_if_fail (host != NULL, NULL);
452
453         if (!host->auth_realms)
454                 return NULL;
455
456         path = g_strdup (const_path);
457         dir = path;
458         do {
459                 realm = g_hash_table_lookup (host->auth_realms, path);
460                 if (realm)
461                         break;
462
463                 dir = strrchr (path, '/');
464                 if (dir)
465                         *dir = '\0';
466         } while (dir);
467
468         g_free (path);
469         if (realm)
470                 return g_hash_table_lookup (host->auths, realm);
471         else
472                 return NULL;
473 }
474
475 static void
476 invalidate_auth (SoupSessionHost *host, SoupAuth *auth)
477 {
478         char *realm;
479         gpointer key, value;
480
481         realm = g_strdup_printf ("%s:%s",
482                                  soup_auth_get_scheme_name (auth),
483                                  soup_auth_get_realm (auth));
484
485         if (g_hash_table_lookup_extended (host->auths, realm, &key, &value) &&
486             auth == (SoupAuth *)value) {
487                 g_hash_table_remove (host->auths, realm);
488                 g_free (key);
489                 g_object_unref (auth);
490         }
491         g_free (realm);
492 }
493
494 static gboolean
495 authenticate_auth (SoupSession *session, SoupAuth *auth,
496                    SoupMessage *msg, gboolean prior_auth_failed,
497                    gboolean proxy)
498 {
499         const SoupUri *uri;
500         char *username = NULL, *password = NULL;
501
502         if (proxy)
503                 uri = session->priv->proxy_uri;
504         else
505                 uri = soup_message_get_uri (msg);
506
507         if (uri->passwd && !prior_auth_failed) {
508                 soup_auth_authenticate (auth, uri->user, uri->passwd);
509                 return TRUE;
510         }
511
512         g_signal_emit (session, signals[prior_auth_failed ? REAUTHENTICATE : AUTHENTICATE], 0,
513                        msg, soup_auth_get_scheme_name (auth),
514                        soup_auth_get_realm (auth),
515                        &username, &password);
516         if (username || password)
517                 soup_auth_authenticate (auth, username, password);
518         if (username)
519                 g_free (username);
520         if (password) {
521                 memset (password, 0, strlen (password));
522                 g_free (password);
523         }
524
525         return soup_auth_is_authenticated (auth);
526 }
527
528 static gboolean
529 update_auth_internal (SoupSession *session, SoupMessage *msg,
530                       const GSList *headers, gboolean proxy,
531                       gboolean got_unauthorized)
532 {
533         SoupSessionHost *host;
534         SoupAuth *new_auth, *prior_auth, *old_auth;
535         gpointer old_path, old_realm;
536         const SoupUri *msg_uri;
537         const char *path;
538         char *realm;
539         GSList *pspace, *p;
540         gboolean prior_auth_failed = FALSE;
541
542         if (proxy)
543                 host = get_proxy_host (session);
544         else
545                 host = get_host_for_message (session, msg);
546
547         g_return_val_if_fail (host != NULL, FALSE);
548
549         /* Try to construct a new auth from the headers; if we can't,
550          * there's no way we'll be able to authenticate.
551          */
552         msg_uri = soup_message_get_uri (msg);
553         new_auth = soup_auth_new_from_header_list (headers);
554         if (!new_auth)
555                 return FALSE;
556
557         /* See if this auth is the same auth we used last time */
558         prior_auth = lookup_auth (session, msg, proxy);
559         if (prior_auth &&
560             G_OBJECT_TYPE (prior_auth) == G_OBJECT_TYPE (new_auth) &&
561             !strcmp (soup_auth_get_realm (prior_auth),
562                      soup_auth_get_realm (new_auth))) {
563                 if (!got_unauthorized) {
564                         /* The user is just trying to preauthenticate
565                          * using information we already have, so
566                          * there's nothing more that needs to be done.
567                          */
568                         g_object_unref (new_auth);
569                         return TRUE;
570                 }
571
572                 /* The server didn't like the username/password we
573                  * provided before. Invalidate it and note this fact.
574                  */
575                 invalidate_auth (host, prior_auth);
576                 prior_auth_failed = TRUE;
577         }
578
579         if (!host->auth_realms) {
580                 host->auth_realms = g_hash_table_new (g_str_hash, g_str_equal);
581                 host->auths = g_hash_table_new (g_str_hash, g_str_equal);
582         }
583
584         /* Record where this auth realm is used */
585         realm = g_strdup_printf ("%s:%s",
586                                  soup_auth_get_scheme_name (new_auth),
587                                  soup_auth_get_realm (new_auth));
588
589         /* 
590          * RFC 2617 is somewhat unclear about the scope of protection
591          * spaces with regard to proxies.  The only mention of it is
592          * as an aside in section 3.2.1, where it is defining the fields
593          * of a Digest challenge and says that the protection space is
594          * always the entire proxy.  Is this the case for all authentication
595          * schemes or just Digest?  Who knows, but we're assuming all.
596          */
597         if (proxy)
598                 pspace = g_slist_prepend (NULL, g_strdup (""));
599         else
600                 pspace = soup_auth_get_protection_space (new_auth, msg_uri);
601
602         for (p = pspace; p; p = p->next) {
603                 path = p->data;
604                 if (g_hash_table_lookup_extended (host->auth_realms, path,
605                                                   &old_path, &old_realm)) {
606                         g_hash_table_remove (host->auth_realms, old_path);
607                         g_free (old_path);
608                         g_free (old_realm);
609                 }
610
611                 g_hash_table_insert (host->auth_realms,
612                                      g_strdup (path), g_strdup (realm));
613         }
614         soup_auth_free_protection_space (new_auth, pspace);
615
616         /* Now, make sure the auth is recorded. (If there's a
617          * pre-existing auth, we keep that rather than the new one,
618          * since the old one might already be authenticated.)
619          */
620         old_auth = g_hash_table_lookup (host->auths, realm);
621         if (old_auth) {
622                 g_free (realm);
623                 g_object_unref (new_auth);
624                 new_auth = old_auth;
625         } else 
626                 g_hash_table_insert (host->auths, realm, new_auth);
627
628         /* If we need to authenticate, try to do it. */
629         if (!soup_auth_is_authenticated (new_auth)) {
630                 return authenticate_auth (session, new_auth, msg,
631                                           prior_auth_failed, proxy);
632         }
633
634         /* Otherwise we're good. */
635         return TRUE;
636 }
637
638 static void
639 connection_authenticate (SoupConnection *conn, SoupMessage *msg,
640                          const char *auth_type, const char *auth_realm,
641                          char **username, char **password, gpointer session)
642 {
643         g_signal_emit (session, signals[AUTHENTICATE], 0,
644                        msg, auth_type, auth_realm, username, password);
645 }
646
647 static void
648 connection_reauthenticate (SoupConnection *conn, SoupMessage *msg,
649                            const char *auth_type, const char *auth_realm,
650                            char **username, char **password,
651                            gpointer user_data)
652 {
653         g_signal_emit (conn, signals[REAUTHENTICATE], 0,
654                        msg, auth_type, auth_realm, username, password);
655 }
656
657
658 static void
659 authorize_handler (SoupMessage *msg, gpointer user_data)
660 {
661         SoupSession *session = user_data;
662         const GSList *headers;
663         gboolean proxy;
664
665         if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
666                 headers = soup_message_get_header_list (msg->response_headers,
667                                                         "Proxy-Authenticate");
668                 proxy = TRUE;
669         } else {
670                 headers = soup_message_get_header_list (msg->response_headers,
671                                                         "WWW-Authenticate");
672                 proxy = FALSE;
673         }
674         if (!headers)
675                 return;
676
677         if (update_auth_internal (session, msg, headers, proxy, TRUE))
678                 soup_session_requeue_message (session, msg);
679 }
680
681 static void
682 redirect_handler (SoupMessage *msg, gpointer user_data)
683 {
684         SoupSession *session = user_data;
685         const char *new_loc;
686         SoupUri *new_uri;
687
688         new_loc = soup_message_get_header (msg->response_headers, "Location");
689         if (!new_loc)
690                 return;
691         new_uri = soup_uri_new (new_loc);
692         if (!new_uri)
693                 goto INVALID_REDIRECT;
694
695         soup_message_set_uri (msg, new_uri);
696         soup_uri_free (new_uri);
697
698         soup_session_requeue_message (session, msg);
699         return;
700
701  INVALID_REDIRECT:
702         soup_message_set_status_full (msg,
703                                       SOUP_STATUS_MALFORMED,
704                                       "Invalid Redirect URL");
705 }
706
707 static void
708 request_restarted (SoupMessage *req, gpointer session)
709 {
710         run_queue (session, FALSE);
711 }
712
713 static void
714 request_finished (SoupMessage *req, gpointer user_data)
715 {
716         req->status = SOUP_MESSAGE_STATUS_FINISHED;
717 }
718
719 static void
720 final_finished (SoupMessage *req, gpointer user_data)
721 {
722         SoupSession *session = user_data;
723
724         if (!SOUP_MESSAGE_IS_STARTING (req)) {
725                 soup_message_queue_remove_message (session->priv->queue, req);
726
727                 g_signal_handlers_disconnect_by_func (req, request_finished, session);
728                 g_signal_handlers_disconnect_by_func (req, final_finished, session);
729                 g_object_unref (req);
730         }
731
732         run_queue (session, FALSE);
733 }
734
735 static void
736 add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
737 {
738         const char *header = proxy ? "Proxy-Authorization" : "Authorization";
739         SoupAuth *auth;
740         char *token;
741
742         soup_message_remove_header (msg->request_headers, header);
743
744         auth = lookup_auth (session, msg, proxy);
745         if (!auth)
746                 return;
747         if (!soup_auth_is_authenticated (auth) &&
748             !authenticate_auth (session, auth, msg, FALSE, proxy))
749                 return;
750
751         token = soup_auth_get_authorization (auth, msg);
752         if (token) {
753                 soup_message_add_header (msg->request_headers, header, token);
754                 g_free (token);
755         }
756 }
757
758 static void
759 send_request (SoupSession *session, SoupMessage *req, SoupConnection *conn)
760 {
761         req->status = SOUP_MESSAGE_STATUS_RUNNING;
762
763         add_auth (session, req, FALSE);
764         if (session->priv->proxy_uri)
765                 add_auth (session, req, TRUE);
766         soup_connection_send_request (conn, req);
767 }
768
769 static void
770 find_oldest_connection (gpointer key, gpointer host, gpointer data)
771 {
772         SoupConnection *conn = key, **oldest = data;
773
774         /* Don't prune a connection that hasn't even been used yet. */
775         if (soup_connection_last_used (conn) == 0)
776                 return;
777
778         if (!*oldest || (soup_connection_last_used (conn) <
779                          soup_connection_last_used (*oldest)))
780                 *oldest = conn;
781 }
782
783 static gboolean
784 try_prune_connection (SoupSession *session)
785 {
786         SoupConnection *oldest = NULL;
787
788         g_hash_table_foreach (session->priv->conns, find_oldest_connection,
789                               &oldest);
790         if (oldest) {
791                 soup_connection_disconnect (oldest);
792                 g_object_unref (oldest);
793                 return TRUE;
794         } else
795                 return FALSE;
796 }
797
798 static void connection_closed (SoupConnection *conn, SoupSession *session);
799
800 static void
801 cleanup_connection (SoupSession *session, SoupConnection *conn)
802 {
803         SoupSessionHost *host =
804                 g_hash_table_lookup (session->priv->conns, conn);
805
806         g_return_if_fail (host != NULL);
807
808         g_hash_table_remove (session->priv->conns, conn);
809         g_signal_handlers_disconnect_by_func (conn, connection_closed, session);
810         session->priv->num_conns--;
811
812         host->connections = g_slist_remove (host->connections, conn);
813         host->num_conns--;
814 }
815
816 static void
817 connection_closed (SoupConnection *conn, SoupSession *session)
818 {
819         cleanup_connection (session, conn);
820
821         /* Run the queue in case anyone was waiting for a connection
822          * to be closed.
823          */
824         run_queue (session, FALSE);
825 }
826
827 static void
828 got_connection (SoupConnection *conn, guint status, gpointer user_data)
829 {
830         SoupSession *session = user_data;
831         SoupSessionHost *host = g_hash_table_lookup (session->priv->conns, conn);
832
833         g_return_if_fail (host != NULL);
834
835         if (status == SOUP_STATUS_OK) {
836                 host->connections = g_slist_prepend (host->connections, conn);
837                 run_queue (session, FALSE);
838                 return;
839         }
840
841         /* We failed */
842         cleanup_connection (session, conn);
843         g_object_unref (conn);
844
845         if (host->connections) {
846                 /* Something went wrong this time, but we have at
847                  * least one open connection to this host. So just
848                  * leave the message in the queue so it can use that
849                  * connection once it's free.
850                  */
851                 return;
852         }
853
854         /* Flush any queued messages for this host */
855         host->error = status;
856         run_queue (session, FALSE);
857
858         if (status != SOUP_STATUS_CANT_RESOLVE &&
859             status != SOUP_STATUS_CANT_RESOLVE_PROXY) {
860                 /* If the error was "can't resolve", then it's not likely
861                  * to improve. But if it was something else, it may have
862                  * been transient, so we clear the error so the user can
863                  * try again later.
864                  */
865                 host->error = 0;
866         }
867 }
868
869 static gboolean
870 run_queue (SoupSession *session, gboolean try_pruning)
871 {
872         SoupMessageQueueIter iter;
873         SoupMessage *msg;
874         SoupConnection *conn;
875         SoupSessionHost *host;
876         gboolean skipped_any = FALSE, started_any = FALSE;
877         GSList *conns;
878
879         /* FIXME: prefer CONNECTING messages */
880
881  try_again:
882         for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
883
884                 if (!SOUP_MESSAGE_IS_STARTING (msg))
885                         continue;
886
887                 host = get_host_for_message (session, msg);
888
889                 /* If the hostname is known to be bad, fail right away */
890                 if (host->error) {
891                         soup_message_set_status (msg, host->error);
892                         msg->status = SOUP_MESSAGE_STATUS_FINISHED;
893                         soup_message_finished (msg);
894                 }
895
896                 /* If there is an idle connection, use it */
897                 for (conns = host->connections; conns; conns = conns->next) {
898                         if (!soup_connection_is_in_use (conns->data))
899                                 break;
900                 }
901                 if (conns) {
902                         send_request (session, msg, conns->data);
903                         started_any = TRUE;
904                         continue;
905                 }
906
907                 if (msg->status == SOUP_MESSAGE_STATUS_CONNECTING) {
908                         /* We already started a connection for this
909                          * message, so don't start another one.
910                          */
911                         continue;
912                 }
913
914                 /* If we have the max number of per-host connections
915                  * or total connections open, we'll have to wait.
916                  */
917                 if (host->num_conns >= session->priv->max_conns_per_host)
918                         continue;
919                 else if (session->priv->num_conns >= session->priv->max_conns) {
920                         /* In this case, closing an idle connection
921                          * somewhere else would let us open one here.
922                          */
923                         skipped_any = TRUE;
924                         continue;
925                 }
926
927                 /* Otherwise, open a new connection */
928                 conn = g_object_new (
929                         (session->priv->use_ntlm ?
930                          SOUP_TYPE_CONNECTION_NTLM : SOUP_TYPE_CONNECTION),
931                         SOUP_CONNECTION_ORIGIN_URI, host->root_uri,
932                         SOUP_CONNECTION_PROXY_URI, session->priv->proxy_uri,
933                         SOUP_CONNECTION_SSL_CREDENTIALS, session->priv->ssl_creds,
934                         NULL);
935                 g_signal_connect (conn, "authenticate",
936                                   G_CALLBACK (connection_authenticate),
937                                   session);
938                 g_signal_connect (conn, "reauthenticate",
939                                   G_CALLBACK (connection_reauthenticate),
940                                   session);
941
942                 soup_connection_connect_async (conn, got_connection, session);
943                 g_signal_connect (conn, "disconnected",
944                                   G_CALLBACK (connection_closed), session);
945                 g_hash_table_insert (session->priv->conns, conn, host);
946                 session->priv->num_conns++;
947
948                 /* Increment the host's connection count, but don't add
949                  * this connection to the list yet, since it's not ready.
950                  */
951                 host->num_conns++;
952
953                 /* Mark the request as connecting, so we don't try to
954                  * open another new connection for it next time around.
955                  */
956                 msg->status = SOUP_MESSAGE_STATUS_CONNECTING;
957
958                 started_any = TRUE;
959         }
960
961         if (try_pruning && skipped_any && !started_any) {
962                 /* We didn't manage to start any message, but there is
963                  * at least one message in the queue that could be
964                  * sent if we pruned an idle connection from some
965                  * other server.
966                  */
967                 if (try_prune_connection (session)) {
968                         try_pruning = FALSE;
969                         goto try_again;
970                 }
971         }
972
973         return started_any;
974 }
975
976 static void
977 queue_message (SoupSession *session, SoupMessage *req, gboolean requeue)
978 {
979         req->status = SOUP_MESSAGE_STATUS_QUEUED;
980         if (!requeue) {
981                 soup_message_queue_append (session->priv->queue, req);
982                 run_queue (session, TRUE);
983         }
984 }
985
986 /**
987  * soup_session_queue_message:
988  * @session: a #SoupSession
989  * @req: the message to queue
990  * @callback: a #SoupMessageCallbackFn which will be called after the
991  * message completes or when an unrecoverable error occurs.
992  * @user_data: a pointer passed to @callback.
993  * 
994  * Queues the message @req for sending. All messages are processed
995  * while the glib main loop runs. If @req has been processed before,
996  * any resources related to the time it was last sent are freed.
997  *
998  * Upon message completion, the callback specified in @callback will
999  * be invoked. If after returning from this callback the message has
1000  * not been requeued, @req will be unreffed.
1001  */
1002 void
1003 soup_session_queue_message (SoupSession *session, SoupMessage *req,
1004                             SoupMessageCallbackFn callback, gpointer user_data)
1005 {
1006         g_return_if_fail (SOUP_IS_SESSION (session));
1007         g_return_if_fail (SOUP_IS_MESSAGE (req));
1008
1009         g_signal_connect (req, "restarted",
1010                           G_CALLBACK (request_restarted), session);
1011
1012         g_signal_connect (req, "finished",
1013                           G_CALLBACK (request_finished), session);
1014
1015         if (callback) {
1016                 g_signal_connect (req, "finished",
1017                                   G_CALLBACK (callback), user_data);
1018         }
1019
1020         g_signal_connect_after (req, "finished",
1021                                 G_CALLBACK (final_finished), session);
1022
1023         soup_message_add_status_code_handler  (req, SOUP_STATUS_UNAUTHORIZED,
1024                                                SOUP_HANDLER_POST_BODY,
1025                                                authorize_handler, session);
1026         soup_message_add_status_code_handler  (req,
1027                                                SOUP_STATUS_PROXY_UNAUTHORIZED,
1028                                                SOUP_HANDLER_POST_BODY,
1029                                                authorize_handler, session);
1030
1031         if (!(soup_message_get_flags (req) & SOUP_MESSAGE_NO_REDIRECT)) {
1032                 soup_message_add_status_class_handler (
1033                         req, SOUP_STATUS_CLASS_REDIRECT,
1034                         SOUP_HANDLER_POST_BODY,
1035                         redirect_handler, session);
1036         }
1037
1038         queue_message (session, req, FALSE);
1039 }
1040
1041 /**
1042  * soup_session_requeue_message:
1043  * @session: a #SoupSession
1044  * @req: the message to requeue
1045  *
1046  * This causes @req to be placed back on the queue to be attempted
1047  * again.
1048  **/
1049 void
1050 soup_session_requeue_message (SoupSession *session, SoupMessage *req)
1051 {
1052         g_return_if_fail (SOUP_IS_SESSION (session));
1053         g_return_if_fail (SOUP_IS_MESSAGE (req));
1054
1055         queue_message (session, req, TRUE);
1056 }
1057
1058
1059 /**
1060  * soup_session_send_message:
1061  * @session: a #SoupSession
1062  * @req: the message to send
1063  * 
1064  * Synchronously send @req. This call will not return until the
1065  * transfer is finished successfully or there is an unrecoverable
1066  * error.
1067  *
1068  * @req is not freed upon return.
1069  *
1070  * Return value: the HTTP status code of the response
1071  */
1072 guint
1073 soup_session_send_message (SoupSession *session, SoupMessage *req)
1074 {
1075         g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
1076         g_return_val_if_fail (SOUP_IS_MESSAGE (req), SOUP_STATUS_MALFORMED);
1077
1078         /* Balance out the unref that final_finished will do */
1079         g_object_ref (req);
1080
1081         soup_session_queue_message (session, req, NULL, NULL);
1082
1083         while (req->status != SOUP_MESSAGE_STATUS_FINISHED &&
1084                !SOUP_STATUS_IS_TRANSPORT_ERROR (req->status_code))
1085                 g_main_iteration (TRUE);
1086
1087         return req->status_code;
1088 }
1089
1090 /**
1091  * soup_session_abort:
1092  * @session: the session
1093  *
1094  * Cancels all pending requests in @session.
1095  **/
1096 void
1097 soup_session_abort (SoupSession *session)
1098 {
1099         SoupMessageQueueIter iter;
1100         SoupMessage *msg;
1101
1102         for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
1103                 soup_message_queue_remove (session->priv->queue, &iter);
1104                 soup_message_cancel (msg);
1105         }
1106 }