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