source: trunk/third/libsoup/libsoup/soup-session.c @ 21108

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