Put the hostname into the reason_phrase with network errors
[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                 double qvalue = quality / 100.0;
761                 return g_strdup_printf ("%s;q=%.2g", str, qvalue);
762         } else {
763                 /* Just dup the string in this case */
764                 return g_strdup (str);
765         }
766 }
767
768 /* Returns a RFC2616 compliant languages list from system locales */
769 static gchar *
770 accept_languages_from_system (void)
771 {
772         const char * const * lang_names;
773         GPtrArray *langs = NULL;
774         char *lang, **langs_array, *langs_str;
775         int delta;
776         int i;
777
778         lang_names = g_get_language_names ();
779         g_return_val_if_fail (lang_names != NULL, NULL);
780
781         /* Build the array of languages */
782         langs = g_ptr_array_new ();
783         for (i = 0; lang_names[i] != NULL; i++) {
784                 lang = posix_lang_to_rfc2616 (lang_names[i]);
785                 if (lang)
786                         g_ptr_array_add (langs, lang);
787         }
788
789         /* Add quality values */
790         if (langs->len < 10)
791                 delta = 10;
792         else if (langs->len < 20)
793                 delta = 5;
794         else
795                 delta = 1;
796
797         for (i = 0; i < langs->len; i++) {
798                 lang = langs->pdata[i];
799                 langs->pdata[i] = add_quality_value (lang, 100 - i * delta);
800                 g_free (lang);
801         }
802
803         /* Fallback: add "en" if list is empty */
804         if (langs->len == 0)
805                 g_ptr_array_add (langs, g_strdup ("en"));
806
807         g_ptr_array_add (langs, NULL);
808         langs_array = (char **)langs->pdata;
809         langs_str = g_strjoinv (", ", langs_array);
810
811         g_strfreev (langs_array);
812         g_ptr_array_free (langs, FALSE);
813
814         return langs_str;
815 }
816
817 static void
818 set_property (GObject *object, guint prop_id,
819               const GValue *value, GParamSpec *pspec)
820 {
821         SoupSession *session = SOUP_SESSION (object);
822         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
823         SoupURI *uri;
824         gboolean ca_file_changed = FALSE;
825         const char *new_ca_file, *user_agent;
826         SoupSessionFeature *feature;
827
828         switch (prop_id) {
829         case PROP_PROXY_URI:
830                 uri = g_value_get_boxed (value);
831
832                 if (uri) {
833                         soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER);
834                         feature = SOUP_SESSION_FEATURE (soup_proxy_resolver_static_new (uri));
835                         soup_session_add_feature (session, feature);
836                         g_object_unref (feature);
837                 } else
838                         soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER_STATIC);
839
840                 soup_session_abort (session);
841                 break;
842         case PROP_MAX_CONNS:
843                 priv->max_conns = g_value_get_int (value);
844                 break;
845         case PROP_MAX_CONNS_PER_HOST:
846                 priv->max_conns_per_host = g_value_get_int (value);
847                 break;
848         case PROP_USE_NTLM:
849                 feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER_NTLM);
850                 if (feature) {
851                         g_object_set_property (G_OBJECT (feature),
852                                                SOUP_AUTH_MANAGER_NTLM_USE_NTLM,
853                                                value);
854                 } else
855                         g_warning ("Trying to set use-ntlm on session with no auth-manager");
856                 break;
857         case PROP_SSL_CA_FILE:
858                 new_ca_file = g_value_get_string (value);
859
860                 if (!safe_str_equal (priv->ssl_ca_file, new_ca_file))
861                         ca_file_changed = TRUE;
862
863                 g_free (priv->ssl_ca_file);
864                 priv->ssl_ca_file = g_strdup (new_ca_file);
865
866                 if (ca_file_changed && priv->ssl_creds) {
867                         soup_ssl_free_client_credentials (priv->ssl_creds);
868                         priv->ssl_creds = NULL;
869                 }
870
871                 break;
872         case PROP_SSL_STRICT:
873                 priv->ssl_strict = g_value_get_boolean (value);
874                 break;
875         case PROP_ASYNC_CONTEXT:
876                 priv->async_context = g_value_get_pointer (value);
877                 if (priv->async_context)
878                         g_main_context_ref (priv->async_context);
879                 break;
880         case PROP_TIMEOUT:
881                 priv->io_timeout = g_value_get_uint (value);
882                 break;
883         case PROP_USER_AGENT:
884                 g_free (priv->user_agent);
885                 user_agent = g_value_get_string (value);
886                 if (!user_agent)
887                         priv->user_agent = NULL;
888                 else if (!*user_agent) {
889                         priv->user_agent =
890                                 g_strdup (SOUP_SESSION_USER_AGENT_BASE);
891                 } else if (g_str_has_suffix (user_agent, " ")) {
892                         priv->user_agent =
893                                 g_strdup_printf ("%s%s", user_agent,
894                                                  SOUP_SESSION_USER_AGENT_BASE);
895                 } else
896                         priv->user_agent = g_strdup (user_agent);
897                 break;
898         case PROP_ACCEPT_LANGUAGE:
899                 g_free (priv->accept_language);
900                 priv->accept_language = g_strdup (g_value_get_string (value));
901                 priv->accept_language_auto = FALSE;
902                 break;
903         case PROP_ACCEPT_LANGUAGE_AUTO:
904                 priv->accept_language_auto = g_value_get_boolean (value);
905                 if (priv->accept_language) {
906                         g_free (priv->accept_language);
907                         priv->accept_language = NULL;
908                 }
909
910                 /* Get languages from system if needed */
911                 if (priv->accept_language_auto)
912                         priv->accept_language = accept_languages_from_system ();
913                 break;
914         case PROP_IDLE_TIMEOUT:
915                 priv->idle_timeout = g_value_get_uint (value);
916                 break;
917         case PROP_ADD_FEATURE:
918                 soup_session_add_feature (session, g_value_get_object (value));
919                 break;
920         case PROP_ADD_FEATURE_BY_TYPE:
921                 soup_session_add_feature_by_type (session, g_value_get_gtype (value));
922                 break;
923         case PROP_REMOVE_FEATURE_BY_TYPE:
924                 soup_session_remove_feature_by_type (session, g_value_get_gtype (value));
925                 break;
926         default:
927                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
928                 break;
929         }
930 }
931
932 static void
933 get_property (GObject *object, guint prop_id,
934               GValue *value, GParamSpec *pspec)
935 {
936         SoupSession *session = SOUP_SESSION (object);
937         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
938         SoupSessionFeature *feature;
939
940         switch (prop_id) {
941         case PROP_PROXY_URI:
942                 feature = soup_session_get_feature (session, SOUP_TYPE_PROXY_RESOLVER_STATIC);
943                 if (feature) {
944                         g_object_get_property (G_OBJECT (feature),
945                                                SOUP_PROXY_RESOLVER_STATIC_PROXY_URI,
946                                                value);
947                 } else
948                         g_value_set_boxed (value, NULL);
949                 break;
950         case PROP_MAX_CONNS:
951                 g_value_set_int (value, priv->max_conns);
952                 break;
953         case PROP_MAX_CONNS_PER_HOST:
954                 g_value_set_int (value, priv->max_conns_per_host);
955                 break;
956         case PROP_USE_NTLM:
957                 feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER_NTLM);
958                 if (feature) {
959                         g_object_get_property (G_OBJECT (feature),
960                                                SOUP_AUTH_MANAGER_NTLM_USE_NTLM,
961                                                value);
962                 } else
963                         g_value_set_boolean (value, FALSE);
964                 break;
965         case PROP_SSL_CA_FILE:
966                 g_value_set_string (value, priv->ssl_ca_file);
967                 break;
968         case PROP_SSL_STRICT:
969                 g_value_set_boolean (value, priv->ssl_strict);
970                 break;
971         case PROP_ASYNC_CONTEXT:
972                 g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL);
973                 break;
974         case PROP_TIMEOUT:
975                 g_value_set_uint (value, priv->io_timeout);
976                 break;
977         case PROP_USER_AGENT:
978                 g_value_set_string (value, priv->user_agent);
979                 break;
980         case PROP_ACCEPT_LANGUAGE:
981                 g_value_set_string (value, priv->accept_language);
982                 break;
983         case PROP_ACCEPT_LANGUAGE_AUTO:
984                 g_value_set_boolean (value, priv->accept_language_auto);
985                 break;
986         case PROP_IDLE_TIMEOUT:
987                 g_value_set_uint (value, priv->idle_timeout);
988                 break;
989         default:
990                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
991                 break;
992         }
993 }
994
995
996 /**
997  * soup_session_get_async_context:
998  * @session: a #SoupSession
999  *
1000  * Gets @session's async_context. This does not add a ref to the
1001  * context, so you will need to ref it yourself if you want it to
1002  * outlive its session.
1003  *
1004  * Return value: (transfer none): @session's #GMainContext, which may
1005  * be %NULL
1006  **/
1007 GMainContext *
1008 soup_session_get_async_context (SoupSession *session)
1009 {
1010         SoupSessionPrivate *priv;
1011
1012         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
1013         priv = SOUP_SESSION_GET_PRIVATE (session);
1014
1015         return priv->async_context;
1016 }
1017
1018 /* Hosts */
1019
1020 static SoupSessionHost *
1021 soup_session_host_new (SoupSession *session, SoupURI *uri)
1022 {
1023         SoupSessionHost *host;
1024
1025         host = g_slice_new0 (SoupSessionHost);
1026         host->uri = soup_uri_copy_host (uri);
1027         host->addr = soup_address_new (host->uri->host, host->uri->port);
1028
1029         return host;
1030 }
1031
1032 /* Requires host_lock to be locked */
1033 static SoupSessionHost *
1034 get_host_for_uri (SoupSession *session, SoupURI *uri)
1035 {
1036         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1037         SoupSessionHost *host;
1038
1039         host = g_hash_table_lookup (priv->hosts, uri);
1040         if (host)
1041                 return host;
1042
1043         host = soup_session_host_new (session, uri);
1044         g_hash_table_insert (priv->hosts, host->uri, host);
1045
1046         return host;
1047 }
1048
1049 /* Note: get_host_for_message doesn't lock the host_lock. The caller
1050  * must do it itself if there's a chance the host doesn't already
1051  * exist.
1052  */
1053 static SoupSessionHost *
1054 get_host_for_message (SoupSession *session, SoupMessage *msg)
1055 {
1056         return get_host_for_uri (session, soup_message_get_uri (msg));
1057 }
1058
1059 static void
1060 free_host (SoupSessionHost *host)
1061 {
1062         while (host->connections) {
1063                 SoupConnection *conn = host->connections->data;
1064
1065                 host->connections = g_slist_remove (host->connections, conn);
1066                 soup_connection_disconnect (conn);
1067         }
1068
1069         soup_uri_free (host->uri);
1070         g_object_unref (host->addr);
1071         g_slice_free (SoupSessionHost, host);
1072 }       
1073
1074 static void
1075 auth_required (SoupSession *session, SoupMessage *msg,
1076                SoupAuth *auth, gboolean retrying)
1077 {
1078         g_signal_emit (session, signals[AUTHENTICATE], 0, msg, auth, retrying);
1079 }
1080
1081 static void
1082 auth_manager_authenticate (SoupAuthManager *manager, SoupMessage *msg,
1083                            SoupAuth *auth, gboolean retrying,
1084                            gpointer session)
1085 {
1086         SOUP_SESSION_GET_CLASS (session)->auth_required (
1087                 session, msg, auth, retrying);
1088 }
1089
1090 #define SOUP_METHOD_IS_SAFE(method) (method == SOUP_METHOD_GET || \
1091                                      method == SOUP_METHOD_HEAD || \
1092                                      method == SOUP_METHOD_OPTIONS || \
1093                                      method == SOUP_METHOD_PROPFIND)
1094
1095 static void
1096 redirect_handler (SoupMessage *msg, gpointer user_data)
1097 {
1098         SoupMessageQueueItem *item = user_data;
1099         SoupSession *session = item->session;
1100         const char *new_loc;
1101         SoupURI *new_uri;
1102
1103         new_loc = soup_message_headers_get_one (msg->response_headers,
1104                                                 "Location");
1105         g_return_if_fail (new_loc != NULL);
1106
1107         if (item->redirection_count >= SOUP_SESSION_MAX_REDIRECTION_COUNT) {
1108                 soup_session_cancel_message (session, msg, SOUP_STATUS_TOO_MANY_REDIRECTS);
1109                 return;
1110         }
1111         item->redirection_count++;
1112
1113         if (msg->status_code == SOUP_STATUS_SEE_OTHER ||
1114             (msg->status_code == SOUP_STATUS_FOUND &&
1115              !SOUP_METHOD_IS_SAFE (msg->method)) ||
1116             (msg->status_code == SOUP_STATUS_MOVED_PERMANENTLY &&
1117              msg->method == SOUP_METHOD_POST)) {
1118                 if (msg->method != SOUP_METHOD_HEAD) {
1119                         /* Redirect using a GET */
1120                         g_object_set (msg,
1121                                       SOUP_MESSAGE_METHOD, SOUP_METHOD_GET,
1122                                       NULL);
1123                 }
1124                 soup_message_set_request (msg, NULL,
1125                                           SOUP_MEMORY_STATIC, NULL, 0);
1126                 soup_message_headers_set_encoding (msg->request_headers,
1127                                                    SOUP_ENCODING_NONE);
1128         } else if (msg->status_code == SOUP_STATUS_MOVED_PERMANENTLY ||
1129                    msg->status_code == SOUP_STATUS_TEMPORARY_REDIRECT ||
1130                    msg->status_code == SOUP_STATUS_FOUND) {
1131                 /* Don't redirect non-safe methods */
1132                 if (!SOUP_METHOD_IS_SAFE (msg->method))
1133                         return;
1134         } else {
1135                 /* Three possibilities:
1136                  *
1137                  *   1) This was a non-3xx response that happened to
1138                  *      have a "Location" header
1139                  *   2) It's a non-redirecty 3xx response (300, 304,
1140                  *      305, 306)
1141                  *   3) It's some newly-defined 3xx response (308+)
1142                  *
1143                  * We ignore all of these cases. In the first two,
1144                  * redirecting would be explicitly wrong, and in the
1145                  * last case, we have no clue if the 3xx response is
1146                  * supposed to be redirecty or non-redirecty. Plus,
1147                  * 2616 says unrecognized status codes should be
1148                  * treated as the equivalent to the x00 code, and we
1149                  * don't redirect on 300, so therefore we shouldn't
1150                  * redirect on 308+ either.
1151                  */
1152                 return;
1153         }
1154
1155         /* Location is supposed to be an absolute URI, but some sites
1156          * are lame, so we use soup_uri_new_with_base().
1157          */
1158         new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
1159         if (!new_uri || !new_uri->host) {
1160                 if (new_uri)
1161                         soup_uri_free (new_uri);
1162                 soup_message_set_status_full (msg,
1163                                               SOUP_STATUS_MALFORMED,
1164                                               "Invalid Redirect URL");
1165                 return;
1166         }
1167
1168         soup_message_set_uri (msg, new_uri);
1169         soup_uri_free (new_uri);
1170
1171         soup_session_requeue_message (session, msg);
1172 }
1173
1174 void
1175 soup_session_send_queue_item (SoupSession *session,
1176                               SoupMessageQueueItem *item,
1177                               SoupMessageCompletionFn completion_cb)
1178 {
1179         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1180
1181         if (priv->user_agent) {
1182                 soup_message_headers_replace (item->msg->request_headers,
1183                                               "User-Agent", priv->user_agent);
1184         }
1185
1186         if (priv->accept_language &&
1187             !soup_message_headers_get_list (item->msg->request_headers,
1188                                             "Accept-Language")) {
1189                 soup_message_headers_append (item->msg->request_headers,
1190                                              "Accept-Language",
1191                                              priv->accept_language);
1192         }
1193
1194         g_signal_emit (session, signals[REQUEST_STARTED], 0,
1195                        item->msg, soup_connection_get_socket (item->conn));
1196         soup_connection_send_request (item->conn, item, completion_cb, item);
1197 }
1198
1199 gboolean
1200 soup_session_cleanup_connections (SoupSession *session,
1201                                   gboolean     prune_idle)
1202 {
1203         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1204         GSList *conns = NULL, *c;
1205         GHashTableIter iter;
1206         gpointer conn, host;
1207         SoupConnectionState state;
1208
1209         g_mutex_lock (priv->host_lock);
1210         g_hash_table_iter_init (&iter, priv->conns);
1211         while (g_hash_table_iter_next (&iter, &conn, &host)) {
1212                 state = soup_connection_get_state (conn);
1213                 if (state == SOUP_CONNECTION_REMOTE_DISCONNECTED ||
1214                     (prune_idle && state == SOUP_CONNECTION_IDLE))
1215                         conns = g_slist_prepend (conns, g_object_ref (conn));
1216         }
1217         g_mutex_unlock (priv->host_lock);
1218
1219         if (!conns)
1220                 return FALSE;
1221
1222         for (c = conns; c; c = c->next) {
1223                 conn = c->data;
1224                 soup_connection_disconnect (conn);
1225                 g_object_unref (conn);
1226         }
1227         g_slist_free (conns);
1228
1229         return TRUE;
1230 }
1231
1232 static void
1233 connection_disconnected (SoupConnection *conn, gpointer user_data)
1234 {
1235         SoupSession *session = user_data;
1236         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1237         SoupSessionHost *host;
1238
1239         g_mutex_lock (priv->host_lock);
1240
1241         host = g_hash_table_lookup (priv->conns, conn);
1242         if (host) {
1243                 g_hash_table_remove (priv->conns, conn);
1244                 host->connections = g_slist_remove (host->connections, conn);
1245                 host->num_conns--;
1246         }
1247
1248         g_signal_handlers_disconnect_by_func (conn, connection_disconnected, session);
1249         priv->num_conns--;
1250
1251         g_mutex_unlock (priv->host_lock);
1252         g_object_unref (conn);
1253 }
1254
1255 SoupMessageQueueItem *
1256 soup_session_make_connect_message (SoupSession    *session,
1257                                    SoupConnection *conn)
1258 {
1259         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1260         SoupAddress *server_addr = soup_connection_get_tunnel_addr (conn);
1261         SoupURI *uri;
1262         SoupMessage *msg;
1263         SoupMessageQueueItem *item;
1264
1265         uri = soup_uri_new (NULL);
1266         soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS);
1267         soup_uri_set_host (uri, soup_address_get_name (server_addr));
1268         soup_uri_set_port (uri, soup_address_get_port (server_addr));
1269         soup_uri_set_path (uri, "");
1270         msg = soup_message_new_from_uri (SOUP_METHOD_CONNECT, uri);
1271         soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
1272         soup_uri_free (uri);
1273
1274         /* Call the base implementation of soup_session_queue_message
1275          * directly, to add msg to the SoupMessageQueue and cause all
1276          * the right signals to be emitted.
1277          */
1278         queue_message (session, msg, NULL, NULL);
1279         item = soup_message_queue_lookup (priv->queue, msg);
1280         item->conn = g_object_ref (conn);
1281         g_object_unref (msg);
1282
1283         item->conn = g_object_ref (conn);
1284         g_signal_emit (session, signals[TUNNELING], 0, conn);
1285         return item;
1286 }
1287
1288 gboolean
1289 soup_session_get_connection (SoupSession *session,
1290                              SoupMessageQueueItem *item,
1291                              gboolean *try_pruning)
1292 {
1293         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1294         SoupConnection *conn;
1295         SoupSessionHost *host;
1296         SoupAddress *remote_addr, *tunnel_addr;
1297         SoupSSLCredentials *ssl_creds;
1298         GSList *conns;
1299         int num_pending = 0;
1300         SoupURI *uri;
1301
1302         if (item->conn) {
1303                 g_return_val_if_fail (soup_connection_get_state (item->conn) != SOUP_CONNECTION_DISCONNECTED, FALSE);
1304                 return TRUE;
1305         }
1306
1307         g_mutex_lock (priv->host_lock);
1308
1309         host = get_host_for_message (session, item->msg);
1310         for (conns = host->connections; conns; conns = conns->next) {
1311                 if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_IDLE) {
1312                         soup_connection_set_state (conns->data, SOUP_CONNECTION_IN_USE);
1313                         g_mutex_unlock (priv->host_lock);
1314                         item->conn = g_object_ref (conns->data);
1315                         return TRUE;
1316                 } else if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_CONNECTING)
1317                         num_pending++;
1318         }
1319
1320         /* Limit the number of pending connections; num_messages / 2
1321          * is somewhat arbitrary...
1322          */
1323         if (num_pending > host->num_messages / 2) {
1324                 g_mutex_unlock (priv->host_lock);
1325                 return FALSE;
1326         }
1327
1328         if (host->num_conns >= priv->max_conns_per_host) {
1329                 g_mutex_unlock (priv->host_lock);
1330                 return FALSE;
1331         }
1332
1333         if (priv->num_conns >= priv->max_conns) {
1334                 *try_pruning = TRUE;
1335                 g_mutex_unlock (priv->host_lock);
1336                 return FALSE;
1337         }
1338
1339         if (item->proxy_addr) {
1340                 remote_addr = item->proxy_addr;
1341                 tunnel_addr = NULL;
1342         } else {
1343                 remote_addr = host->addr;
1344                 tunnel_addr = NULL;
1345         }
1346
1347         uri = soup_message_get_uri (item->msg);
1348         if (uri->scheme == SOUP_URI_SCHEME_HTTPS) {
1349                 if (!priv->ssl_creds)
1350                         priv->ssl_creds = soup_ssl_get_client_credentials (priv->ssl_ca_file);
1351                 ssl_creds = priv->ssl_creds;
1352
1353                 if (item->proxy_addr)
1354                         tunnel_addr = host->addr;
1355         } else
1356                 ssl_creds = NULL;
1357
1358         conn = soup_connection_new (
1359                 SOUP_CONNECTION_REMOTE_ADDRESS, remote_addr,
1360                 SOUP_CONNECTION_TUNNEL_ADDRESS, tunnel_addr,
1361                 SOUP_CONNECTION_PROXY_URI, item->proxy_uri,
1362                 SOUP_CONNECTION_SSL_CREDENTIALS, ssl_creds,
1363                 SOUP_CONNECTION_SSL_STRICT, priv->ssl_strict,
1364                 SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context,
1365                 SOUP_CONNECTION_TIMEOUT, priv->io_timeout,
1366                 SOUP_CONNECTION_IDLE_TIMEOUT, priv->idle_timeout,
1367                 NULL);
1368         g_signal_connect (conn, "disconnected",
1369                           G_CALLBACK (connection_disconnected),
1370                           session);
1371
1372         g_signal_emit (session, signals[CONNECTION_CREATED], 0, conn);
1373
1374         g_hash_table_insert (priv->conns, conn, host);
1375
1376         priv->num_conns++;
1377         host->num_conns++;
1378         host->connections = g_slist_prepend (host->connections, conn);
1379
1380         g_mutex_unlock (priv->host_lock);
1381         item->conn = g_object_ref (conn);
1382         return TRUE;
1383 }
1384
1385 SoupMessageQueue *
1386 soup_session_get_queue (SoupSession *session)
1387 {
1388         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1389
1390         return priv->queue;
1391 }
1392
1393 void
1394 soup_session_unqueue_item (SoupSession          *session,
1395                            SoupMessageQueueItem *item)
1396 {
1397         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1398         SoupSessionHost *host;
1399
1400         if (item->conn) {
1401                 g_object_unref (item->conn);
1402                 item->conn = NULL;
1403         }
1404
1405         if (item->state != SOUP_MESSAGE_FINISHED) {
1406                 g_warning ("finished an item with state %d", item->state);
1407                 return;
1408         }
1409
1410         soup_message_queue_remove (priv->queue, item);
1411
1412         g_mutex_lock (priv->host_lock);
1413         host = get_host_for_message (session, item->msg);
1414         host->num_messages--;
1415         g_mutex_unlock (priv->host_lock);
1416
1417         /* g_signal_handlers_disconnect_by_func doesn't work if you
1418          * have a metamarshal, meaning it doesn't work with
1419          * soup_message_add_header_handler()
1420          */
1421         g_signal_handlers_disconnect_matched (item->msg, G_SIGNAL_MATCH_DATA,
1422                                               0, 0, NULL, NULL, item);
1423         g_signal_emit (session, signals[REQUEST_UNQUEUED], 0, item->msg);
1424         soup_message_queue_item_unref (item);
1425 }
1426
1427 void
1428 soup_session_set_item_status (SoupSession          *session,
1429                               SoupMessageQueueItem *item,
1430                               guint                 status_code)
1431 {
1432         SoupURI *uri;
1433         char *msg;
1434
1435         switch (status_code) {
1436         case SOUP_STATUS_CANT_RESOLVE:
1437         case SOUP_STATUS_CANT_CONNECT:
1438                 uri = soup_message_get_uri (item->msg);
1439                 msg = g_strdup_printf ("%s (%s)",
1440                                        soup_status_get_phrase (status_code),
1441                                        uri->host);
1442                 soup_message_set_status_full (item->msg, status_code, msg);
1443                 g_free (msg);
1444                 break;
1445
1446         case SOUP_STATUS_CANT_RESOLVE_PROXY:
1447         case SOUP_STATUS_CANT_CONNECT_PROXY:
1448                 if (item->proxy_uri && item->proxy_uri->host) {
1449                         msg = g_strdup_printf ("%s (%s)",
1450                                                soup_status_get_phrase (status_code),
1451                                                item->proxy_uri->host);
1452                         soup_message_set_status_full (item->msg, status_code, msg);
1453                         g_free (msg);
1454                         break;
1455                 }
1456                 /* else fall through */
1457
1458         default:
1459                 soup_message_set_status (item->msg, status_code);
1460         }
1461 }
1462
1463 static void
1464 queue_message (SoupSession *session, SoupMessage *msg,
1465                SoupSessionCallback callback, gpointer user_data)
1466 {
1467         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1468         SoupMessageQueueItem *item;
1469         SoupSessionHost *host;
1470
1471         item = soup_message_queue_append (priv->queue, msg, callback, user_data);
1472
1473         g_mutex_lock (priv->host_lock);
1474         host = get_host_for_message (session, item->msg);
1475         host->num_messages++;
1476         g_mutex_unlock (priv->host_lock);
1477
1478         if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_NO_REDIRECT)) {
1479                 soup_message_add_header_handler (
1480                         msg, "got_body", "Location",
1481                         G_CALLBACK (redirect_handler), item);
1482         }
1483
1484         g_signal_emit (session, signals[REQUEST_QUEUED], 0, msg);
1485 }
1486
1487 /**
1488  * SoupSessionCallback:
1489  * @session: the session
1490  * @msg: the message that has finished
1491  * @user_data: the data passed to soup_session_queue_message
1492  *
1493  * Prototype for the callback passed to soup_session_queue_message(),
1494  * qv.
1495  **/
1496
1497 /**
1498  * soup_session_queue_message:
1499  * @session: a #SoupSession
1500  * @msg: (transfer full): the message to queue
1501  * @callback: (allow-none) (scope async): a #SoupSessionCallback which will
1502  * be called after the message completes or when an unrecoverable error occurs.
1503  * @user_data: (allow-none): a pointer passed to @callback.
1504  * 
1505  * Queues the message @msg for sending. All messages are processed
1506  * while the glib main loop runs. If @msg has been processed before,
1507  * any resources related to the time it was last sent are freed.
1508  *
1509  * Upon message completion, the callback specified in @callback will
1510  * be invoked (in the thread associated with @session's async
1511  * context). If after returning from this callback the message has not
1512  * been requeued, @msg will be unreffed.
1513  */
1514 void
1515 soup_session_queue_message (SoupSession *session, SoupMessage *msg,
1516                             SoupSessionCallback callback, gpointer user_data)
1517 {
1518         g_return_if_fail (SOUP_IS_SESSION (session));
1519         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1520
1521         SOUP_SESSION_GET_CLASS (session)->queue_message (session, msg,
1522                                                          callback, user_data);
1523 }
1524
1525 static void
1526 requeue_message (SoupSession *session, SoupMessage *msg)
1527 {
1528         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1529         SoupMessageQueueItem *item;
1530
1531         item = soup_message_queue_lookup (priv->queue, msg);
1532         g_return_if_fail (item != NULL);
1533         item->state = SOUP_MESSAGE_RESTARTING;
1534         soup_message_queue_item_unref (item);
1535 }
1536
1537 /**
1538  * soup_session_requeue_message:
1539  * @session: a #SoupSession
1540  * @msg: the message to requeue
1541  *
1542  * This causes @msg to be placed back on the queue to be attempted
1543  * again.
1544  **/
1545 void
1546 soup_session_requeue_message (SoupSession *session, SoupMessage *msg)
1547 {
1548         g_return_if_fail (SOUP_IS_SESSION (session));
1549         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1550
1551         SOUP_SESSION_GET_CLASS (session)->requeue_message (session, msg);
1552 }
1553
1554
1555 /**
1556  * soup_session_send_message:
1557  * @session: a #SoupSession
1558  * @msg: the message to send
1559  * 
1560  * Synchronously send @msg. This call will not return until the
1561  * transfer is finished successfully or there is an unrecoverable
1562  * error.
1563  *
1564  * @msg is not freed upon return.
1565  *
1566  * Return value: the HTTP status code of the response
1567  */
1568 guint
1569 soup_session_send_message (SoupSession *session, SoupMessage *msg)
1570 {
1571         g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
1572         g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_STATUS_MALFORMED);
1573
1574         return SOUP_SESSION_GET_CLASS (session)->send_message (session, msg);
1575 }
1576
1577
1578 /**
1579  * soup_session_pause_message:
1580  * @session: a #SoupSession
1581  * @msg: a #SoupMessage currently running on @session
1582  *
1583  * Pauses HTTP I/O on @msg. Call soup_session_unpause_message() to
1584  * resume I/O.
1585  **/
1586 void
1587 soup_session_pause_message (SoupSession *session,
1588                             SoupMessage *msg)
1589 {
1590         g_return_if_fail (SOUP_IS_SESSION (session));
1591         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1592
1593         soup_message_io_pause (msg);
1594 }
1595
1596 /**
1597  * soup_session_unpause_message:
1598  * @session: a #SoupSession
1599  * @msg: a #SoupMessage currently running on @session
1600  *
1601  * Resumes HTTP I/O on @msg. Use this to resume after calling
1602  * soup_session_pause_message().
1603  *
1604  * If @msg is being sent via blocking I/O, this will resume reading or
1605  * writing immediately. If @msg is using non-blocking I/O, then
1606  * reading or writing won't resume until you return to the main loop.
1607  **/
1608 void
1609 soup_session_unpause_message (SoupSession *session,
1610                               SoupMessage *msg)
1611 {
1612         g_return_if_fail (SOUP_IS_SESSION (session));
1613         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1614
1615         soup_message_io_unpause (msg);
1616 }
1617
1618
1619 static void
1620 cancel_message (SoupSession *session, SoupMessage *msg, guint status_code)
1621 {
1622         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1623         SoupMessageQueueItem *item;
1624
1625         item = soup_message_queue_lookup (priv->queue, msg);
1626         g_return_if_fail (item != NULL);
1627
1628         if (item->cancellable)
1629                 g_cancellable_cancel (item->cancellable);
1630
1631         soup_message_set_status (msg, status_code);
1632         if (soup_message_io_in_progress (msg))
1633                 soup_message_io_finished (msg);
1634         else
1635                 item->state = SOUP_MESSAGE_FINISHING;
1636
1637         soup_message_queue_item_unref (item);
1638 }
1639
1640 /**
1641  * soup_session_cancel_message:
1642  * @session: a #SoupSession
1643  * @msg: the message to cancel
1644  * @status_code: status code to set on @msg (generally
1645  * %SOUP_STATUS_CANCELLED)
1646  *
1647  * Causes @session to immediately finish processing @msg (regardless
1648  * of its current state) with a final status_code of @status_code. You
1649  * may call this at any time after handing @msg off to @session; if
1650  * @session has started sending the request but has not yet received
1651  * the complete response, then it will close the request's connection.
1652  * Note that with non-idempotent requests (eg, %POST, %PUT, %DELETE)
1653  * it is possible that you might cancel the request after the server
1654  * acts on it, but before it returns a response, leaving the remote
1655  * resource in an unknown state.
1656  *
1657  * If the message is cancelled while its response body is being read,
1658  * then the response body in @msg will be left partially-filled-in.
1659  * The response headers, on the other hand, will always be either
1660  * empty or complete.
1661  *
1662  * For messages queued with soup_session_queue_message() (and
1663  * cancelled from the same thread), the callback will be invoked
1664  * before soup_session_cancel_message() returns.
1665  **/
1666 void
1667 soup_session_cancel_message (SoupSession *session, SoupMessage *msg,
1668                              guint status_code)
1669 {
1670         SoupSessionPrivate *priv;
1671         SoupMessageQueueItem *item;
1672
1673         g_return_if_fail (SOUP_IS_SESSION (session));
1674         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1675
1676         priv = SOUP_SESSION_GET_PRIVATE (session);
1677         item = soup_message_queue_lookup (priv->queue, msg);
1678         /* If the message is already ending, don't do anything */
1679         if (!item)
1680                 return;
1681         if (item->state == SOUP_MESSAGE_FINISHED) {
1682                 soup_message_queue_item_unref (item);
1683                 return;
1684         }
1685
1686         SOUP_SESSION_GET_CLASS (session)->cancel_message (session, msg, status_code);
1687         soup_message_queue_item_unref (item);
1688 }
1689
1690 static void
1691 gather_conns (gpointer key, gpointer host, gpointer data)
1692 {
1693         SoupConnection *conn = key;
1694         GSList **conns = data;
1695
1696         *conns = g_slist_prepend (*conns, g_object_ref (conn));
1697 }
1698
1699 /**
1700  * soup_session_abort:
1701  * @session: the session
1702  *
1703  * Cancels all pending requests in @session.
1704  **/
1705 void
1706 soup_session_abort (SoupSession *session)
1707 {
1708         SoupSessionPrivate *priv;
1709         SoupMessageQueueItem *item;
1710         GSList *conns, *c;
1711
1712         g_return_if_fail (SOUP_IS_SESSION (session));
1713         priv = SOUP_SESSION_GET_PRIVATE (session);
1714
1715         for (item = soup_message_queue_first (priv->queue);
1716              item;
1717              item = soup_message_queue_next (priv->queue, item)) {
1718                 soup_session_cancel_message (session, item->msg,
1719                                              SOUP_STATUS_CANCELLED);
1720         }
1721
1722         /* Close all connections */
1723         g_mutex_lock (priv->host_lock);
1724         conns = NULL;
1725         g_hash_table_foreach (priv->conns, gather_conns, &conns);
1726
1727         g_mutex_unlock (priv->host_lock);
1728         for (c = conns; c; c = c->next) {
1729                 soup_connection_disconnect (c->data);
1730                 g_object_unref (c->data);
1731         }
1732
1733         g_slist_free (conns);
1734 }
1735
1736 /**
1737 * soup_session_prepare_for_uri:
1738 * @session: a #SoupSession
1739 * @uri: a #SoupURI which may be required
1740 *
1741 * Tells @session that @uri may be requested shortly, and so the
1742 * session can try to prepare (resolving the domain name, obtaining
1743 * proxy address, etc.) in order to work more quickly once the URI is
1744 * actually requested.
1745 *
1746 * This method acts asynchronously, in @session's %async_context.
1747 * If you are using #SoupSessionSync and do not have a main loop running,
1748 * then you can't use this method.
1749 *
1750 * Since: 2.30
1751 **/
1752 void
1753 soup_session_prepare_for_uri (SoupSession *session, SoupURI *uri)
1754 {
1755         SoupSessionPrivate *priv;
1756         SoupSessionHost *host;
1757         SoupAddress *addr;
1758
1759         g_return_if_fail (SOUP_IS_SESSION (session));
1760         g_return_if_fail (uri != NULL);
1761
1762         if (!uri->host)
1763                 return;
1764
1765         priv = SOUP_SESSION_GET_PRIVATE (session);
1766
1767         g_mutex_lock (priv->host_lock);
1768         host = get_host_for_uri (session, uri);
1769         addr = g_object_ref (host->addr);
1770         g_mutex_unlock (priv->host_lock);
1771
1772         soup_address_resolve_async (addr, priv->async_context,
1773                                     NULL, NULL, NULL);
1774 }
1775
1776 /**
1777  * soup_session_add_feature:
1778  * @session: a #SoupSession
1779  * @feature: an object that implements #SoupSessionFeature
1780  *
1781  * Adds @feature's functionality to @session. You can also add a
1782  * feature to the session at construct time by using the
1783  * %SOUP_SESSION_ADD_FEATURE property.
1784  *
1785  * Since: 2.24
1786  **/
1787 void
1788 soup_session_add_feature (SoupSession *session, SoupSessionFeature *feature)
1789 {
1790         SoupSessionPrivate *priv;
1791
1792         g_return_if_fail (SOUP_IS_SESSION (session));
1793         g_return_if_fail (SOUP_IS_SESSION_FEATURE (feature));
1794
1795         priv = SOUP_SESSION_GET_PRIVATE (session);
1796         priv->features = g_slist_prepend (priv->features, g_object_ref (feature));
1797         g_hash_table_remove_all (priv->features_cache);
1798         soup_session_feature_attach (feature, session);
1799 }
1800
1801 /**
1802  * soup_session_add_feature_by_type:
1803  * @session: a #SoupSession
1804  * @feature_type: the #GType of a class that implements #SoupSessionFeature
1805  *
1806  * Creates a new feature of type @feature_type and adds it to
1807  * @session. You can use this instead of soup_session_add_feature() in
1808  * the case wher you don't need to customize the new feature in any
1809  * way. You can also add a feature to the session at construct time by
1810  * using the %SOUP_SESSION_ADD_FEATURE_BY_TYPE property.
1811  *
1812  * Since: 2.24
1813  **/
1814 void
1815 soup_session_add_feature_by_type (SoupSession *session, GType feature_type)
1816 {
1817         SoupSessionFeature *feature;
1818
1819         g_return_if_fail (SOUP_IS_SESSION (session));
1820         g_return_if_fail (g_type_is_a (feature_type, SOUP_TYPE_SESSION_FEATURE));
1821
1822         feature = g_object_new (feature_type, NULL);
1823         soup_session_add_feature (session, feature);
1824         g_object_unref (feature);
1825 }
1826
1827 /**
1828  * soup_session_remove_feature:
1829  * @session: a #SoupSession
1830  * @feature: a feature that has previously been added to @session
1831  *
1832  * Removes @feature's functionality from @session.
1833  *
1834  * Since: 2.24
1835  **/
1836 void
1837 soup_session_remove_feature (SoupSession *session, SoupSessionFeature *feature)
1838 {
1839         SoupSessionPrivate *priv;
1840
1841         g_return_if_fail (SOUP_IS_SESSION (session));
1842
1843         priv = SOUP_SESSION_GET_PRIVATE (session);
1844         if (g_slist_find (priv->features, feature)) {
1845                 priv->features = g_slist_remove (priv->features, feature);
1846                 g_hash_table_remove_all (priv->features_cache);
1847                 soup_session_feature_detach (feature, session);
1848                 g_object_unref (feature);
1849         }
1850 }
1851
1852 /**
1853  * soup_session_remove_feature_by_type:
1854  * @session: a #SoupSession
1855  * @feature_type: the #GType of a class that implements #SoupSessionFeature
1856  *
1857  * Removes all features of type @feature_type (or any subclass of
1858  * @feature_type) from @session. You can also remove standard features
1859  * from the session at construct time by using the
1860  * %SOUP_SESSION_REMOVE_FEATURE_BY_TYPE property.
1861  *
1862  * Since: 2.24
1863  **/
1864 void
1865 soup_session_remove_feature_by_type (SoupSession *session, GType feature_type)
1866 {
1867         SoupSessionPrivate *priv;
1868         GSList *f;
1869
1870         g_return_if_fail (SOUP_IS_SESSION (session));
1871
1872         priv = SOUP_SESSION_GET_PRIVATE (session);
1873 restart:
1874         for (f = priv->features; f; f = f->next) {
1875                 if (G_TYPE_CHECK_INSTANCE_TYPE (f->data, feature_type)) {
1876                         soup_session_remove_feature (session, f->data);
1877                         goto restart;
1878                 }
1879         }
1880 }
1881
1882 /**
1883  * soup_session_get_features:
1884  * @session: a #SoupSession
1885  * @feature_type: the #GType of the class of features to get
1886  *
1887  * Generates a list of @session's features of type @feature_type. (If
1888  * you want to see all features, you can pass %G_TYPE_SESSION_FEATURE
1889  * for @feature_type.)
1890  *
1891  * Return value: (transfer container): a list of features. You must
1892  * free the list, but not its contents
1893  *
1894  * Since: 2.26
1895  **/
1896 GSList *
1897 soup_session_get_features (SoupSession *session, GType feature_type)
1898 {
1899         SoupSessionPrivate *priv;
1900         GSList *f, *ret;
1901
1902         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
1903
1904         priv = SOUP_SESSION_GET_PRIVATE (session);
1905         for (f = priv->features, ret = NULL; f; f = f->next) {
1906                 if (G_TYPE_CHECK_INSTANCE_TYPE (f->data, feature_type))
1907                         ret = g_slist_prepend (ret, f->data);
1908         }
1909         return g_slist_reverse (ret);
1910 }
1911
1912 /**
1913  * soup_session_get_feature:
1914  * @session: a #SoupSession
1915  * @feature_type: the #GType of the feature to get
1916  *
1917  * Gets the first feature in @session of type @feature_type. For
1918  * features where there may be more than one feature of a given type,
1919  * use soup_session_get_features().
1920  *
1921  * Return value: (transfer none): a #SoupSessionFeature, or %NULL. The
1922  * feature is owned by @session.
1923  *
1924  * Since: 2.26
1925  **/
1926 SoupSessionFeature *
1927 soup_session_get_feature (SoupSession *session, GType feature_type)
1928 {
1929         SoupSessionPrivate *priv;
1930         SoupSessionFeature *feature;
1931         GSList *f;
1932
1933         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
1934
1935         priv = SOUP_SESSION_GET_PRIVATE (session);
1936
1937         feature = g_hash_table_lookup (priv->features_cache,
1938                                        GSIZE_TO_POINTER (feature_type));
1939         if (feature)
1940                 return feature;
1941
1942         for (f = priv->features; f; f = f->next) {
1943                 feature = f->data;
1944                 if (G_TYPE_CHECK_INSTANCE_TYPE (feature, feature_type)) {
1945                         g_hash_table_insert (priv->features_cache,
1946                                              GSIZE_TO_POINTER (feature_type),
1947                                              feature);
1948                         return feature;
1949                 }
1950         }
1951         return NULL;
1952 }
1953
1954 /**
1955  * soup_session_get_feature_for_message:
1956  * @session: a #SoupSession
1957  * @feature_type: the #GType of the feature to get
1958  * @msg: a #SoupMessage
1959  *
1960  * Gets the first feature in @session of type @feature_type, provided
1961  * that it is not disabled for @msg. As with
1962  * soup_session_get_feature(), this should only be used for features
1963  * where @feature_type is only expected to match a single feature. In
1964  * particular, if there are two matching features, and the first is
1965  * disabled on @msg, and the second is not, then this will return
1966  * %NULL, not the second feature.
1967  *
1968  * Return value: (transfer none): a #SoupSessionFeature, or %NULL. The
1969  * feature is owned by @session.
1970  *
1971  * Since: 2.28
1972  **/
1973 SoupSessionFeature *
1974 soup_session_get_feature_for_message (SoupSession *session, GType feature_type,
1975                                       SoupMessage *msg)
1976 {
1977         SoupSessionFeature *feature;
1978
1979         feature = soup_session_get_feature (session, feature_type);
1980         if (feature && soup_message_disables_feature (msg, feature))
1981                 return NULL;
1982         return feature;
1983 }