soup-request-file: plug leak
[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-address.h"
17 #include "soup-auth.h"
18 #include "soup-auth-basic.h"
19 #include "soup-auth-digest.h"
20 #include "soup-auth-manager-ntlm.h"
21 #include "soup-connection.h"
22 #include "soup-marshal.h"
23 #include "soup-message-private.h"
24 #include "soup-message-queue.h"
25 #include "soup-misc.h"
26 #include "soup-proxy-resolver-static.h"
27 #include "soup-proxy-uri-resolver.h"
28 #include "soup-session.h"
29 #include "soup-session-feature.h"
30 #include "soup-session-private.h"
31 #include "soup-socket.h"
32 #include "soup-ssl.h"
33 #include "soup-uri.h"
34
35 /**
36  * SECTION:soup-session
37  * @short_description: Soup session state object
38  *
39  * #SoupSession is the object that controls client-side HTTP. A
40  * #SoupSession encapsulates all of the state that libsoup is keeping
41  * on behalf of your program; cached HTTP connections, authentication
42  * information, etc.
43  *
44  * Most applications will only need a single #SoupSession; the primary
45  * reason you might need multiple sessions is if you need to have
46  * multiple independent authentication contexts. (Eg, you are
47  * connecting to a server and authenticating as two different users at
48  * different times; the easiest way to ensure that each #SoupMessage
49  * is sent with the authentication information you intended is to use
50  * one session for the first user, and a second session for the other
51  * user.)
52  *
53  * #SoupSession itself is an abstract class, with two subclasses. If
54  * you are using the glib main loop, you will generally want to use
55  * #SoupSessionAsync, which uses non-blocking I/O and callbacks. On
56  * the other hand, if your application is threaded and you want to do
57  * synchronous I/O in a separate thread from the UI, use
58  * #SoupSessionSync.
59  **/
60
61 typedef struct {
62         SoupURI *uri;
63         SoupAddress *addr;
64
65         GSList      *connections;      /* CONTAINS: SoupConnection */
66         guint        num_conns;
67
68         guint        num_messages;
69 } SoupSessionHost;
70
71 typedef struct {
72         char *ssl_ca_file;
73         SoupSSLCredentials *ssl_creds;
74         gboolean ssl_strict;
75
76         SoupMessageQueue *queue;
77
78         char *user_agent;
79         char *accept_language;
80         gboolean accept_language_auto;
81
82         GSList *features;
83         GHashTable *features_cache;
84
85         GHashTable *hosts; /* char* -> SoupSessionHost */
86         GHashTable *conns; /* SoupConnection -> SoupSessionHost */
87         guint num_conns;
88         guint max_conns, max_conns_per_host;
89         guint io_timeout, idle_timeout;
90
91         /* Must hold the host_lock before potentially creating a
92          * new SoupSessionHost, or adding/removing a connection.
93          * Must not emit signals or destroy objects while holding it.
94          */
95         GMutex *host_lock;
96
97         GMainContext *async_context;
98
99         GResolver *resolver;
100 } SoupSessionPrivate;
101 #define SOUP_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION, SoupSessionPrivate))
102
103 static void     free_host      (SoupSessionHost *host);
104
105 static void queue_message   (SoupSession *session, SoupMessage *msg,
106                              SoupSessionCallback callback, gpointer user_data);
107 static void requeue_message (SoupSession *session, SoupMessage *msg);
108 static void cancel_message  (SoupSession *session, SoupMessage *msg,
109                              guint status_code);
110 static void auth_required   (SoupSession *session, SoupMessage *msg,
111                              SoupAuth *auth, gboolean retrying);
112 static void flush_queue     (SoupSession *session);
113
114 static void auth_manager_authenticate (SoupAuthManager *manager,
115                                        SoupMessage *msg, SoupAuth *auth,
116                                        gboolean retrying, gpointer user_data);
117
118 #define SOUP_SESSION_MAX_CONNS_DEFAULT 10
119 #define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 2
120
121 #define SOUP_SESSION_MAX_REDIRECTION_COUNT 20
122
123 #define SOUP_SESSION_USER_AGENT_BASE "libsoup/" PACKAGE_VERSION
124
125 G_DEFINE_ABSTRACT_TYPE (SoupSession, soup_session, G_TYPE_OBJECT)
126
127 enum {
128         REQUEST_QUEUED,
129         REQUEST_STARTED,
130         REQUEST_UNQUEUED,
131         AUTHENTICATE,
132         CONNECTION_CREATED,
133         TUNNELING,
134         LAST_SIGNAL
135 };
136
137 static guint signals[LAST_SIGNAL] = { 0 };
138
139 enum {
140         PROP_0,
141
142         PROP_PROXY_URI,
143         PROP_MAX_CONNS,
144         PROP_MAX_CONNS_PER_HOST,
145         PROP_USE_NTLM,
146         PROP_SSL_CA_FILE,
147         PROP_SSL_STRICT,
148         PROP_ASYNC_CONTEXT,
149         PROP_TIMEOUT,
150         PROP_USER_AGENT,
151         PROP_ACCEPT_LANGUAGE,
152         PROP_ACCEPT_LANGUAGE_AUTO,
153         PROP_IDLE_TIMEOUT,
154         PROP_ADD_FEATURE,
155         PROP_ADD_FEATURE_BY_TYPE,
156         PROP_REMOVE_FEATURE_BY_TYPE,
157
158         LAST_PROP
159 };
160
161 static void set_property (GObject *object, guint prop_id,
162                           const GValue *value, GParamSpec *pspec);
163 static void get_property (GObject *object, guint prop_id,
164                           GValue *value, GParamSpec *pspec);
165
166 static void
167 soup_session_init (SoupSession *session)
168 {
169         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
170         SoupAuthManager *auth_manager;
171
172         priv->queue = soup_message_queue_new (session);
173
174         priv->host_lock = g_mutex_new ();
175         priv->hosts = g_hash_table_new_full (soup_uri_host_hash,
176                                              soup_uri_host_equal,
177                                              NULL, (GDestroyNotify)free_host);
178         priv->conns = g_hash_table_new (NULL, NULL);
179
180         priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT;
181         priv->max_conns_per_host = SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT;
182
183         priv->features_cache = g_hash_table_new (NULL, NULL);
184
185         auth_manager = g_object_new (SOUP_TYPE_AUTH_MANAGER_NTLM, NULL);
186         g_signal_connect (auth_manager, "authenticate",
187                           G_CALLBACK (auth_manager_authenticate), session);
188         soup_session_feature_add_feature (SOUP_SESSION_FEATURE (auth_manager),
189                                           SOUP_TYPE_AUTH_BASIC);
190         soup_session_feature_add_feature (SOUP_SESSION_FEATURE (auth_manager),
191                                           SOUP_TYPE_AUTH_DIGEST);
192         soup_session_add_feature (session, SOUP_SESSION_FEATURE (auth_manager));
193         g_object_unref (auth_manager);
194
195         /* We'll be doing DNS continuously-ish while the session is active,
196          * so hold a ref on the default GResolver.
197          */
198         priv->resolver = g_resolver_get_default ();
199
200         priv->ssl_strict = TRUE;
201 }
202
203 static void
204 dispose (GObject *object)
205 {
206         SoupSession *session = SOUP_SESSION (object);
207         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
208
209         soup_session_abort (session);
210
211         while (priv->features)
212                 soup_session_remove_feature (session, priv->features->data);
213
214         G_OBJECT_CLASS (soup_session_parent_class)->dispose (object);
215 }
216
217 static void
218 finalize (GObject *object)
219 {
220         SoupSession *session = SOUP_SESSION (object);
221         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
222
223         soup_message_queue_destroy (priv->queue);
224
225         g_mutex_free (priv->host_lock);
226         g_hash_table_destroy (priv->hosts);
227         g_hash_table_destroy (priv->conns);
228
229         g_free (priv->user_agent);
230         g_free (priv->accept_language);
231
232         if (priv->ssl_ca_file)
233                 g_free (priv->ssl_ca_file);
234         if (priv->ssl_creds)
235                 soup_ssl_free_client_credentials (priv->ssl_creds);
236
237         if (priv->async_context)
238                 g_main_context_unref (priv->async_context);
239
240         g_hash_table_destroy (priv->features_cache);
241
242         g_object_unref (priv->resolver);
243
244         G_OBJECT_CLASS (soup_session_parent_class)->finalize (object);
245 }
246
247 static void
248 soup_session_class_init (SoupSessionClass *session_class)
249 {
250         GObjectClass *object_class = G_OBJECT_CLASS (session_class);
251
252         g_type_class_add_private (session_class, sizeof (SoupSessionPrivate));
253
254         /* virtual method definition */
255         session_class->queue_message = queue_message;
256         session_class->requeue_message = requeue_message;
257         session_class->cancel_message = cancel_message;
258         session_class->auth_required = auth_required;
259         session_class->flush_queue = flush_queue;
260
261         /* virtual method override */
262         object_class->dispose = dispose;
263         object_class->finalize = finalize;
264         object_class->set_property = set_property;
265         object_class->get_property = get_property;
266
267         /* signals */
268
269         /**
270          * SoupSession::request-queued:
271          * @session: the session
272          * @msg: the request that was queued
273          *
274          * Emitted when a request is queued on @session. (Note that
275          * "queued" doesn't just mean soup_session_queue_message();
276          * soup_session_send_message() implicitly queues the message
277          * as well.)
278          *
279          * When sending a request, first #SoupSession::request_queued
280          * is emitted, indicating that the session has become aware of
281          * the request.
282          *
283          * Once a connection is available to send the request on, the
284          * session emits #SoupSession::request_started. Then, various
285          * #SoupMessage signals are emitted as the message is
286          * processed. If the message is requeued, it will emit
287          * #SoupMessage::restarted, which will then be followed by
288          * another #SoupSession::request_started and another set of
289          * #SoupMessage signals when the message is re-sent.
290          *
291          * Eventually, the message will emit #SoupMessage::finished.
292          * Normally, this signals the completion of message
293          * processing. However, it is possible that the application
294          * will requeue the message from the "finished" handler (or
295          * equivalently, from the soup_session_queue_message()
296          * callback). In that case, the process will loop back to
297          * #SoupSession::request_started.
298          *
299          * Eventually, a message will reach "finished" and not be
300          * requeued. At that point, the session will emit
301          * #SoupSession::request_unqueued to indicate that it is done
302          * with the message.
303          *
304          * To sum up: #SoupSession::request_queued and
305          * #SoupSession::request_unqueued are guaranteed to be emitted
306          * exactly once, but #SoupSession::request_started and
307          * #SoupMessage::finished (and all of the other #SoupMessage
308          * signals) may be invoked multiple times for a given message.
309          *
310          * Since: 2.4.1
311          **/
312         signals[REQUEST_QUEUED] =
313                 g_signal_new ("request-queued",
314                               G_OBJECT_CLASS_TYPE (object_class),
315                               G_SIGNAL_RUN_FIRST,
316                               0, /* FIXME? */
317                               NULL, NULL,
318                               soup_marshal_NONE__OBJECT,
319                               G_TYPE_NONE, 1,
320                               SOUP_TYPE_MESSAGE);
321
322         /**
323          * SoupSession::request-started:
324          * @session: the session
325          * @msg: the request being sent
326          * @socket: the socket the request is being sent on
327          *
328          * Emitted just before a request is sent. See
329          * #SoupSession::request_queued for a detailed description of
330          * the message lifecycle within a session.
331          **/
332         signals[REQUEST_STARTED] =
333                 g_signal_new ("request-started",
334                               G_OBJECT_CLASS_TYPE (object_class),
335                               G_SIGNAL_RUN_FIRST,
336                               G_STRUCT_OFFSET (SoupSessionClass, request_started),
337                               NULL, NULL,
338                               soup_marshal_NONE__OBJECT_OBJECT,
339                               G_TYPE_NONE, 2,
340                               SOUP_TYPE_MESSAGE,
341                               SOUP_TYPE_SOCKET);
342
343         /**
344          * SoupSession::request-unqueued:
345          * @session: the session
346          * @msg: the request that was unqueued
347          *
348          * Emitted when a request is removed from @session's queue,
349          * indicating that @session is done with it. See
350          * #SoupSession::request_queued for a detailed description of the
351          * message lifecycle within a session.
352          *
353          * Since: 2.4.1
354          **/
355         signals[REQUEST_UNQUEUED] =
356                 g_signal_new ("request-unqueued",
357                               G_OBJECT_CLASS_TYPE (object_class),
358                               G_SIGNAL_RUN_FIRST,
359                               0, /* FIXME? */
360                               NULL, NULL,
361                               soup_marshal_NONE__OBJECT,
362                               G_TYPE_NONE, 1,
363                               SOUP_TYPE_MESSAGE);
364
365         /**
366          * SoupSession::authenticate:
367          * @session: the session
368          * @msg: the #SoupMessage being sent
369          * @auth: the #SoupAuth to authenticate
370          * @retrying: %TRUE if this is the second (or later) attempt
371          *
372          * Emitted when the session requires authentication. If
373          * credentials are available call soup_auth_authenticate() on
374          * @auth. If these credentials fail, the signal will be
375          * emitted again, with @retrying set to %TRUE, which will
376          * continue until you return without calling
377          * soup_auth_authenticate() on @auth.
378          *
379          * Note that this may be emitted before @msg's body has been
380          * fully read.
381          *
382          * If you call soup_session_pause_message() on @msg before
383          * returning, then you can authenticate @auth asynchronously
384          * (as long as you g_object_ref() it to make sure it doesn't
385          * get destroyed), and then unpause @msg when you are ready
386          * for it to continue.
387          **/
388         signals[AUTHENTICATE] =
389                 g_signal_new ("authenticate",
390                               G_OBJECT_CLASS_TYPE (object_class),
391                               G_SIGNAL_RUN_FIRST,
392                               G_STRUCT_OFFSET (SoupSessionClass, authenticate),
393                               NULL, NULL,
394                               soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN,
395                               G_TYPE_NONE, 3,
396                               SOUP_TYPE_MESSAGE,
397                               SOUP_TYPE_AUTH,
398                               G_TYPE_BOOLEAN);
399
400         signals[CONNECTION_CREATED] =
401                 g_signal_new ("connection-created",
402                               G_OBJECT_CLASS_TYPE (object_class),
403                               G_SIGNAL_RUN_FIRST,
404                               0,
405                               NULL, NULL,
406                               soup_marshal_NONE__OBJECT,
407                               G_TYPE_NONE, 1,
408                               /* SoupConnection is private, so we can't use
409                                * SOUP_TYPE_CONNECTION here.
410                                */
411                               G_TYPE_OBJECT);
412
413         signals[TUNNELING] =
414                 g_signal_new ("tunneling",
415                               G_OBJECT_CLASS_TYPE (object_class),
416                               G_SIGNAL_RUN_FIRST,
417                               0,
418                               NULL, NULL,
419                               soup_marshal_NONE__OBJECT,
420                               G_TYPE_NONE, 1,
421                               /* SoupConnection is private, so we can't use
422                                * SOUP_TYPE_CONNECTION here.
423                                */
424                               G_TYPE_OBJECT);
425
426
427         /* properties */
428         /**
429          * SOUP_SESSION_PROXY_URI:
430          *
431          * Alias for the #SoupSession:proxy-uri property. (The HTTP
432          * proxy to use for this session.)
433          **/
434         g_object_class_install_property (
435                 object_class, PROP_PROXY_URI,
436                 g_param_spec_boxed (SOUP_SESSION_PROXY_URI,
437                                     "Proxy URI",
438                                     "The HTTP Proxy to use for this session",
439                                     SOUP_TYPE_URI,
440                                     G_PARAM_READWRITE));
441         /**
442          * SOUP_SESSION_MAX_CONNS:
443          *
444          * Alias for the #SoupSession:max-conns property. (The maximum
445          * number of connections that the session can open at once.)
446          **/
447         g_object_class_install_property (
448                 object_class, PROP_MAX_CONNS,
449                 g_param_spec_int (SOUP_SESSION_MAX_CONNS,
450                                   "Max Connection Count",
451                                   "The maximum number of connections that the session can open at once",
452                                   1,
453                                   G_MAXINT,
454                                   SOUP_SESSION_MAX_CONNS_DEFAULT,
455                                   G_PARAM_READWRITE));
456         /**
457          * SOUP_SESSION_MAX_CONNS_PER_HOST:
458          *
459          * Alias for the #SoupSession:max-conns-per-host property.
460          * (The maximum number of connections that the session can
461          * open at once to a given host.)
462          **/
463         g_object_class_install_property (
464                 object_class, PROP_MAX_CONNS_PER_HOST,
465                 g_param_spec_int (SOUP_SESSION_MAX_CONNS_PER_HOST,
466                                   "Max Per-Host Connection Count",
467                                   "The maximum number of connections that the session can open at once to a given host",
468                                   1,
469                                   G_MAXINT,
470                                   SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT,
471                                   G_PARAM_READWRITE));
472         /**
473          * SoupSession:idle-timeout:
474          *
475          * Connection lifetime when idle
476          *
477          * Since: 2.4.1
478          **/
479         /**
480          * SOUP_SESSION_IDLE_TIMEOUT:
481          *
482          * Alias for the #SoupSession:idle-timeout property. (The idle
483          * connection lifetime.)
484          *
485          * Since: 2.4.1
486          **/
487         g_object_class_install_property (
488                 object_class, PROP_IDLE_TIMEOUT,
489                 g_param_spec_uint (SOUP_SESSION_IDLE_TIMEOUT,
490                                    "Idle Timeout",
491                                    "Connection lifetime when idle",
492                                    0, G_MAXUINT, 0,
493                                    G_PARAM_READWRITE));
494         /**
495          * SoupSession:use-ntlm:
496          *
497          * Whether or not to use NTLM authentication.
498          *
499          * Deprecated: use soup_session_add_feature_by_type() with
500          * #SOUP_TYPE_AUTH_NTLM.
501          **/
502         /**
503          * SOUP_SESSION_USE_NTLM:
504          *
505          * Alias for the #SoupSession:use-ntlm property. (Whether or
506          * not to use NTLM authentication.)
507          **/
508         g_object_class_install_property (
509                 object_class, PROP_USE_NTLM,
510                 g_param_spec_boolean (SOUP_SESSION_USE_NTLM,
511                                       "Use NTLM",
512                                       "Whether or not to use NTLM authentication",
513                                       FALSE,
514                                       G_PARAM_READWRITE));
515         /**
516          * SOUP_SESSION_SSL_CA_FILE:
517          *
518          * Alias for the #SoupSession:ssl-ca-file property. (File
519          * containing SSL CA certificates.)
520          **/
521         g_object_class_install_property (
522                 object_class, PROP_SSL_CA_FILE,
523                 g_param_spec_string (SOUP_SESSION_SSL_CA_FILE,
524                                      "SSL CA file",
525                                      "File containing SSL CA certificates",
526                                      NULL,
527                                      G_PARAM_READWRITE));
528         /**
529          * SOUP_SESSION_SSL_STRICT:
530          *
531          * Alias for the #SoupSession:ignore-ssl-cert-errors
532          * property. By default, when validating certificates against
533          * a CA file, Soup will consider invalid certificates as a
534          * connection error. Setting this property to %TRUE makes soup
535          * ignore the errors, and make the connection.
536          *
537          * Since: 2.30
538          **/
539         g_object_class_install_property (
540                 object_class, PROP_SSL_STRICT,
541                 g_param_spec_boolean (SOUP_SESSION_SSL_STRICT,
542                                       "Strictly validate SSL certificates",
543                                       "Whether certificate errors should be considered a connection error",
544                                       TRUE,
545                                       G_PARAM_READWRITE));
546         /**
547          * SOUP_SESSION_ASYNC_CONTEXT:
548          *
549          * Alias for the #SoupSession:async-context property. (The
550          * session's #GMainContext.)
551          **/
552         g_object_class_install_property (
553                 object_class, PROP_ASYNC_CONTEXT,
554                 g_param_spec_pointer (SOUP_SESSION_ASYNC_CONTEXT,
555                                       "Async GMainContext",
556                                       "The GMainContext to dispatch async I/O in",
557                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
558         /**
559          * SOUP_SESSION_TIMEOUT:
560          *
561          * Alias for the #SoupSession:timeout property. (The timeout
562          * in seconds for blocking socket I/O operations.)
563          **/
564         g_object_class_install_property (
565                 object_class, PROP_TIMEOUT,
566                 g_param_spec_uint (SOUP_SESSION_TIMEOUT,
567                                    "Timeout value",
568                                    "Value in seconds to timeout a blocking I/O",
569                                    0, G_MAXUINT, 0,
570                                    G_PARAM_READWRITE));
571
572         /**
573          * SoupSession:user-agent:
574          *
575          * If non-%NULL, the value to use for the "User-Agent" header
576          * on #SoupMessage<!-- -->s sent from this session.
577          *
578          * RFC 2616 says: "The User-Agent request-header field
579          * contains information about the user agent originating the
580          * request. This is for statistical purposes, the tracing of
581          * protocol violations, and automated recognition of user
582          * agents for the sake of tailoring responses to avoid
583          * particular user agent limitations. User agents SHOULD
584          * include this field with requests."
585          *
586          * The User-Agent header contains a list of one or more
587          * product tokens, separated by whitespace, with the most
588          * significant product token coming first. The tokens must be
589          * brief, ASCII, and mostly alphanumeric (although "-", "_",
590          * and "." are also allowed), and may optionally include a "/"
591          * followed by a version string. You may also put comments,
592          * enclosed in parentheses, between or after the tokens.
593          *
594          * If you set a %user_agent property that has trailing
595          * whitespace, #SoupSession will append its own product token
596          * (eg, "<literal>libsoup/2.3.2</literal>") to the end of the
597          * header for you.
598          **/
599         /**
600          * SOUP_SESSION_USER_AGENT:
601          *
602          * Alias for the #SoupSession:user-agent property, qv.
603          **/
604         g_object_class_install_property (
605                 object_class, PROP_USER_AGENT,
606                 g_param_spec_string (SOUP_SESSION_USER_AGENT,
607                                      "User-Agent string",
608                                      "User-Agent string",
609                                      NULL,
610                                      G_PARAM_READWRITE));
611
612         /**
613          * SoupSession:accept-language:
614          *
615          * If non-%NULL, the value to use for the "Accept-Language" header
616          * on #SoupMessage<!-- -->s sent from this session.
617          *
618          * Setting this will disable
619          * #SoupSession:accept-language-auto.
620          *
621          * Since: 2.30
622          **/
623         /**
624          * SOUP_SESSION_ACCEPT_LANGUAGE:
625          *
626          * Alias for the #SoupSession:accept-language property, qv.
627          **/
628         g_object_class_install_property (
629                 object_class, PROP_ACCEPT_LANGUAGE,
630                 g_param_spec_string (SOUP_SESSION_ACCEPT_LANGUAGE,
631                                      "Accept-Language string",
632                                      "Accept-Language string",
633                                      NULL,
634                                      G_PARAM_READWRITE));
635
636         /**
637          * SoupSession:accept-language-auto:
638          *
639          * If %TRUE, #SoupSession will automatically set the string
640          * for the "Accept-Language" header on every #SoupMessage
641          * sent, based on the return value of g_get_language_names().
642          *
643          * Setting this will override any previous value of
644          * #SoupSession:accept-language.
645          *
646          * Since: 2.30
647          **/
648         /**
649          * SOUP_SESSION_ACCEPT_LANGUAGE_AUTO:
650          *
651          * Alias for the #SoupSession:accept-language-auto property, qv.
652          **/
653         g_object_class_install_property (
654                 object_class, PROP_ACCEPT_LANGUAGE_AUTO,
655                 g_param_spec_boolean (SOUP_SESSION_ACCEPT_LANGUAGE_AUTO,
656                                       "Accept-Language automatic mode",
657                                       "Accept-Language automatic mode",
658                                       FALSE,
659                                       G_PARAM_READWRITE));
660
661         /**
662          * SoupSession:add-feature:
663          *
664          * Add a feature object to the session. (Shortcut for calling
665          * soup_session_add_feature().)
666          *
667          * Since: 2.24
668          **/
669         /**
670          * SOUP_SESSION_ADD_FEATURE:
671          *
672          * Alias for the #SoupSession:add-feature property. (Shortcut
673          * for calling soup_session_add_feature().
674          *
675          * Since: 2.24
676          **/
677         g_object_class_install_property (
678                 object_class, PROP_ADD_FEATURE,
679                 g_param_spec_object (SOUP_SESSION_ADD_FEATURE,
680                                      "Add Feature",
681                                      "Add a feature object to the session",
682                                      SOUP_TYPE_SESSION_FEATURE,
683                                      G_PARAM_READWRITE));
684         /**
685          * SoupSession:add-feature-by-type:
686          *
687          * Add a feature object of the given type to the session.
688          * (Shortcut for calling soup_session_add_feature_by_type().)
689          *
690          * Since: 2.24
691          **/
692         /**
693          * SOUP_SESSION_ADD_FEATURE_BY_TYPE:
694          *
695          * Alias for the #SoupSession:add-feature-by-type property.
696          * (Shortcut for calling soup_session_add_feature_by_type().
697          *
698          * Since: 2.24
699          **/
700         g_object_class_install_property (
701                 object_class, PROP_ADD_FEATURE_BY_TYPE,
702                 g_param_spec_gtype (SOUP_SESSION_ADD_FEATURE_BY_TYPE,
703                                     "Add Feature By Type",
704                                     "Add a feature object of the given type to the session",
705                                     SOUP_TYPE_SESSION_FEATURE,
706                                     G_PARAM_READWRITE));
707         /**
708          * SoupSession:remove-feature-by-type:
709          *
710          * Remove feature objects from the session. (Shortcut for
711          * calling soup_session_remove_feature_by_type().)
712          *
713          * Since: 2.24
714          **/
715         /**
716          * SOUP_SESSION_REMOVE_FEATURE_BY_TYPE:
717          *
718          * Alias for the #SoupSession:remove-feature-by-type
719          * property. (Shortcut for calling
720          * soup_session_remove_feature_by_type().
721          *
722          * Since: 2.24
723          **/
724         g_object_class_install_property (
725                 object_class, PROP_REMOVE_FEATURE_BY_TYPE,
726                 g_param_spec_gtype (SOUP_SESSION_REMOVE_FEATURE_BY_TYPE,
727                                     "Remove Feature By Type",
728                                     "Remove features of the given type from the session",
729                                     SOUP_TYPE_SESSION_FEATURE,
730                                     G_PARAM_READWRITE));
731 }
732
733 static gboolean
734 safe_str_equal (const char *a, const char *b)
735 {
736         if (!a && !b)
737                 return TRUE;
738
739         if ((a && !b) || (b && !a))
740                 return FALSE;
741
742         return strcmp (a, b) == 0;
743 }
744
745 /* Converts a language in POSIX format and to be RFC2616 compliant    */
746 /* Based on code from epiphany-webkit (ephy_langs_append_languages()) */
747 static gchar *
748 posix_lang_to_rfc2616 (const gchar *language)
749 {
750         /* Don't include charset variants, etc */
751         if (strchr (language, '.') || strchr (language, '@'))
752                 return NULL;
753
754         /* Ignore "C" locale, which g_get_language_names() always
755          * includes as a fallback.
756          */
757         if (!strcmp (language, "C"))
758                 return NULL;
759
760         return g_strdelimit (g_ascii_strdown (language, -1), "_", '-');
761 }
762
763 /* Converts @quality from 0-100 to 0.0-1.0 and appends to @str */
764 static gchar *
765 add_quality_value (const gchar *str, int quality)
766 {
767         g_return_val_if_fail (str != NULL, NULL);
768
769         if (quality >= 0 && quality < 100) {
770                 /* We don't use %.02g because of "." vs "," locale issues */
771                 if (quality % 10)
772                         return g_strdup_printf ("%s;q=0.%02d", str, quality);
773                 else
774                         return g_strdup_printf ("%s;q=0.%d", str, quality / 10);
775         } else
776                 return g_strdup (str);
777 }
778
779 /* Returns a RFC2616 compliant languages list from system locales */
780 static gchar *
781 accept_languages_from_system (void)
782 {
783         const char * const * lang_names;
784         GPtrArray *langs = NULL;
785         char *lang, **langs_array, *langs_str;
786         int delta;
787         int i;
788
789         lang_names = g_get_language_names ();
790         g_return_val_if_fail (lang_names != NULL, NULL);
791
792         /* Build the array of languages */
793         langs = g_ptr_array_new ();
794         for (i = 0; lang_names[i] != NULL; i++) {
795                 lang = posix_lang_to_rfc2616 (lang_names[i]);
796                 if (lang)
797                         g_ptr_array_add (langs, lang);
798         }
799
800         /* Add quality values */
801         if (langs->len < 10)
802                 delta = 10;
803         else if (langs->len < 20)
804                 delta = 5;
805         else
806                 delta = 1;
807
808         for (i = 0; i < langs->len; i++) {
809                 lang = langs->pdata[i];
810                 langs->pdata[i] = add_quality_value (lang, 100 - i * delta);
811                 g_free (lang);
812         }
813
814         /* Fallback: add "en" if list is empty */
815         if (langs->len == 0)
816                 g_ptr_array_add (langs, g_strdup ("en"));
817
818         g_ptr_array_add (langs, NULL);
819         langs_array = (char **)langs->pdata;
820         langs_str = g_strjoinv (", ", langs_array);
821
822         g_strfreev (langs_array);
823         g_ptr_array_free (langs, FALSE);
824
825         return langs_str;
826 }
827
828 static void
829 set_property (GObject *object, guint prop_id,
830               const GValue *value, GParamSpec *pspec)
831 {
832         SoupSession *session = SOUP_SESSION (object);
833         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
834         SoupURI *uri;
835         gboolean ca_file_changed = FALSE;
836         const char *new_ca_file, *user_agent;
837         SoupSessionFeature *feature;
838
839         switch (prop_id) {
840         case PROP_PROXY_URI:
841                 uri = g_value_get_boxed (value);
842
843                 if (uri) {
844                         soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER);
845                         feature = SOUP_SESSION_FEATURE (soup_proxy_resolver_static_new (uri));
846                         soup_session_add_feature (session, feature);
847                         g_object_unref (feature);
848                 } else
849                         soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER_STATIC);
850
851                 soup_session_abort (session);
852                 break;
853         case PROP_MAX_CONNS:
854                 priv->max_conns = g_value_get_int (value);
855                 break;
856         case PROP_MAX_CONNS_PER_HOST:
857                 priv->max_conns_per_host = g_value_get_int (value);
858                 break;
859         case PROP_USE_NTLM:
860                 feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER_NTLM);
861                 if (feature) {
862                         if (g_value_get_boolean (value))
863                                 soup_session_feature_add_feature (feature, SOUP_TYPE_AUTH_NTLM);
864                         else
865                                 soup_session_feature_remove_feature (feature, SOUP_TYPE_AUTH_NTLM);
866                 } else
867                         g_warning ("Trying to set use-ntlm on session with no auth-manager");
868                 break;
869         case PROP_SSL_CA_FILE:
870                 new_ca_file = g_value_get_string (value);
871
872                 if (!safe_str_equal (priv->ssl_ca_file, new_ca_file))
873                         ca_file_changed = TRUE;
874
875                 g_free (priv->ssl_ca_file);
876                 priv->ssl_ca_file = g_strdup (new_ca_file);
877
878                 if (ca_file_changed && priv->ssl_creds) {
879                         soup_ssl_free_client_credentials (priv->ssl_creds);
880                         priv->ssl_creds = NULL;
881                 }
882
883                 break;
884         case PROP_SSL_STRICT:
885                 priv->ssl_strict = g_value_get_boolean (value);
886                 break;
887         case PROP_ASYNC_CONTEXT:
888                 priv->async_context = g_value_get_pointer (value);
889                 if (priv->async_context)
890                         g_main_context_ref (priv->async_context);
891                 break;
892         case PROP_TIMEOUT:
893                 priv->io_timeout = g_value_get_uint (value);
894                 break;
895         case PROP_USER_AGENT:
896                 g_free (priv->user_agent);
897                 user_agent = g_value_get_string (value);
898                 if (!user_agent)
899                         priv->user_agent = NULL;
900                 else if (!*user_agent) {
901                         priv->user_agent =
902                                 g_strdup (SOUP_SESSION_USER_AGENT_BASE);
903                 } else if (g_str_has_suffix (user_agent, " ")) {
904                         priv->user_agent =
905                                 g_strdup_printf ("%s%s", user_agent,
906                                                  SOUP_SESSION_USER_AGENT_BASE);
907                 } else
908                         priv->user_agent = g_strdup (user_agent);
909                 break;
910         case PROP_ACCEPT_LANGUAGE:
911                 g_free (priv->accept_language);
912                 priv->accept_language = g_strdup (g_value_get_string (value));
913                 priv->accept_language_auto = FALSE;
914                 break;
915         case PROP_ACCEPT_LANGUAGE_AUTO:
916                 priv->accept_language_auto = g_value_get_boolean (value);
917                 if (priv->accept_language) {
918                         g_free (priv->accept_language);
919                         priv->accept_language = NULL;
920                 }
921
922                 /* Get languages from system if needed */
923                 if (priv->accept_language_auto)
924                         priv->accept_language = accept_languages_from_system ();
925                 break;
926         case PROP_IDLE_TIMEOUT:
927                 priv->idle_timeout = g_value_get_uint (value);
928                 break;
929         case PROP_ADD_FEATURE:
930                 soup_session_add_feature (session, g_value_get_object (value));
931                 break;
932         case PROP_ADD_FEATURE_BY_TYPE:
933                 soup_session_add_feature_by_type (session, g_value_get_gtype (value));
934                 break;
935         case PROP_REMOVE_FEATURE_BY_TYPE:
936                 soup_session_remove_feature_by_type (session, g_value_get_gtype (value));
937                 break;
938         default:
939                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
940                 break;
941         }
942 }
943
944 static void
945 get_property (GObject *object, guint prop_id,
946               GValue *value, GParamSpec *pspec)
947 {
948         SoupSession *session = SOUP_SESSION (object);
949         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
950         SoupSessionFeature *feature;
951
952         switch (prop_id) {
953         case PROP_PROXY_URI:
954                 feature = soup_session_get_feature (session, SOUP_TYPE_PROXY_RESOLVER_STATIC);
955                 if (feature) {
956                         g_object_get_property (G_OBJECT (feature),
957                                                SOUP_PROXY_RESOLVER_STATIC_PROXY_URI,
958                                                value);
959                 } else
960                         g_value_set_boxed (value, NULL);
961                 break;
962         case PROP_MAX_CONNS:
963                 g_value_set_int (value, priv->max_conns);
964                 break;
965         case PROP_MAX_CONNS_PER_HOST:
966                 g_value_set_int (value, priv->max_conns_per_host);
967                 break;
968         case PROP_USE_NTLM:
969                 feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER_NTLM);
970                 if (feature)
971                         g_value_set_boolean (value, soup_session_feature_has_feature (feature, SOUP_TYPE_AUTH_NTLM));
972                 else
973                         g_value_set_boolean (value, FALSE);
974                 break;
975         case PROP_SSL_CA_FILE:
976                 g_value_set_string (value, priv->ssl_ca_file);
977                 break;
978         case PROP_SSL_STRICT:
979                 g_value_set_boolean (value, priv->ssl_strict);
980                 break;
981         case PROP_ASYNC_CONTEXT:
982                 g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL);
983                 break;
984         case PROP_TIMEOUT:
985                 g_value_set_uint (value, priv->io_timeout);
986                 break;
987         case PROP_USER_AGENT:
988                 g_value_set_string (value, priv->user_agent);
989                 break;
990         case PROP_ACCEPT_LANGUAGE:
991                 g_value_set_string (value, priv->accept_language);
992                 break;
993         case PROP_ACCEPT_LANGUAGE_AUTO:
994                 g_value_set_boolean (value, priv->accept_language_auto);
995                 break;
996         case PROP_IDLE_TIMEOUT:
997                 g_value_set_uint (value, priv->idle_timeout);
998                 break;
999         default:
1000                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1001                 break;
1002         }
1003 }
1004
1005
1006 /**
1007  * soup_session_get_async_context:
1008  * @session: a #SoupSession
1009  *
1010  * Gets @session's async_context. This does not add a ref to the
1011  * context, so you will need to ref it yourself if you want it to
1012  * outlive its session.
1013  *
1014  * Return value: (transfer none): @session's #GMainContext, which may
1015  * be %NULL
1016  **/
1017 GMainContext *
1018 soup_session_get_async_context (SoupSession *session)
1019 {
1020         SoupSessionPrivate *priv;
1021
1022         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
1023         priv = SOUP_SESSION_GET_PRIVATE (session);
1024
1025         return priv->async_context;
1026 }
1027
1028 /* Hosts */
1029
1030 static SoupSessionHost *
1031 soup_session_host_new (SoupSession *session, SoupURI *uri)
1032 {
1033         SoupSessionHost *host;
1034
1035         host = g_slice_new0 (SoupSessionHost);
1036         host->uri = soup_uri_copy_host (uri);
1037         host->addr = soup_address_new (host->uri->host, host->uri->port);
1038
1039         return host;
1040 }
1041
1042 /* Requires host_lock to be locked */
1043 static SoupSessionHost *
1044 get_host_for_uri (SoupSession *session, SoupURI *uri)
1045 {
1046         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1047         SoupSessionHost *host;
1048
1049         host = g_hash_table_lookup (priv->hosts, uri);
1050         if (host)
1051                 return host;
1052
1053         host = soup_session_host_new (session, uri);
1054         g_hash_table_insert (priv->hosts, host->uri, host);
1055
1056         return host;
1057 }
1058
1059 /* Note: get_host_for_message doesn't lock the host_lock. The caller
1060  * must do it itself if there's a chance the host doesn't already
1061  * exist.
1062  */
1063 static SoupSessionHost *
1064 get_host_for_message (SoupSession *session, SoupMessage *msg)
1065 {
1066         return get_host_for_uri (session, soup_message_get_uri (msg));
1067 }
1068
1069 static void
1070 free_host (SoupSessionHost *host)
1071 {
1072         while (host->connections) {
1073                 SoupConnection *conn = host->connections->data;
1074
1075                 host->connections = g_slist_remove (host->connections, conn);
1076                 soup_connection_disconnect (conn);
1077         }
1078
1079         soup_uri_free (host->uri);
1080         g_object_unref (host->addr);
1081         g_slice_free (SoupSessionHost, host);
1082 }       
1083
1084 static void
1085 auth_required (SoupSession *session, SoupMessage *msg,
1086                SoupAuth *auth, gboolean retrying)
1087 {
1088         g_signal_emit (session, signals[AUTHENTICATE], 0, msg, auth, retrying);
1089 }
1090
1091 static void
1092 auth_manager_authenticate (SoupAuthManager *manager, SoupMessage *msg,
1093                            SoupAuth *auth, gboolean retrying,
1094                            gpointer session)
1095 {
1096         SOUP_SESSION_GET_CLASS (session)->auth_required (
1097                 session, msg, auth, retrying);
1098 }
1099
1100 #define SOUP_METHOD_IS_SAFE(method) (method == SOUP_METHOD_GET || \
1101                                      method == SOUP_METHOD_HEAD || \
1102                                      method == SOUP_METHOD_OPTIONS || \
1103                                      method == SOUP_METHOD_PROPFIND)
1104
1105 static void
1106 redirect_handler (SoupMessage *msg, gpointer user_data)
1107 {
1108         SoupMessageQueueItem *item = user_data;
1109         SoupSession *session = item->session;
1110         const char *new_loc;
1111         SoupURI *new_uri;
1112
1113         new_loc = soup_message_headers_get_one (msg->response_headers,
1114                                                 "Location");
1115         g_return_if_fail (new_loc != NULL);
1116
1117         if (item->redirection_count >= SOUP_SESSION_MAX_REDIRECTION_COUNT) {
1118                 soup_session_cancel_message (session, msg, SOUP_STATUS_TOO_MANY_REDIRECTS);
1119                 return;
1120         }
1121         item->redirection_count++;
1122
1123         if (msg->status_code == SOUP_STATUS_SEE_OTHER ||
1124             (msg->status_code == SOUP_STATUS_FOUND &&
1125              !SOUP_METHOD_IS_SAFE (msg->method)) ||
1126             (msg->status_code == SOUP_STATUS_MOVED_PERMANENTLY &&
1127              msg->method == SOUP_METHOD_POST)) {
1128                 if (msg->method != SOUP_METHOD_HEAD) {
1129                         /* Redirect using a GET */
1130                         g_object_set (msg,
1131                                       SOUP_MESSAGE_METHOD, SOUP_METHOD_GET,
1132                                       NULL);
1133                 }
1134                 soup_message_set_request (msg, NULL,
1135                                           SOUP_MEMORY_STATIC, NULL, 0);
1136                 soup_message_headers_set_encoding (msg->request_headers,
1137                                                    SOUP_ENCODING_NONE);
1138         } else if (msg->status_code == SOUP_STATUS_MOVED_PERMANENTLY ||
1139                    msg->status_code == SOUP_STATUS_TEMPORARY_REDIRECT ||
1140                    msg->status_code == SOUP_STATUS_FOUND) {
1141                 /* Don't redirect non-safe methods */
1142                 if (!SOUP_METHOD_IS_SAFE (msg->method))
1143                         return;
1144         } else {
1145                 /* Three possibilities:
1146                  *
1147                  *   1) This was a non-3xx response that happened to
1148                  *      have a "Location" header
1149                  *   2) It's a non-redirecty 3xx response (300, 304,
1150                  *      305, 306)
1151                  *   3) It's some newly-defined 3xx response (308+)
1152                  *
1153                  * We ignore all of these cases. In the first two,
1154                  * redirecting would be explicitly wrong, and in the
1155                  * last case, we have no clue if the 3xx response is
1156                  * supposed to be redirecty or non-redirecty. Plus,
1157                  * 2616 says unrecognized status codes should be
1158                  * treated as the equivalent to the x00 code, and we
1159                  * don't redirect on 300, so therefore we shouldn't
1160                  * redirect on 308+ either.
1161                  */
1162                 return;
1163         }
1164
1165         /* Location is supposed to be an absolute URI, but some sites
1166          * are lame, so we use soup_uri_new_with_base().
1167          */
1168         new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
1169         if (!new_uri || !new_uri->host) {
1170                 if (new_uri)
1171                         soup_uri_free (new_uri);
1172                 soup_message_set_status_full (msg,
1173                                               SOUP_STATUS_MALFORMED,
1174                                               "Invalid Redirect URL");
1175                 return;
1176         }
1177
1178         soup_message_set_uri (msg, new_uri);
1179         soup_uri_free (new_uri);
1180
1181         soup_session_requeue_message (session, msg);
1182 }
1183
1184 void
1185 soup_session_send_queue_item (SoupSession *session,
1186                               SoupMessageQueueItem *item,
1187                               SoupMessageCompletionFn completion_cb)
1188 {
1189         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1190         const char *conn_header;
1191
1192         if (priv->user_agent) {
1193                 soup_message_headers_replace (item->msg->request_headers,
1194                                               "User-Agent", priv->user_agent);
1195         }
1196
1197         if (priv->accept_language &&
1198             !soup_message_headers_get_list (item->msg->request_headers,
1199                                             "Accept-Language")) {
1200                 soup_message_headers_append (item->msg->request_headers,
1201                                              "Accept-Language",
1202                                              priv->accept_language);
1203         }
1204
1205         /* Force keep alive connections for HTTP 1.0. Performance will
1206          * improve when issuing multiple requests to the same host in
1207          * a short period of time, as we wouldn't need to establish
1208          * new connections. Keep alive is implicit for HTTP 1.1.
1209          */
1210         conn_header = soup_message_headers_get_list (item->msg->request_headers, "Connection");
1211         if (!conn_header ||
1212             (!soup_header_contains (conn_header, "Keep-Alive") &&
1213              !soup_header_contains (conn_header, "close")))
1214                 soup_message_headers_append (item->msg->request_headers,
1215                                              "Connection", "Keep-Alive");
1216
1217         g_signal_emit (session, signals[REQUEST_STARTED], 0,
1218                        item->msg, soup_connection_get_socket (item->conn));
1219         soup_connection_send_request (item->conn, item, completion_cb, item);
1220 }
1221
1222 gboolean
1223 soup_session_cleanup_connections (SoupSession *session,
1224                                   gboolean     prune_idle)
1225 {
1226         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1227         GSList *conns = NULL, *c;
1228         GHashTableIter iter;
1229         gpointer conn, host;
1230         SoupConnectionState state;
1231
1232         g_mutex_lock (priv->host_lock);
1233         g_hash_table_iter_init (&iter, priv->conns);
1234         while (g_hash_table_iter_next (&iter, &conn, &host)) {
1235                 state = soup_connection_get_state (conn);
1236                 if (state == SOUP_CONNECTION_REMOTE_DISCONNECTED ||
1237                     (prune_idle && state == SOUP_CONNECTION_IDLE))
1238                         conns = g_slist_prepend (conns, g_object_ref (conn));
1239         }
1240         g_mutex_unlock (priv->host_lock);
1241
1242         if (!conns)
1243                 return FALSE;
1244
1245         for (c = conns; c; c = c->next) {
1246                 conn = c->data;
1247                 soup_connection_disconnect (conn);
1248                 g_object_unref (conn);
1249         }
1250         g_slist_free (conns);
1251
1252         return TRUE;
1253 }
1254
1255 static void
1256 connection_disconnected (SoupConnection *conn, gpointer user_data)
1257 {
1258         SoupSession *session = user_data;
1259         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1260         SoupSessionHost *host;
1261
1262         g_mutex_lock (priv->host_lock);
1263
1264         host = g_hash_table_lookup (priv->conns, conn);
1265         if (host) {
1266                 g_hash_table_remove (priv->conns, conn);
1267                 host->connections = g_slist_remove (host->connections, conn);
1268                 host->num_conns--;
1269         }
1270
1271         g_signal_handlers_disconnect_by_func (conn, connection_disconnected, session);
1272         priv->num_conns--;
1273
1274         g_mutex_unlock (priv->host_lock);
1275         g_object_unref (conn);
1276 }
1277
1278 SoupMessageQueueItem *
1279 soup_session_make_connect_message (SoupSession    *session,
1280                                    SoupConnection *conn)
1281 {
1282         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1283         SoupAddress *server_addr = soup_connection_get_tunnel_addr (conn);
1284         SoupURI *uri;
1285         SoupMessage *msg;
1286         SoupMessageQueueItem *item;
1287
1288         uri = soup_uri_new (NULL);
1289         soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS);
1290         soup_uri_set_host (uri, soup_address_get_name (server_addr));
1291         soup_uri_set_port (uri, soup_address_get_port (server_addr));
1292         soup_uri_set_path (uri, "");
1293         msg = soup_message_new_from_uri (SOUP_METHOD_CONNECT, uri);
1294         soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
1295         soup_uri_free (uri);
1296
1297         /* Call the base implementation of soup_session_queue_message
1298          * directly, to add msg to the SoupMessageQueue and cause all
1299          * the right signals to be emitted.
1300          */
1301         queue_message (session, msg, NULL, NULL);
1302         item = soup_message_queue_lookup (priv->queue, msg);
1303         item->conn = g_object_ref (conn);
1304         g_object_unref (msg);
1305
1306         g_signal_emit (session, signals[TUNNELING], 0, conn);
1307         return item;
1308 }
1309
1310 gboolean
1311 soup_session_get_connection (SoupSession *session,
1312                              SoupMessageQueueItem *item,
1313                              gboolean *try_pruning)
1314 {
1315         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1316         SoupConnection *conn;
1317         SoupSessionHost *host;
1318         SoupAddress *remote_addr, *tunnel_addr;
1319         SoupSSLCredentials *ssl_creds;
1320         GSList *conns;
1321         int num_pending = 0;
1322         SoupURI *uri;
1323
1324         if (item->conn) {
1325                 g_return_val_if_fail (soup_connection_get_state (item->conn) != SOUP_CONNECTION_DISCONNECTED, FALSE);
1326                 return TRUE;
1327         }
1328
1329         g_mutex_lock (priv->host_lock);
1330
1331         host = get_host_for_message (session, item->msg);
1332         for (conns = host->connections; conns; conns = conns->next) {
1333                 if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_IDLE) {
1334                         soup_connection_set_state (conns->data, SOUP_CONNECTION_IN_USE);
1335                         g_mutex_unlock (priv->host_lock);
1336                         item->conn = g_object_ref (conns->data);
1337                         return TRUE;
1338                 } else if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_CONNECTING)
1339                         num_pending++;
1340         }
1341
1342         /* Limit the number of pending connections; num_messages / 2
1343          * is somewhat arbitrary...
1344          */
1345         if (num_pending > host->num_messages / 2) {
1346                 g_mutex_unlock (priv->host_lock);
1347                 return FALSE;
1348         }
1349
1350         if (host->num_conns >= priv->max_conns_per_host) {
1351                 g_mutex_unlock (priv->host_lock);
1352                 return FALSE;
1353         }
1354
1355         if (priv->num_conns >= priv->max_conns) {
1356                 *try_pruning = TRUE;
1357                 g_mutex_unlock (priv->host_lock);
1358                 return FALSE;
1359         }
1360
1361         if (item->proxy_addr) {
1362                 remote_addr = item->proxy_addr;
1363                 tunnel_addr = NULL;
1364         } else {
1365                 remote_addr = host->addr;
1366                 tunnel_addr = NULL;
1367         }
1368
1369         uri = soup_message_get_uri (item->msg);
1370         if (uri->scheme == SOUP_URI_SCHEME_HTTPS) {
1371                 if (!priv->ssl_creds)
1372                         priv->ssl_creds = soup_ssl_get_client_credentials (priv->ssl_ca_file);
1373                 ssl_creds = priv->ssl_creds;
1374
1375                 if (item->proxy_addr)
1376                         tunnel_addr = host->addr;
1377         } else
1378                 ssl_creds = NULL;
1379
1380         conn = soup_connection_new (
1381                 SOUP_CONNECTION_REMOTE_ADDRESS, remote_addr,
1382                 SOUP_CONNECTION_TUNNEL_ADDRESS, tunnel_addr,
1383                 SOUP_CONNECTION_PROXY_URI, item->proxy_uri,
1384                 SOUP_CONNECTION_SSL_CREDENTIALS, ssl_creds,
1385                 SOUP_CONNECTION_SSL_STRICT, priv->ssl_strict,
1386                 SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context,
1387                 SOUP_CONNECTION_TIMEOUT, priv->io_timeout,
1388                 SOUP_CONNECTION_IDLE_TIMEOUT, priv->idle_timeout,
1389                 NULL);
1390         g_signal_connect (conn, "disconnected",
1391                           G_CALLBACK (connection_disconnected),
1392                           session);
1393
1394         g_signal_emit (session, signals[CONNECTION_CREATED], 0, conn);
1395
1396         g_hash_table_insert (priv->conns, conn, host);
1397
1398         priv->num_conns++;
1399         host->num_conns++;
1400         host->connections = g_slist_prepend (host->connections, conn);
1401
1402         g_mutex_unlock (priv->host_lock);
1403         item->conn = g_object_ref (conn);
1404         return TRUE;
1405 }
1406
1407 SoupMessageQueue *
1408 soup_session_get_queue (SoupSession *session)
1409 {
1410         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1411
1412         return priv->queue;
1413 }
1414
1415 void
1416 soup_session_unqueue_item (SoupSession          *session,
1417                            SoupMessageQueueItem *item)
1418 {
1419         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1420         SoupSessionHost *host;
1421
1422         if (item->conn) {
1423                 g_object_unref (item->conn);
1424                 item->conn = NULL;
1425         }
1426
1427         if (item->state != SOUP_MESSAGE_FINISHED) {
1428                 g_warning ("finished an item with state %d", item->state);
1429                 return;
1430         }
1431
1432         soup_message_queue_remove (priv->queue, item);
1433
1434         g_mutex_lock (priv->host_lock);
1435         host = get_host_for_message (session, item->msg);
1436         host->num_messages--;
1437         g_mutex_unlock (priv->host_lock);
1438
1439         /* g_signal_handlers_disconnect_by_func doesn't work if you
1440          * have a metamarshal, meaning it doesn't work with
1441          * soup_message_add_header_handler()
1442          */
1443         g_signal_handlers_disconnect_matched (item->msg, G_SIGNAL_MATCH_DATA,
1444                                               0, 0, NULL, NULL, item);
1445         g_signal_emit (session, signals[REQUEST_UNQUEUED], 0, item->msg);
1446         soup_message_queue_item_unref (item);
1447 }
1448
1449 void
1450 soup_session_set_item_status (SoupSession          *session,
1451                               SoupMessageQueueItem *item,
1452                               guint                 status_code)
1453 {
1454         SoupURI *uri;
1455         char *msg;
1456
1457         switch (status_code) {
1458         case SOUP_STATUS_CANT_RESOLVE:
1459         case SOUP_STATUS_CANT_CONNECT:
1460                 uri = soup_message_get_uri (item->msg);
1461                 msg = g_strdup_printf ("%s (%s)",
1462                                        soup_status_get_phrase (status_code),
1463                                        uri->host);
1464                 soup_message_set_status_full (item->msg, status_code, msg);
1465                 g_free (msg);
1466                 break;
1467
1468         case SOUP_STATUS_CANT_RESOLVE_PROXY:
1469         case SOUP_STATUS_CANT_CONNECT_PROXY:
1470                 if (item->proxy_uri && item->proxy_uri->host) {
1471                         msg = g_strdup_printf ("%s (%s)",
1472                                                soup_status_get_phrase (status_code),
1473                                                item->proxy_uri->host);
1474                         soup_message_set_status_full (item->msg, status_code, msg);
1475                         g_free (msg);
1476                         break;
1477                 }
1478                 soup_message_set_status (item->msg, status_code);
1479                 break;
1480
1481         case SOUP_STATUS_SSL_FAILED:
1482                 if (!g_tls_backend_supports_tls (g_tls_backend_get_default ())) {
1483                         soup_message_set_status_full (item->msg, status_code,
1484                                                       "TLS/SSL support not available; install glib-networking");
1485                 } else
1486                         soup_message_set_status (item->msg, status_code);
1487                 break;
1488
1489         default:
1490                 soup_message_set_status (item->msg, status_code);
1491                 break;
1492         }
1493 }
1494
1495 static void
1496 queue_message (SoupSession *session, SoupMessage *msg,
1497                SoupSessionCallback callback, gpointer user_data)
1498 {
1499         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1500         SoupMessageQueueItem *item;
1501         SoupSessionHost *host;
1502
1503         item = soup_message_queue_append (priv->queue, msg, callback, user_data);
1504
1505         g_mutex_lock (priv->host_lock);
1506         host = get_host_for_message (session, item->msg);
1507         host->num_messages++;
1508         g_mutex_unlock (priv->host_lock);
1509
1510         if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_NO_REDIRECT)) {
1511                 soup_message_add_header_handler (
1512                         msg, "got_body", "Location",
1513                         G_CALLBACK (redirect_handler), item);
1514         }
1515
1516         g_signal_emit (session, signals[REQUEST_QUEUED], 0, msg);
1517 }
1518
1519 /**
1520  * SoupSessionCallback:
1521  * @session: the session
1522  * @msg: the message that has finished
1523  * @user_data: the data passed to soup_session_queue_message
1524  *
1525  * Prototype for the callback passed to soup_session_queue_message(),
1526  * qv.
1527  **/
1528
1529 /**
1530  * soup_session_queue_message:
1531  * @session: a #SoupSession
1532  * @msg: (transfer full): the message to queue
1533  * @callback: (allow-none) (scope async): a #SoupSessionCallback which will
1534  * be called after the message completes or when an unrecoverable error occurs.
1535  * @user_data: (allow-none): a pointer passed to @callback.
1536  * 
1537  * Queues the message @msg for sending. All messages are processed
1538  * while the glib main loop runs. If @msg has been processed before,
1539  * any resources related to the time it was last sent are freed.
1540  *
1541  * Upon message completion, the callback specified in @callback will
1542  * be invoked (in the thread associated with @session's async
1543  * context). If after returning from this callback the message has not
1544  * been requeued, @msg will be unreffed.
1545  */
1546 void
1547 soup_session_queue_message (SoupSession *session, SoupMessage *msg,
1548                             SoupSessionCallback callback, gpointer user_data)
1549 {
1550         g_return_if_fail (SOUP_IS_SESSION (session));
1551         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1552
1553         SOUP_SESSION_GET_CLASS (session)->queue_message (session, msg,
1554                                                          callback, user_data);
1555 }
1556
1557 static void
1558 requeue_message (SoupSession *session, SoupMessage *msg)
1559 {
1560         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1561         SoupMessageQueueItem *item;
1562
1563         item = soup_message_queue_lookup (priv->queue, msg);
1564         g_return_if_fail (item != NULL);
1565         item->state = SOUP_MESSAGE_RESTARTING;
1566         soup_message_queue_item_unref (item);
1567 }
1568
1569 /**
1570  * soup_session_requeue_message:
1571  * @session: a #SoupSession
1572  * @msg: the message to requeue
1573  *
1574  * This causes @msg to be placed back on the queue to be attempted
1575  * again.
1576  **/
1577 void
1578 soup_session_requeue_message (SoupSession *session, SoupMessage *msg)
1579 {
1580         g_return_if_fail (SOUP_IS_SESSION (session));
1581         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1582
1583         SOUP_SESSION_GET_CLASS (session)->requeue_message (session, msg);
1584 }
1585
1586
1587 /**
1588  * soup_session_send_message:
1589  * @session: a #SoupSession
1590  * @msg: the message to send
1591  * 
1592  * Synchronously send @msg. This call will not return until the
1593  * transfer is finished successfully or there is an unrecoverable
1594  * error.
1595  *
1596  * @msg is not freed upon return.
1597  *
1598  * Return value: the HTTP status code of the response
1599  */
1600 guint
1601 soup_session_send_message (SoupSession *session, SoupMessage *msg)
1602 {
1603         g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
1604         g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_STATUS_MALFORMED);
1605
1606         return SOUP_SESSION_GET_CLASS (session)->send_message (session, msg);
1607 }
1608
1609
1610 /**
1611  * soup_session_pause_message:
1612  * @session: a #SoupSession
1613  * @msg: a #SoupMessage currently running on @session
1614  *
1615  * Pauses HTTP I/O on @msg. Call soup_session_unpause_message() to
1616  * resume I/O.
1617  **/
1618 void
1619 soup_session_pause_message (SoupSession *session,
1620                             SoupMessage *msg)
1621 {
1622         g_return_if_fail (SOUP_IS_SESSION (session));
1623         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1624
1625         soup_message_io_pause (msg);
1626 }
1627
1628 /**
1629  * soup_session_unpause_message:
1630  * @session: a #SoupSession
1631  * @msg: a #SoupMessage currently running on @session
1632  *
1633  * Resumes HTTP I/O on @msg. Use this to resume after calling
1634  * soup_session_pause_message().
1635  *
1636  * If @msg is being sent via blocking I/O, this will resume reading or
1637  * writing immediately. If @msg is using non-blocking I/O, then
1638  * reading or writing won't resume until you return to the main loop.
1639  **/
1640 void
1641 soup_session_unpause_message (SoupSession *session,
1642                               SoupMessage *msg)
1643 {
1644         g_return_if_fail (SOUP_IS_SESSION (session));
1645         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1646
1647         soup_message_io_unpause (msg);
1648 }
1649
1650
1651 static void
1652 cancel_message (SoupSession *session, SoupMessage *msg, guint status_code)
1653 {
1654         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1655         SoupMessageQueueItem *item;
1656
1657         item = soup_message_queue_lookup (priv->queue, msg);
1658         g_return_if_fail (item != NULL);
1659
1660         soup_message_set_status (msg, status_code);
1661         g_cancellable_cancel (item->cancellable);
1662
1663         soup_message_queue_item_unref (item);
1664 }
1665
1666 /**
1667  * soup_session_cancel_message:
1668  * @session: a #SoupSession
1669  * @msg: the message to cancel
1670  * @status_code: status code to set on @msg (generally
1671  * %SOUP_STATUS_CANCELLED)
1672  *
1673  * Causes @session to immediately finish processing @msg (regardless
1674  * of its current state) with a final status_code of @status_code. You
1675  * may call this at any time after handing @msg off to @session; if
1676  * @session has started sending the request but has not yet received
1677  * the complete response, then it will close the request's connection.
1678  * Note that with non-idempotent requests (eg, %POST, %PUT, %DELETE)
1679  * it is possible that you might cancel the request after the server
1680  * acts on it, but before it returns a response, leaving the remote
1681  * resource in an unknown state.
1682  *
1683  * If the message is cancelled while its response body is being read,
1684  * then the response body in @msg will be left partially-filled-in.
1685  * The response headers, on the other hand, will always be either
1686  * empty or complete.
1687  *
1688  * For messages queued with soup_session_queue_message() (and
1689  * cancelled from the same thread), the callback will be invoked
1690  * before soup_session_cancel_message() returns.
1691  **/
1692 void
1693 soup_session_cancel_message (SoupSession *session, SoupMessage *msg,
1694                              guint status_code)
1695 {
1696         SoupSessionPrivate *priv;
1697         SoupMessageQueueItem *item;
1698
1699         g_return_if_fail (SOUP_IS_SESSION (session));
1700         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1701
1702         priv = SOUP_SESSION_GET_PRIVATE (session);
1703         item = soup_message_queue_lookup (priv->queue, msg);
1704         /* If the message is already ending, don't do anything */
1705         if (!item)
1706                 return;
1707         if (item->state == SOUP_MESSAGE_FINISHED) {
1708                 soup_message_queue_item_unref (item);
1709                 return;
1710         }
1711
1712         SOUP_SESSION_GET_CLASS (session)->cancel_message (session, msg, status_code);
1713         soup_message_queue_item_unref (item);
1714 }
1715
1716 static void
1717 gather_conns (gpointer key, gpointer host, gpointer data)
1718 {
1719         SoupConnection *conn = key;
1720         GSList **conns = data;
1721
1722         *conns = g_slist_prepend (*conns, g_object_ref (conn));
1723 }
1724
1725 static void
1726 flush_queue (SoupSession *session)
1727 {
1728         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1729         SoupMessageQueueItem *item;
1730
1731         for (item = soup_message_queue_first (priv->queue);
1732              item;
1733              item = soup_message_queue_next (priv->queue, item)) {
1734                 soup_session_cancel_message (session, item->msg,
1735                                              SOUP_STATUS_CANCELLED);
1736         }
1737 }
1738
1739 /**
1740  * soup_session_abort:
1741  * @session: the session
1742  *
1743  * Cancels all pending requests in @session.
1744  **/
1745 void
1746 soup_session_abort (SoupSession *session)
1747 {
1748         SoupSessionPrivate *priv;
1749         GSList *conns, *c;
1750
1751         g_return_if_fail (SOUP_IS_SESSION (session));
1752         priv = SOUP_SESSION_GET_PRIVATE (session);
1753
1754         SOUP_SESSION_GET_CLASS (session)->flush_queue (session);
1755
1756         /* Close all connections */
1757         g_mutex_lock (priv->host_lock);
1758         conns = NULL;
1759         g_hash_table_foreach (priv->conns, gather_conns, &conns);
1760
1761         g_mutex_unlock (priv->host_lock);
1762         for (c = conns; c; c = c->next) {
1763                 soup_connection_disconnect (c->data);
1764                 g_object_unref (c->data);
1765         }
1766
1767         g_slist_free (conns);
1768 }
1769
1770 /**
1771 * soup_session_prepare_for_uri:
1772 * @session: a #SoupSession
1773 * @uri: a #SoupURI which may be required
1774 *
1775 * Tells @session that @uri may be requested shortly, and so the
1776 * session can try to prepare (resolving the domain name, obtaining
1777 * proxy address, etc.) in order to work more quickly once the URI is
1778 * actually requested.
1779 *
1780 * This method acts asynchronously, in @session's %async_context.
1781 * If you are using #SoupSessionSync and do not have a main loop running,
1782 * then you can't use this method.
1783 *
1784 * Since: 2.30
1785 **/
1786 void
1787 soup_session_prepare_for_uri (SoupSession *session, SoupURI *uri)
1788 {
1789         SoupSessionPrivate *priv;
1790         SoupSessionHost *host;
1791         SoupAddress *addr;
1792
1793         g_return_if_fail (SOUP_IS_SESSION (session));
1794         g_return_if_fail (uri != NULL);
1795
1796         if (!uri->host)
1797                 return;
1798
1799         priv = SOUP_SESSION_GET_PRIVATE (session);
1800
1801         g_mutex_lock (priv->host_lock);
1802         host = get_host_for_uri (session, uri);
1803         addr = g_object_ref (host->addr);
1804         g_mutex_unlock (priv->host_lock);
1805
1806         soup_address_resolve_async (addr, priv->async_context,
1807                                     NULL, NULL, NULL);
1808 }
1809
1810 /**
1811  * soup_session_add_feature:
1812  * @session: a #SoupSession
1813  * @feature: an object that implements #SoupSessionFeature
1814  *
1815  * Adds @feature's functionality to @session. You can also add a
1816  * feature to the session at construct time by using the
1817  * %SOUP_SESSION_ADD_FEATURE property.
1818  *
1819  * Since: 2.24
1820  **/
1821 void
1822 soup_session_add_feature (SoupSession *session, SoupSessionFeature *feature)
1823 {
1824         SoupSessionPrivate *priv;
1825
1826         g_return_if_fail (SOUP_IS_SESSION (session));
1827         g_return_if_fail (SOUP_IS_SESSION_FEATURE (feature));
1828
1829         priv = SOUP_SESSION_GET_PRIVATE (session);
1830         priv->features = g_slist_prepend (priv->features, g_object_ref (feature));
1831         g_hash_table_remove_all (priv->features_cache);
1832         soup_session_feature_attach (feature, session);
1833 }
1834
1835 /**
1836  * soup_session_add_feature_by_type:
1837  * @session: a #SoupSession
1838  * @feature_type: a #GType
1839  *
1840  * If @feature_type is the type of a class that implements
1841  * #SoupSessionFeature, this creates a new feature of that type and
1842  * adds it to @session as with soup_session_add_feature(). You can use
1843  * this when you don't need to customize the new feature in any way.
1844  *
1845  * If @feature_type is not a #SoupSessionFeature type, this gives
1846  * each existing feature on @session the chance to accept @feature_type
1847  * as a "subfeature". This can be used to add new #SoupAuth types,
1848  * for instance.
1849  *
1850  * You can also add a feature to the session at construct time by
1851  * using the %SOUP_SESSION_ADD_FEATURE_BY_TYPE property.
1852  *
1853  * Since: 2.24
1854  **/
1855 void
1856 soup_session_add_feature_by_type (SoupSession *session, GType feature_type)
1857 {
1858         g_return_if_fail (SOUP_IS_SESSION (session));
1859
1860         if (g_type_is_a (feature_type, SOUP_TYPE_SESSION_FEATURE)) {
1861                 SoupSessionFeature *feature;
1862
1863                 feature = g_object_new (feature_type, NULL);
1864                 soup_session_add_feature (session, feature);
1865                 g_object_unref (feature);
1866         } else {
1867                 SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1868                 GSList *f;
1869
1870                 for (f = priv->features; f; f = f->next) {
1871                         if (soup_session_feature_add_feature (f->data, feature_type))
1872                                 return;
1873                 }
1874                 g_warning ("No feature manager for feature of type '%s'", g_type_name (feature_type));
1875         }
1876 }
1877
1878 /**
1879  * soup_session_remove_feature:
1880  * @session: a #SoupSession
1881  * @feature: a feature that has previously been added to @session
1882  *
1883  * Removes @feature's functionality from @session.
1884  *
1885  * Since: 2.24
1886  **/
1887 void
1888 soup_session_remove_feature (SoupSession *session, SoupSessionFeature *feature)
1889 {
1890         SoupSessionPrivate *priv;
1891
1892         g_return_if_fail (SOUP_IS_SESSION (session));
1893
1894         priv = SOUP_SESSION_GET_PRIVATE (session);
1895         if (g_slist_find (priv->features, feature)) {
1896                 priv->features = g_slist_remove (priv->features, feature);
1897                 g_hash_table_remove_all (priv->features_cache);
1898                 soup_session_feature_detach (feature, session);
1899                 g_object_unref (feature);
1900         }
1901 }
1902
1903 /**
1904  * soup_session_remove_feature_by_type:
1905  * @session: a #SoupSession
1906  * @feature_type: a #GType
1907  *
1908  * Removes all features of type @feature_type (or any subclass of
1909  * @feature_type) from @session. You can also remove standard features
1910  * from the session at construct time by using the
1911  * %SOUP_SESSION_REMOVE_FEATURE_BY_TYPE property.
1912  *
1913  * Since: 2.24
1914  **/
1915 void
1916 soup_session_remove_feature_by_type (SoupSession *session, GType feature_type)
1917 {
1918         SoupSessionPrivate *priv;
1919         GSList *f;
1920
1921         g_return_if_fail (SOUP_IS_SESSION (session));
1922
1923         priv = SOUP_SESSION_GET_PRIVATE (session);
1924
1925         if (g_type_is_a (feature_type, SOUP_TYPE_SESSION_FEATURE)) {
1926         restart:
1927                 for (f = priv->features; f; f = f->next) {
1928                         if (G_TYPE_CHECK_INSTANCE_TYPE (f->data, feature_type)) {
1929                                 soup_session_remove_feature (session, f->data);
1930                                 goto restart;
1931                         }
1932                 }
1933         } else {
1934                 for (f = priv->features; f; f = f->next) {
1935                         if (soup_session_feature_remove_feature (f->data, feature_type))
1936                                 return;
1937                 }
1938                 g_warning ("No feature manager for feature of type '%s'", g_type_name (feature_type));
1939         }
1940 }
1941
1942 /**
1943  * soup_session_get_features:
1944  * @session: a #SoupSession
1945  * @feature_type: the #GType of the class of features to get
1946  *
1947  * Generates a list of @session's features of type @feature_type. (If
1948  * you want to see all features, you can pass %G_TYPE_SESSION_FEATURE
1949  * for @feature_type.)
1950  *
1951  * Return value: (transfer container): a list of features. You must
1952  * free the list, but not its contents
1953  *
1954  * Since: 2.26
1955  **/
1956 GSList *
1957 soup_session_get_features (SoupSession *session, GType feature_type)
1958 {
1959         SoupSessionPrivate *priv;
1960         GSList *f, *ret;
1961
1962         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
1963
1964         priv = SOUP_SESSION_GET_PRIVATE (session);
1965         for (f = priv->features, ret = NULL; f; f = f->next) {
1966                 if (G_TYPE_CHECK_INSTANCE_TYPE (f->data, feature_type))
1967                         ret = g_slist_prepend (ret, f->data);
1968         }
1969         return g_slist_reverse (ret);
1970 }
1971
1972 /**
1973  * soup_session_get_feature:
1974  * @session: a #SoupSession
1975  * @feature_type: the #GType of the feature to get
1976  *
1977  * Gets the first feature in @session of type @feature_type. For
1978  * features where there may be more than one feature of a given type,
1979  * use soup_session_get_features().
1980  *
1981  * Return value: (transfer none): a #SoupSessionFeature, or %NULL. The
1982  * feature is owned by @session.
1983  *
1984  * Since: 2.26
1985  **/
1986 SoupSessionFeature *
1987 soup_session_get_feature (SoupSession *session, GType feature_type)
1988 {
1989         SoupSessionPrivate *priv;
1990         SoupSessionFeature *feature;
1991         GSList *f;
1992
1993         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
1994
1995         priv = SOUP_SESSION_GET_PRIVATE (session);
1996
1997         feature = g_hash_table_lookup (priv->features_cache,
1998                                        GSIZE_TO_POINTER (feature_type));
1999         if (feature)
2000                 return feature;
2001
2002         for (f = priv->features; f; f = f->next) {
2003                 feature = f->data;
2004                 if (G_TYPE_CHECK_INSTANCE_TYPE (feature, feature_type)) {
2005                         g_hash_table_insert (priv->features_cache,
2006                                              GSIZE_TO_POINTER (feature_type),
2007                                              feature);
2008                         return feature;
2009                 }
2010         }
2011         return NULL;
2012 }
2013
2014 /**
2015  * soup_session_get_feature_for_message:
2016  * @session: a #SoupSession
2017  * @feature_type: the #GType of the feature to get
2018  * @msg: a #SoupMessage
2019  *
2020  * Gets the first feature in @session of type @feature_type, provided
2021  * that it is not disabled for @msg. As with
2022  * soup_session_get_feature(), this should only be used for features
2023  * where @feature_type is only expected to match a single feature. In
2024  * particular, if there are two matching features, and the first is
2025  * disabled on @msg, and the second is not, then this will return
2026  * %NULL, not the second feature.
2027  *
2028  * Return value: (transfer none): a #SoupSessionFeature, or %NULL. The
2029  * feature is owned by @session.
2030  *
2031  * Since: 2.28
2032  **/
2033 SoupSessionFeature *
2034 soup_session_get_feature_for_message (SoupSession *session, GType feature_type,
2035                                       SoupMessage *msg)
2036 {
2037         SoupSessionFeature *feature;
2038
2039         feature = soup_session_get_feature (session, feature_type);
2040         if (feature && soup_message_disables_feature (msg, feature))
2041                 return NULL;
2042         return feature;
2043 }