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