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