require glib-2.0 >= 2.4.0
[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-filter.h"
22 #include "soup-message-queue.h"
23 #include "soup-ssl.h"
24 #include "soup-uri.h"
25
26 typedef struct {
27         SoupUri    *root_uri;
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 typedef struct {
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         GSList *filters;
45
46         GHashTable *hosts; /* SoupUri -> SoupSessionHost */
47         GHashTable *conns; /* SoupConnection -> SoupSessionHost */
48         guint num_conns;
49
50         SoupSessionHost *proxy_host;
51
52         /* Must hold the host_lock before potentially creating a
53          * new SoupSessionHost, or adding/removing a connection.
54          * Must not emit signals or destroy objects while holding it.
55          */
56         GMutex *host_lock;
57 } SoupSessionPrivate;
58 #define SOUP_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION, SoupSessionPrivate))
59
60 static guint    host_uri_hash  (gconstpointer key);
61 static gboolean host_uri_equal (gconstpointer v1, gconstpointer v2);
62 static void     free_host      (SoupSessionHost *host);
63
64 static void setup_message   (SoupMessageFilter *filter, SoupMessage *msg);
65
66 static void queue_message   (SoupSession *session, SoupMessage *msg,
67                              SoupMessageCallbackFn callback,
68                              gpointer user_data);
69 static void requeue_message (SoupSession *session, SoupMessage *msg);
70 static void cancel_message  (SoupSession *session, SoupMessage *msg);
71
72 #define SOUP_SESSION_MAX_CONNS_DEFAULT 10
73 #define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 4
74
75 static void filter_iface_init (SoupMessageFilterClass *filter_class);
76
77 G_DEFINE_TYPE_EXTENDED (SoupSession, soup_session, G_TYPE_OBJECT, 0,
78                         G_IMPLEMENT_INTERFACE (SOUP_TYPE_MESSAGE_FILTER,
79                                                filter_iface_init))
80
81 enum {
82         AUTHENTICATE,
83         REAUTHENTICATE,
84         LAST_SIGNAL
85 };
86
87 static guint signals[LAST_SIGNAL] = { 0 };
88
89 enum {
90         PROP_0,
91
92         PROP_PROXY_URI,
93         PROP_MAX_CONNS,
94         PROP_MAX_CONNS_PER_HOST,
95         PROP_USE_NTLM,
96         PROP_SSL_CA_FILE,
97
98         LAST_PROP
99 };
100
101 static void set_property (GObject *object, guint prop_id,
102                           const GValue *value, GParamSpec *pspec);
103 static void get_property (GObject *object, guint prop_id,
104                           GValue *value, GParamSpec *pspec);
105
106 static void
107 soup_session_init (SoupSession *session)
108 {
109         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
110
111         session->queue = soup_message_queue_new ();
112
113         priv->host_lock = g_mutex_new ();
114         priv->hosts = g_hash_table_new (host_uri_hash, host_uri_equal);
115         priv->conns = g_hash_table_new (NULL, NULL);
116
117         priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT;
118         priv->max_conns_per_host = SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT;
119 }
120
121 static gboolean
122 foreach_free_host (gpointer key, gpointer host, gpointer data)
123 {
124         free_host (host);
125         return TRUE;
126 }
127
128 static void
129 cleanup_hosts (SoupSessionPrivate *priv)
130 {
131         g_hash_table_foreach_remove (priv->hosts, foreach_free_host, NULL);
132 }
133
134 static void
135 dispose (GObject *object)
136 {
137         SoupSession *session = SOUP_SESSION (object);
138         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
139         GSList *f;
140
141         soup_session_abort (session);
142         cleanup_hosts (priv);
143
144         if (priv->filters) {
145                 for (f = priv->filters; f; f = f->next)
146                         g_object_unref (f->data);
147                 g_slist_free (priv->filters);
148                 priv->filters = NULL;
149         }
150
151         G_OBJECT_CLASS (soup_session_parent_class)->dispose (object);
152 }
153
154 static void
155 finalize (GObject *object)
156 {
157         SoupSession *session = SOUP_SESSION (object);
158         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
159
160         soup_message_queue_destroy (session->queue);
161
162         g_mutex_free (priv->host_lock);
163         g_hash_table_destroy (priv->hosts);
164         g_hash_table_destroy (priv->conns);
165
166         G_OBJECT_CLASS (soup_session_parent_class)->finalize (object);
167 }
168
169 static void
170 soup_session_class_init (SoupSessionClass *session_class)
171 {
172         GObjectClass *object_class = G_OBJECT_CLASS (session_class);
173
174         g_type_class_add_private (session_class, sizeof (SoupSessionPrivate));
175
176         /* virtual method definition */
177         session_class->queue_message = queue_message;
178         session_class->requeue_message = requeue_message;
179         session_class->cancel_message = cancel_message;
180
181         /* virtual method override */
182         object_class->dispose = dispose;
183         object_class->finalize = finalize;
184         object_class->set_property = set_property;
185         object_class->get_property = get_property;
186
187         /* signals */
188         signals[AUTHENTICATE] =
189                 g_signal_new ("authenticate",
190                               G_OBJECT_CLASS_TYPE (object_class),
191                               G_SIGNAL_RUN_FIRST,
192                               G_STRUCT_OFFSET (SoupSessionClass, authenticate),
193                               NULL, NULL,
194                               soup_marshal_NONE__OBJECT_STRING_STRING_POINTER_POINTER,
195                               G_TYPE_NONE, 5,
196                               SOUP_TYPE_MESSAGE,
197                               G_TYPE_STRING,
198                               G_TYPE_STRING,
199                               G_TYPE_POINTER,
200                               G_TYPE_POINTER);
201         signals[REAUTHENTICATE] =
202                 g_signal_new ("reauthenticate",
203                               G_OBJECT_CLASS_TYPE (object_class),
204                               G_SIGNAL_RUN_FIRST,
205                               G_STRUCT_OFFSET (SoupSessionClass, reauthenticate),
206                               NULL, NULL,
207                               soup_marshal_NONE__OBJECT_STRING_STRING_POINTER_POINTER,
208                               G_TYPE_NONE, 5,
209                               SOUP_TYPE_MESSAGE,
210                               G_TYPE_STRING,
211                               G_TYPE_STRING,
212                               G_TYPE_POINTER,
213                               G_TYPE_POINTER);
214
215         /* properties */
216         g_object_class_install_property (
217                 object_class, PROP_PROXY_URI,
218                 g_param_spec_pointer (SOUP_SESSION_PROXY_URI,
219                                       "Proxy URI",
220                                       "The HTTP Proxy to use for this session",
221                                       G_PARAM_READWRITE));
222         g_object_class_install_property (
223                 object_class, PROP_MAX_CONNS,
224                 g_param_spec_int (SOUP_SESSION_MAX_CONNS,
225                                   "Max Connection Count",
226                                   "The maximum number of connections that the session can open at once",
227                                   1,
228                                   G_MAXINT,
229                                   10,
230                                   G_PARAM_READWRITE));
231         g_object_class_install_property (
232                 object_class, PROP_MAX_CONNS_PER_HOST,
233                 g_param_spec_int (SOUP_SESSION_MAX_CONNS_PER_HOST,
234                                   "Max Per-Host Connection Count",
235                                   "The maximum number of connections that the session can open at once to a given host",
236                                   1,
237                                   G_MAXINT,
238                                   4,
239                                   G_PARAM_READWRITE));
240         g_object_class_install_property (
241                 object_class, PROP_USE_NTLM,
242                 g_param_spec_boolean (SOUP_SESSION_USE_NTLM,
243                                       "Use NTLM",
244                                       "Whether or not to use NTLM authentication",
245                                       FALSE,
246                                       G_PARAM_READWRITE));
247         g_object_class_install_property (
248                 object_class, PROP_SSL_CA_FILE,
249                 g_param_spec_string (SOUP_SESSION_SSL_CA_FILE,
250                                      "SSL CA file",
251                                      "File containing SSL CA certificates",
252                                      NULL,
253                                      G_PARAM_READWRITE));
254 }
255
256 static void
257 filter_iface_init (SoupMessageFilterClass *filter_class)
258 {
259         /* interface implementation */
260         filter_class->setup_message = setup_message;
261 }
262
263
264 static gboolean
265 safe_uri_equal (const SoupUri *a, const SoupUri *b)
266 {
267         if (!a && !b)
268                 return TRUE;
269
270         if ((a && !b) || (b && !a))
271                 return FALSE;
272
273         return soup_uri_equal (a, b);
274 }
275
276 static gboolean
277 safe_str_equal (const char *a, const char *b)
278 {
279         if (!a && !b)
280                 return TRUE;
281
282         if ((a && !b) || (b && !a))
283                 return FALSE;
284
285         return strcmp (a, b) == 0;
286 }
287
288 static void
289 set_property (GObject *object, guint prop_id,
290               const GValue *value, GParamSpec *pspec)
291 {
292         SoupSession *session = SOUP_SESSION (object);
293         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
294         gpointer pval;
295         gboolean need_abort = FALSE;
296         gboolean ca_file_changed = FALSE;
297         const char *new_ca_file;
298
299         switch (prop_id) {
300         case PROP_PROXY_URI:
301                 pval = g_value_get_pointer (value);
302
303                 if (!safe_uri_equal (priv->proxy_uri, pval))
304                         need_abort = TRUE;
305
306                 if (priv->proxy_uri)
307                         soup_uri_free (priv->proxy_uri);
308
309                 priv->proxy_uri = pval ? soup_uri_copy (pval) : NULL;
310
311                 if (need_abort) {
312                         soup_session_abort (session);
313                         cleanup_hosts (priv);
314                 }
315
316                 break;
317         case PROP_MAX_CONNS:
318                 priv->max_conns = g_value_get_int (value);
319                 break;
320         case PROP_MAX_CONNS_PER_HOST:
321                 priv->max_conns_per_host = g_value_get_int (value);
322                 break;
323         case PROP_USE_NTLM:
324                 priv->use_ntlm = g_value_get_boolean (value);
325                 break;
326         case PROP_SSL_CA_FILE:
327                 new_ca_file = g_value_get_string (value);
328
329                 if (!safe_str_equal (priv->ssl_ca_file, new_ca_file))
330                         ca_file_changed = TRUE;
331
332                 g_free (priv->ssl_ca_file);
333                 priv->ssl_ca_file = g_strdup (new_ca_file);
334
335                 if (ca_file_changed) {
336                         if (priv->ssl_creds) {
337                                 soup_ssl_free_client_credentials (priv->ssl_creds);
338                                 priv->ssl_creds = NULL;
339                         }
340
341                         cleanup_hosts (priv);
342                 }
343
344                 break;
345         default:
346                 break;
347         }
348 }
349
350 static void
351 get_property (GObject *object, guint prop_id,
352               GValue *value, GParamSpec *pspec)
353 {
354         SoupSession *session = SOUP_SESSION (object);
355         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
356
357         switch (prop_id) {
358         case PROP_PROXY_URI:
359                 g_value_set_pointer (value, priv->proxy_uri ?
360                                      soup_uri_copy (priv->proxy_uri) :
361                                      NULL);
362                 break;
363         case PROP_MAX_CONNS:
364                 g_value_set_int (value, priv->max_conns);
365                 break;
366         case PROP_MAX_CONNS_PER_HOST:
367                 g_value_set_int (value, priv->max_conns_per_host);
368                 break;
369         case PROP_USE_NTLM:
370                 g_value_set_boolean (value, priv->use_ntlm);
371                 break;
372         case PROP_SSL_CA_FILE:
373                 g_value_set_string (value, priv->ssl_ca_file);
374                 break;
375         default:
376                 break;
377         }
378 }
379
380
381 /**
382  * soup_session_add_filter:
383  * @session: a #SoupSession
384  * @filter: an object implementing the #SoupMessageFilter interface
385  *
386  * Adds @filter to @session's list of message filters to be applied to
387  * all messages.
388  **/
389 void
390 soup_session_add_filter (SoupSession *session, SoupMessageFilter *filter)
391 {
392         SoupSessionPrivate *priv;
393
394         g_return_if_fail (SOUP_IS_SESSION (session));
395         g_return_if_fail (SOUP_IS_MESSAGE_FILTER (filter));
396         priv = SOUP_SESSION_GET_PRIVATE (session);
397
398         g_object_ref (filter);
399         priv->filters = g_slist_prepend (priv->filters, filter);
400 }
401
402 /**
403  * soup_session_remove_filter:
404  * @session: a #SoupSession
405  * @filter: an object implementing the #SoupMessageFilter interface
406  *
407  * Removes @filter from @session's list of message filters
408  **/
409 void
410 soup_session_remove_filter (SoupSession *session, SoupMessageFilter *filter)
411 {
412         SoupSessionPrivate *priv;
413
414         g_return_if_fail (SOUP_IS_SESSION (session));
415         g_return_if_fail (SOUP_IS_MESSAGE_FILTER (filter));
416         priv = SOUP_SESSION_GET_PRIVATE (session);
417
418         priv->filters = g_slist_remove (priv->filters, filter);
419         g_object_unref (filter);
420 }
421
422
423 /* Hosts */
424 static guint
425 host_uri_hash (gconstpointer key)
426 {
427         const SoupUri *uri = key;
428
429         return (uri->protocol << 16) + uri->port + g_str_hash (uri->host);
430 }
431
432 static gboolean
433 host_uri_equal (gconstpointer v1, gconstpointer v2)
434 {
435         const SoupUri *one = v1;
436         const SoupUri *two = v2;
437
438         if (one->protocol != two->protocol)
439                 return FALSE;
440         if (one->port != two->port)
441                 return FALSE;
442
443         return strcmp (one->host, two->host) == 0;
444 }
445
446 static SoupSessionHost *
447 soup_session_host_new (SoupSession *session, const SoupUri *source_uri)
448 {
449         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
450         SoupSessionHost *host;
451
452         host = g_new0 (SoupSessionHost, 1);
453         host->root_uri = soup_uri_copy_root (source_uri);
454
455         if (host->root_uri->protocol == SOUP_PROTOCOL_HTTPS &&
456             !priv->ssl_creds) {
457                 priv->ssl_creds =
458                         soup_ssl_get_client_credentials (priv->ssl_ca_file);
459         }
460
461         return host;
462 }
463
464 /* Note: get_host_for_message doesn't lock the host_lock. The caller
465  * must do it itself if there's a chance the host doesn't already
466  * exist.
467  */
468 static SoupSessionHost *
469 get_host_for_message (SoupSession *session, SoupMessage *msg)
470 {
471         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
472         SoupSessionHost *host;
473         const SoupUri *source = soup_message_get_uri (msg);
474
475         host = g_hash_table_lookup (priv->hosts, source);
476         if (host)
477                 return host;
478
479         host = soup_session_host_new (session, source);
480         g_hash_table_insert (priv->hosts, host->root_uri, host);
481
482         return host;
483 }
484
485 /* Note: get_proxy_host doesn't lock the host_lock. The caller must do
486  * it itself if there's a chance the host doesn't already exist.
487  */
488 static SoupSessionHost *
489 get_proxy_host (SoupSession *session)
490 {
491         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
492
493         if (priv->proxy_host || !priv->proxy_uri)
494                 return priv->proxy_host;
495
496         priv->proxy_host =
497                 soup_session_host_new (session, priv->proxy_uri);
498         return priv->proxy_host;
499 }
500
501 static void
502 free_realm (gpointer path, gpointer scheme_realm, gpointer data)
503 {
504         g_free (path);
505         g_free (scheme_realm);
506 }
507
508 static void
509 free_auth (gpointer scheme_realm, gpointer auth, gpointer data)
510 {
511         g_free (scheme_realm);
512         g_object_unref (auth);
513 }
514
515 static void
516 free_host (SoupSessionHost *host)
517 {
518         while (host->connections) {
519                 SoupConnection *conn = host->connections->data;
520
521                 host->connections = g_slist_remove (host->connections, conn);
522                 soup_connection_disconnect (conn);
523         }
524
525         if (host->auth_realms) {
526                 g_hash_table_foreach (host->auth_realms, free_realm, NULL);
527                 g_hash_table_destroy (host->auth_realms);
528         }
529         if (host->auths) {
530                 g_hash_table_foreach (host->auths, free_auth, NULL);
531                 g_hash_table_destroy (host->auths);
532         }
533
534         soup_uri_free (host->root_uri);
535         g_free (host);
536 }       
537
538 /* Authentication */
539
540 static SoupAuth *
541 lookup_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
542 {
543         SoupSessionHost *host;
544         char *path, *dir;
545         const char *realm, *const_path;
546
547         if (proxy) {
548                 host = get_proxy_host (session);
549                 const_path = "/";
550         } else {
551                 host = get_host_for_message (session, msg);
552                 const_path = soup_message_get_uri (msg)->path;
553
554                 if (!const_path)
555                         const_path = "/";
556         }
557         g_return_val_if_fail (host != NULL, NULL);
558
559         if (!host->auth_realms)
560                 return NULL;
561
562         path = g_strdup (const_path);
563         dir = path;
564         do {
565                 realm = g_hash_table_lookup (host->auth_realms, path);
566                 if (realm)
567                         break;
568
569                 dir = strrchr (path, '/');
570                 if (dir)
571                         *dir = '\0';
572         } while (dir);
573
574         g_free (path);
575         if (realm)
576                 return g_hash_table_lookup (host->auths, realm);
577         else
578                 return NULL;
579 }
580
581 static void
582 invalidate_auth (SoupSessionHost *host, SoupAuth *auth)
583 {
584         char *realm;
585         gpointer key, value;
586
587         realm = g_strdup_printf ("%s:%s",
588                                  soup_auth_get_scheme_name (auth),
589                                  soup_auth_get_realm (auth));
590
591         if (g_hash_table_lookup_extended (host->auths, realm, &key, &value) &&
592             auth == (SoupAuth *)value) {
593                 g_hash_table_remove (host->auths, realm);
594                 g_free (key);
595                 g_object_unref (auth);
596         }
597         g_free (realm);
598 }
599
600 static gboolean
601 authenticate_auth (SoupSession *session, SoupAuth *auth,
602                    SoupMessage *msg, gboolean prior_auth_failed,
603                    gboolean proxy)
604 {
605         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
606         const SoupUri *uri;
607         char *username = NULL, *password = NULL;
608
609         if (proxy)
610                 uri = priv->proxy_uri;
611         else
612                 uri = soup_message_get_uri (msg);
613
614         if (uri->passwd && !prior_auth_failed) {
615                 soup_auth_authenticate (auth, uri->user, uri->passwd);
616                 return TRUE;
617         }
618
619         g_signal_emit (session, signals[prior_auth_failed ? REAUTHENTICATE : AUTHENTICATE], 0,
620                        msg, soup_auth_get_scheme_name (auth),
621                        soup_auth_get_realm (auth),
622                        &username, &password);
623         if (username || password)
624                 soup_auth_authenticate (auth, username, password);
625         if (username)
626                 g_free (username);
627         if (password) {
628                 memset (password, 0, strlen (password));
629                 g_free (password);
630         }
631
632         return soup_auth_is_authenticated (auth);
633 }
634
635 static gboolean
636 update_auth_internal (SoupSession *session, SoupMessage *msg,
637                       const GSList *headers, gboolean proxy,
638                       gboolean got_unauthorized)
639 {
640         SoupSessionHost *host;
641         SoupAuth *new_auth, *prior_auth, *old_auth;
642         gpointer old_path, old_realm;
643         const SoupUri *msg_uri;
644         const char *path;
645         char *realm;
646         GSList *pspace, *p;
647         gboolean prior_auth_failed = FALSE;
648
649         if (proxy)
650                 host = get_proxy_host (session);
651         else
652                 host = get_host_for_message (session, msg);
653
654         g_return_val_if_fail (host != NULL, FALSE);
655
656         /* Try to construct a new auth from the headers; if we can't,
657          * there's no way we'll be able to authenticate.
658          */
659         msg_uri = soup_message_get_uri (msg);
660         new_auth = soup_auth_new_from_header_list (headers);
661         if (!new_auth)
662                 return FALSE;
663
664         /* See if this auth is the same auth we used last time */
665         prior_auth = lookup_auth (session, msg, proxy);
666         if (prior_auth &&
667             G_OBJECT_TYPE (prior_auth) == G_OBJECT_TYPE (new_auth) &&
668             !strcmp (soup_auth_get_realm (prior_auth),
669                      soup_auth_get_realm (new_auth))) {
670                 if (!got_unauthorized) {
671                         /* The user is just trying to preauthenticate
672                          * using information we already have, so
673                          * there's nothing more that needs to be done.
674                          */
675                         g_object_unref (new_auth);
676                         return TRUE;
677                 }
678
679                 /* The server didn't like the username/password we
680                  * provided before. Invalidate it and note this fact.
681                  */
682                 invalidate_auth (host, prior_auth);
683                 prior_auth_failed = TRUE;
684         }
685
686         if (!host->auth_realms) {
687                 host->auth_realms = g_hash_table_new (g_str_hash, g_str_equal);
688                 host->auths = g_hash_table_new (g_str_hash, g_str_equal);
689         }
690
691         /* Record where this auth realm is used */
692         realm = g_strdup_printf ("%s:%s",
693                                  soup_auth_get_scheme_name (new_auth),
694                                  soup_auth_get_realm (new_auth));
695
696         /* 
697          * RFC 2617 is somewhat unclear about the scope of protection
698          * spaces with regard to proxies.  The only mention of it is
699          * as an aside in section 3.2.1, where it is defining the fields
700          * of a Digest challenge and says that the protection space is
701          * always the entire proxy.  Is this the case for all authentication
702          * schemes or just Digest?  Who knows, but we're assuming all.
703          */
704         if (proxy)
705                 pspace = g_slist_prepend (NULL, g_strdup (""));
706         else
707                 pspace = soup_auth_get_protection_space (new_auth, msg_uri);
708
709         for (p = pspace; p; p = p->next) {
710                 path = p->data;
711                 if (g_hash_table_lookup_extended (host->auth_realms, path,
712                                                   &old_path, &old_realm)) {
713                         g_hash_table_remove (host->auth_realms, old_path);
714                         g_free (old_path);
715                         g_free (old_realm);
716                 }
717
718                 g_hash_table_insert (host->auth_realms,
719                                      g_strdup (path), g_strdup (realm));
720         }
721         soup_auth_free_protection_space (new_auth, pspace);
722
723         /* Now, make sure the auth is recorded. (If there's a
724          * pre-existing auth, we keep that rather than the new one,
725          * since the old one might already be authenticated.)
726          */
727         old_auth = g_hash_table_lookup (host->auths, realm);
728         if (old_auth) {
729                 g_free (realm);
730                 g_object_unref (new_auth);
731                 new_auth = old_auth;
732         } else 
733                 g_hash_table_insert (host->auths, realm, new_auth);
734
735         /* If we need to authenticate, try to do it. */
736         if (!soup_auth_is_authenticated (new_auth)) {
737                 return authenticate_auth (session, new_auth,
738                                           msg, prior_auth_failed, proxy);
739         }
740
741         /* Otherwise we're good. */
742         return TRUE;
743 }
744
745 static void
746 connection_authenticate (SoupConnection *conn, SoupMessage *msg,
747                          const char *auth_type, const char *auth_realm,
748                          char **username, char **password, gpointer session)
749 {
750         g_signal_emit (session, signals[AUTHENTICATE], 0,
751                        msg, auth_type, auth_realm, username, password);
752 }
753
754 static void
755 connection_reauthenticate (SoupConnection *conn, SoupMessage *msg,
756                            const char *auth_type, const char *auth_realm,
757                            char **username, char **password,
758                            gpointer user_data)
759 {
760         g_signal_emit (conn, signals[REAUTHENTICATE], 0,
761                        msg, auth_type, auth_realm, username, password);
762 }
763
764
765 static void
766 authorize_handler (SoupMessage *msg, gpointer user_data)
767 {
768         SoupSession *session = user_data;
769         const GSList *headers;
770         gboolean proxy;
771
772         if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
773                 headers = soup_message_get_header_list (msg->response_headers,
774                                                         "Proxy-Authenticate");
775                 proxy = TRUE;
776         } else {
777                 headers = soup_message_get_header_list (msg->response_headers,
778                                                         "WWW-Authenticate");
779                 proxy = FALSE;
780         }
781         if (!headers)
782                 return;
783
784         if (update_auth_internal (session, msg, headers, proxy, TRUE))
785                 soup_session_requeue_message (session, msg);
786 }
787
788 static void
789 redirect_handler (SoupMessage *msg, gpointer user_data)
790 {
791         SoupSession *session = user_data;
792         const char *new_loc;
793         SoupUri *new_uri;
794
795         new_loc = soup_message_get_header (msg->response_headers, "Location");
796         if (!new_loc)
797                 return;
798         new_uri = soup_uri_new (new_loc);
799         if (!new_uri) {
800                 soup_message_set_status_full (msg,
801                                               SOUP_STATUS_MALFORMED,
802                                               "Invalid Redirect URL");
803                 return;
804         }
805
806         soup_message_set_uri (msg, new_uri);
807         soup_uri_free (new_uri);
808
809         soup_session_requeue_message (session, msg);
810 }
811
812 static void
813 add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
814 {
815         const char *header = proxy ? "Proxy-Authorization" : "Authorization";
816         SoupAuth *auth;
817         char *token;
818
819         auth = lookup_auth (session, msg, proxy);
820         if (!auth)
821                 return;
822         if (!soup_auth_is_authenticated (auth) &&
823             !authenticate_auth (session, auth, msg, FALSE, proxy))
824                 return;
825
826         token = soup_auth_get_authorization (auth, msg);
827         if (token) {
828                 soup_message_remove_header (msg->request_headers, header);
829                 soup_message_add_header (msg->request_headers, header, token);
830                 g_free (token);
831         }
832 }
833
834 static void
835 setup_message (SoupMessageFilter *filter, SoupMessage *msg)
836 {
837         SoupSession *session = SOUP_SESSION (filter);
838         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
839         GSList *f;
840
841         for (f = priv->filters; f; f = f->next) {
842                 filter = f->data;
843                 soup_message_filter_setup_message (filter, msg);
844         }
845
846         add_auth (session, msg, FALSE);
847         soup_message_add_status_code_handler (
848                 msg, SOUP_STATUS_UNAUTHORIZED,
849                 SOUP_HANDLER_POST_BODY,
850                 authorize_handler, session);
851
852         if (priv->proxy_uri) {
853                 add_auth (session, msg, TRUE);
854                 soup_message_add_status_code_handler  (
855                         msg, SOUP_STATUS_PROXY_UNAUTHORIZED,
856                         SOUP_HANDLER_POST_BODY,
857                         authorize_handler, session);
858         }
859 }
860
861 static void
862 find_oldest_connection (gpointer key, gpointer host, gpointer data)
863 {
864         SoupConnection *conn = key, **oldest = data;
865
866         /* Don't prune a connection that is currently in use, or
867          * hasn't been used yet.
868          */
869         if (soup_connection_is_in_use (conn) ||
870             soup_connection_last_used (conn) == 0)
871                 return;
872
873         if (!*oldest || (soup_connection_last_used (conn) <
874                          soup_connection_last_used (*oldest)))
875                 *oldest = conn;
876 }
877
878 /**
879  * soup_session_try_prune_connection:
880  * @session: a #SoupSession
881  *
882  * Finds the least-recently-used idle connection in @session and closes
883  * it.
884  *
885  * Return value: %TRUE if a connection was closed, %FALSE if there are
886  * no idle connections.
887  **/
888 gboolean
889 soup_session_try_prune_connection (SoupSession *session)
890 {
891         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
892         SoupConnection *oldest = NULL;
893
894         g_mutex_lock (priv->host_lock);
895         g_hash_table_foreach (priv->conns, find_oldest_connection,
896                               &oldest);
897         if (oldest) {
898                 /* Ref the connection before unlocking the mutex in
899                  * case someone else tries to prune it too.
900                  */
901                 g_object_ref (oldest);
902                 g_mutex_unlock (priv->host_lock);
903                 soup_connection_disconnect (oldest);
904                 g_object_unref (oldest);
905                 return TRUE;
906         } else {
907                 g_mutex_unlock (priv->host_lock);
908                 return FALSE;
909         }
910 }
911
912 static void
913 connection_disconnected (SoupConnection *conn, gpointer user_data)
914 {
915         SoupSession *session = user_data;
916         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
917         SoupSessionHost *host;
918
919         g_mutex_lock (priv->host_lock);
920
921         host = g_hash_table_lookup (priv->conns, conn);
922         if (host) {
923                 g_hash_table_remove (priv->conns, conn);
924                 host->connections = g_slist_remove (host->connections, conn);
925                 host->num_conns--;
926         }
927
928         g_signal_handlers_disconnect_by_func (conn, connection_disconnected, session);
929         priv->num_conns--;
930
931         g_mutex_unlock (priv->host_lock);
932         g_object_unref (conn);
933 }
934
935 static void
936 connect_result (SoupConnection *conn, guint status, gpointer user_data)
937 {
938         SoupSession *session = user_data;
939         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
940         SoupSessionHost *host;
941         SoupMessageQueueIter iter;
942         SoupMessage *msg;
943
944         g_mutex_lock (priv->host_lock);
945
946         host = g_hash_table_lookup (priv->conns, conn);
947         if (!host) {
948                 g_mutex_unlock (priv->host_lock);
949                 return;
950         }
951
952         if (status == SOUP_STATUS_OK) {
953                 soup_connection_reserve (conn);
954                 host->connections = g_slist_prepend (host->connections, conn);
955                 g_mutex_unlock (priv->host_lock);
956                 return;
957         }
958
959         /* The connection failed. */
960         g_mutex_unlock (priv->host_lock);
961         connection_disconnected (conn, session);
962
963         if (host->connections) {
964                 /* Something went wrong this time, but we have at
965                  * least one open connection to this host. So just
966                  * leave the message in the queue so it can use that
967                  * connection once it's free.
968                  */
969                 return;
970         }
971
972         /* There are two possibilities: either status is
973          * SOUP_STATUS_TRY_AGAIN, in which case the session implementation
974          * will create a new connection (and all we need to do here
975          * is downgrade the message from CONNECTING to QUEUED); or
976          * status is something else, probably CANT_CONNECT or
977          * CANT_RESOLVE or the like, in which case we need to cancel
978          * any messages waiting for this host, since they're out
979          * of luck.
980          */
981         for (msg = soup_message_queue_first (session->queue, &iter); msg; msg = soup_message_queue_next (session->queue, &iter)) {
982                 if (get_host_for_message (session, msg) == host) {
983                         if (status == SOUP_STATUS_TRY_AGAIN) {
984                                 if (msg->status == SOUP_MESSAGE_STATUS_CONNECTING)
985                                         msg->status = SOUP_MESSAGE_STATUS_QUEUED;
986                         } else {
987                                 soup_message_set_status (msg, status);
988                                 soup_session_cancel_message (session, msg);
989                         }
990                 }
991         }
992 }
993
994 /**
995  * soup_session_get_connection:
996  * @session: a #SoupSession
997  * @msg: a #SoupMessage
998  * @try_pruning: on return, whether or not to try pruning a connection
999  * @is_new: on return, %TRUE if the returned connection is new and not
1000  * yet connected
1001  * 
1002  * Tries to find or create a connection for @msg.
1003  *
1004  * If there is an idle connection to the relevant host available, then
1005  * that connection will be returned (with *@is_new set to %FALSE). The
1006  * connection will be marked "reserved", so the caller must call
1007  * soup_connection_release() if it ends up not using the connection
1008  * right away.
1009  *
1010  * If there is no idle connection available, but it is possible to
1011  * create a new connection, then one will be created and returned,
1012  * with *@is_new set to %TRUE. The caller MUST then call
1013  * soup_connection_connect_sync() or soup_connection_connect_async()
1014  * to connect it. If the connection attempt succeeds, the connection
1015  * will be marked "reserved" and added to @session's connection pool
1016  * once it connects. If the connection attempt fails, the connection
1017  * will be unreffed.
1018  *
1019  * If no connection is available and a new connection cannot be made,
1020  * soup_session_get_connection() will return %NULL. If @session has
1021  * the maximum number of open connections open, but does not have the
1022  * maximum number of per-host connections open to the relevant host,
1023  * then *@try_pruning will be set to %TRUE. In this case, the caller
1024  * can call soup_session_try_prune_connection() to close an idle
1025  * connection, and then try soup_session_get_connection() again. (If
1026  * calling soup_session_try_prune_connection() wouldn't help, then
1027  * *@try_pruning is left untouched; it is NOT set to %FALSE.)
1028  *
1029  * Return value: a #SoupConnection, or %NULL
1030  **/
1031 SoupConnection *
1032 soup_session_get_connection (SoupSession *session, SoupMessage *msg,
1033                              gboolean *try_pruning, gboolean *is_new)
1034 {
1035         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1036         SoupConnection *conn;
1037         SoupSessionHost *host;
1038         GSList *conns;
1039
1040         g_mutex_lock (priv->host_lock);
1041
1042         host = get_host_for_message (session, msg);
1043         for (conns = host->connections; conns; conns = conns->next) {
1044                 if (!soup_connection_is_in_use (conns->data)) {
1045                         soup_connection_reserve (conns->data);
1046                         g_mutex_unlock (priv->host_lock);
1047                         *is_new = FALSE;
1048                         return conns->data;
1049                 }
1050         }
1051
1052         if (msg->status == SOUP_MESSAGE_STATUS_CONNECTING) {
1053                 /* We already started a connection for this
1054                  * message, so don't start another one.
1055                  */
1056                 g_mutex_unlock (priv->host_lock);
1057                 return NULL;
1058         }
1059
1060         if (host->num_conns >= priv->max_conns_per_host) {
1061                 g_mutex_unlock (priv->host_lock);
1062                 return NULL;
1063         }
1064
1065         if (priv->num_conns >= priv->max_conns) {
1066                 *try_pruning = TRUE;
1067                 g_mutex_unlock (priv->host_lock);
1068                 return NULL;
1069         }
1070
1071         /* Make sure priv->proxy_host gets set now while
1072          * we have the host_lock.
1073          */
1074         if (priv->proxy_uri)
1075                 get_proxy_host (session);
1076
1077         conn = g_object_new (
1078                 (priv->use_ntlm ?
1079                  SOUP_TYPE_CONNECTION_NTLM : SOUP_TYPE_CONNECTION),
1080                 SOUP_CONNECTION_ORIGIN_URI, host->root_uri,
1081                 SOUP_CONNECTION_PROXY_URI, priv->proxy_uri,
1082                 SOUP_CONNECTION_SSL_CREDENTIALS, priv->ssl_creds,
1083                 SOUP_CONNECTION_MESSAGE_FILTER, session,
1084                 NULL);
1085         g_signal_connect (conn, "connect_result",
1086                           G_CALLBACK (connect_result),
1087                           session);
1088         g_signal_connect (conn, "disconnected",
1089                           G_CALLBACK (connection_disconnected),
1090                           session);
1091         g_signal_connect (conn, "authenticate",
1092                           G_CALLBACK (connection_authenticate),
1093                           session);
1094         g_signal_connect (conn, "reauthenticate",
1095                           G_CALLBACK (connection_reauthenticate),
1096                           session);
1097
1098         g_hash_table_insert (priv->conns, conn, host);
1099
1100         /* We increment the connection counts so it counts against the
1101          * totals, but we don't add it to the host's connection list
1102          * yet, since it's not ready for use.
1103          */
1104         priv->num_conns++;
1105         host->num_conns++;
1106
1107         /* Mark the request as connecting, so we don't try to open
1108          * another new connection for it while waiting for this one.
1109          */
1110         msg->status = SOUP_MESSAGE_STATUS_CONNECTING;
1111
1112         g_mutex_unlock (priv->host_lock);
1113         *is_new = TRUE;
1114         return conn;
1115 }
1116
1117 static void
1118 message_finished (SoupMessage *msg, gpointer user_data)
1119 {
1120         SoupSession *session = user_data;
1121
1122         if (!SOUP_MESSAGE_IS_STARTING (msg)) {
1123                 soup_message_queue_remove_message (session->queue, msg);
1124                 g_signal_handlers_disconnect_by_func (msg, message_finished, session);
1125         }
1126 }
1127
1128 static void
1129 queue_message (SoupSession *session, SoupMessage *msg,
1130                SoupMessageCallbackFn callback, gpointer user_data)
1131 {
1132         g_signal_connect_after (msg, "finished",
1133                                 G_CALLBACK (message_finished), session);
1134
1135         if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_NO_REDIRECT)) {
1136                 soup_message_add_status_class_handler (
1137                         msg, SOUP_STATUS_CLASS_REDIRECT,
1138                         SOUP_HANDLER_POST_BODY,
1139                         redirect_handler, session);
1140         }
1141
1142         msg->status = SOUP_MESSAGE_STATUS_QUEUED;
1143         soup_message_queue_append (session->queue, msg);
1144 }
1145
1146 /**
1147  * soup_session_queue_message:
1148  * @session: a #SoupSession
1149  * @msg: the message to queue
1150  * @callback: a #SoupMessageCallbackFn which will be called after the
1151  * message completes or when an unrecoverable error occurs.
1152  * @user_data: a pointer passed to @callback.
1153  * 
1154  * Queues the message @msg for sending. All messages are processed
1155  * while the glib main loop runs. If @msg has been processed before,
1156  * any resources related to the time it was last sent are freed.
1157  *
1158  * Upon message completion, the callback specified in @callback will
1159  * be invoked. If after returning from this callback the message has
1160  * not been requeued, @msg will be unreffed.
1161  */
1162 void
1163 soup_session_queue_message (SoupSession *session, SoupMessage *msg,
1164                             SoupMessageCallbackFn callback, gpointer user_data)
1165 {
1166         g_return_if_fail (SOUP_IS_SESSION (session));
1167         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1168
1169         SOUP_SESSION_GET_CLASS (session)->queue_message (session, msg,
1170                                                          callback, user_data);
1171 }
1172
1173 static void
1174 requeue_message (SoupSession *session, SoupMessage *msg)
1175 {
1176         msg->status = SOUP_MESSAGE_STATUS_QUEUED;
1177 }
1178
1179 /**
1180  * soup_session_requeue_message:
1181  * @session: a #SoupSession
1182  * @msg: the message to requeue
1183  *
1184  * This causes @msg to be placed back on the queue to be attempted
1185  * again.
1186  **/
1187 void
1188 soup_session_requeue_message (SoupSession *session, SoupMessage *msg)
1189 {
1190         g_return_if_fail (SOUP_IS_SESSION (session));
1191         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1192
1193         SOUP_SESSION_GET_CLASS (session)->requeue_message (session, msg);
1194 }
1195
1196
1197 /**
1198  * soup_session_send_message:
1199  * @session: a #SoupSession
1200  * @msg: the message to send
1201  * 
1202  * Synchronously send @msg. This call will not return until the
1203  * transfer is finished successfully or there is an unrecoverable
1204  * error.
1205  *
1206  * @msg is not freed upon return.
1207  *
1208  * Return value: the HTTP status code of the response
1209  */
1210 guint
1211 soup_session_send_message (SoupSession *session, SoupMessage *msg)
1212 {
1213         g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
1214         g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_STATUS_MALFORMED);
1215
1216         return SOUP_SESSION_GET_CLASS (session)->send_message (session, msg);
1217 }
1218
1219
1220 static void
1221 cancel_message (SoupSession *session, SoupMessage *msg)
1222 {
1223         soup_message_queue_remove_message (session->queue, msg);
1224         soup_message_finished (msg);
1225 }
1226
1227 /**
1228  * soup_session_cancel_message:
1229  * @session: a #SoupSession
1230  * @msg: the message to cancel
1231  *
1232  * Causes @session to immediately finish processing @msg. You should
1233  * set a status code on @msg with soup_message_set_status() before
1234  * calling this function.
1235  **/
1236 void
1237 soup_session_cancel_message (SoupSession *session, SoupMessage *msg)
1238 {
1239         g_return_if_fail (SOUP_IS_SESSION (session));
1240         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1241
1242         SOUP_SESSION_GET_CLASS (session)->cancel_message (session, msg);
1243 }
1244
1245 /**
1246  * soup_session_abort:
1247  * @session: the session
1248  *
1249  * Cancels all pending requests in @session.
1250  **/
1251 void
1252 soup_session_abort (SoupSession *session)
1253 {
1254         SoupMessageQueueIter iter;
1255         SoupMessage *msg;
1256
1257         g_return_if_fail (SOUP_IS_SESSION (session));
1258
1259         for (msg = soup_message_queue_first (session->queue, &iter); msg; msg = soup_message_queue_next (session->queue, &iter)) {
1260                 soup_message_set_status (msg, SOUP_STATUS_CANCELLED);
1261                 soup_session_cancel_message (session, msg);
1262         }
1263 }