soup-auth-manager: add soup_auth_manager_use_auth()
[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 <glib/gi18n-lib.h>
13
14 #include "soup-session.h"
15 #include "soup.h"
16 #include "soup-auth-manager.h"
17 #include "soup-cache-private.h"
18 #include "soup-connection.h"
19 #include "soup-marshal.h"
20 #include "soup-message-private.h"
21 #include "soup-misc-private.h"
22 #include "soup-message-queue.h"
23 #include "soup-proxy-resolver-static.h"
24 #include "soup-session-private.h"
25
26 #define HOST_KEEP_ALIVE 5 * 60 * 1000 /* 5 min in msecs */
27
28 /**
29  * SECTION:soup-session
30  * @short_description: Soup session state object
31  *
32  * #SoupSession is the object that controls client-side HTTP. A
33  * #SoupSession encapsulates all of the state that libsoup is keeping
34  * on behalf of your program; cached HTTP connections, authentication
35  * information, etc. It also keeps track of various global options
36  * and features that you are using.
37  *
38  * Most applications will only need a single #SoupSession; the primary
39  * reason you might need multiple sessions is if you need to have
40  * multiple independent authentication contexts. (Eg, you are
41  * connecting to a server and authenticating as two different users at
42  * different times; the easiest way to ensure that each #SoupMessage
43  * is sent with the authentication information you intended is to use
44  * one session for the first user, and a second session for the other
45  * user.)
46  *
47  * In the past, #SoupSession was an abstract class, and users needed
48  * to choose between #SoupSessionAsync (which always uses
49  * #GMainLoop<!-- -->-based I/O), or #SoupSessionSync (which always uses
50  * blocking I/O and can be used from multiple threads simultaneously).
51  * This is no longer necessary; you can (and should) use a plain
52  * #SoupSession, which supports both synchronous and asynchronous use.
53  * (When using a plain #SoupSession, soup_session_queue_message()
54  * behaves like it traditionally did on a #SoupSessionAsync, and
55  * soup_session_send_message() behaves like it traditionally did on a
56  * #SoupSessionSync.)
57  **/
58
59 static void
60 soup_init (void)
61 {
62         bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
63 #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
64         bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
65 #endif
66 }
67
68 typedef struct {
69         SoupURI     *uri;
70         SoupAddress *addr;
71
72         GSList      *connections;      /* CONTAINS: SoupConnection */
73         guint        num_conns;
74
75         guint        num_messages;
76
77         gboolean     ssl_fallback;
78
79         GSource     *keep_alive_src;
80         SoupSession *session;
81 } SoupSessionHost;
82 static guint soup_host_uri_hash (gconstpointer key);
83 static gboolean soup_host_uri_equal (gconstpointer v1, gconstpointer v2);
84
85 typedef struct {
86         SoupSession *session;
87         gboolean disposed;
88
89         GTlsDatabase *tlsdb;
90         char *ssl_ca_file;
91         gboolean ssl_strict;
92
93         SoupMessageQueue *queue;
94
95         char *user_agent;
96         char *accept_language;
97         gboolean accept_language_auto;
98
99         GSList *features;
100         GHashTable *features_cache;
101
102         GHashTable *http_hosts, *https_hosts; /* char* -> SoupSessionHost */
103         GHashTable *conns; /* SoupConnection -> SoupSessionHost */
104         guint num_conns;
105         guint max_conns, max_conns_per_host;
106         guint io_timeout, idle_timeout;
107
108         /* Must hold the conn_lock before potentially creating a new
109          * SoupSessionHost, adding/removing a connection,
110          * disconnecting a connection, or moving a connection from
111          * IDLE to IN_USE. Must not emit signals or destroy objects
112          * while holding it. conn_cond is signaled when it may be
113          * possible for a previously-blocked message to continue.
114          */
115         GMutex conn_lock;
116         GCond conn_cond;
117
118         GMainContext *async_context;
119         gboolean use_thread_context;
120         GSList *run_queue_sources;
121
122         GResolver *resolver;
123
124         char **http_aliases, **https_aliases;
125
126         GHashTable *request_types;
127 } SoupSessionPrivate;
128 #define SOUP_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION, SoupSessionPrivate))
129
130 #define SOUP_IS_PLAIN_SESSION(o) (G_TYPE_FROM_INSTANCE (o) == SOUP_TYPE_SESSION)
131
132 static void free_host (SoupSessionHost *host);
133 static void connection_state_changed (GObject *object, GParamSpec *param,
134                                       gpointer user_data);
135 static void connection_disconnected (SoupConnection *conn, gpointer user_data);
136 static void drop_connection (SoupSession *session, SoupSessionHost *host,
137                              SoupConnection *conn);
138
139 static void auth_manager_authenticate (SoupAuthManager *manager,
140                                        SoupMessage *msg, SoupAuth *auth,
141                                        gboolean retrying, gpointer user_data);
142
143 static void async_run_queue (SoupSession *session);
144
145 static void async_send_request_running (SoupSession *session, SoupMessageQueueItem *item);
146
147 #define SOUP_SESSION_MAX_CONNS_DEFAULT 10
148 #define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 2
149
150 #define SOUP_SESSION_MAX_REDIRECTION_COUNT 20
151
152 #define SOUP_SESSION_USER_AGENT_BASE "libsoup/" PACKAGE_VERSION
153
154 G_DEFINE_TYPE_WITH_CODE (SoupSession, soup_session, G_TYPE_OBJECT,
155                          soup_init ();
156                          )
157
158 enum {
159         REQUEST_QUEUED,
160         REQUEST_STARTED,
161         REQUEST_UNQUEUED,
162         AUTHENTICATE,
163         CONNECTION_CREATED,
164         TUNNELING,
165         LAST_SIGNAL
166 };
167
168 static guint signals[LAST_SIGNAL] = { 0 };
169
170 enum {
171         PROP_0,
172
173         PROP_PROXY_URI,
174         PROP_MAX_CONNS,
175         PROP_MAX_CONNS_PER_HOST,
176         PROP_USE_NTLM,
177         PROP_SSL_CA_FILE,
178         PROP_SSL_USE_SYSTEM_CA_FILE,
179         PROP_TLS_DATABASE,
180         PROP_SSL_STRICT,
181         PROP_ASYNC_CONTEXT,
182         PROP_USE_THREAD_CONTEXT,
183         PROP_TIMEOUT,
184         PROP_USER_AGENT,
185         PROP_ACCEPT_LANGUAGE,
186         PROP_ACCEPT_LANGUAGE_AUTO,
187         PROP_IDLE_TIMEOUT,
188         PROP_ADD_FEATURE,
189         PROP_ADD_FEATURE_BY_TYPE,
190         PROP_REMOVE_FEATURE_BY_TYPE,
191         PROP_HTTP_ALIASES,
192         PROP_HTTPS_ALIASES,
193
194         LAST_PROP
195 };
196
197 static void
198 soup_session_init (SoupSession *session)
199 {
200         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
201         SoupAuthManager *auth_manager;
202
203         priv->session = session;
204
205         priv->queue = soup_message_queue_new (session);
206
207         g_mutex_init (&priv->conn_lock);
208         g_cond_init (&priv->conn_cond);
209         priv->http_hosts = g_hash_table_new_full (soup_host_uri_hash,
210                                                   soup_host_uri_equal,
211                                                   NULL, (GDestroyNotify)free_host);
212         priv->https_hosts = g_hash_table_new_full (soup_host_uri_hash,
213                                                    soup_host_uri_equal,
214                                                    NULL, (GDestroyNotify)free_host);
215         priv->conns = g_hash_table_new (NULL, NULL);
216
217         priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT;
218         priv->max_conns_per_host = SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT;
219
220         priv->features_cache = g_hash_table_new (NULL, NULL);
221
222         auth_manager = g_object_new (SOUP_TYPE_AUTH_MANAGER, NULL);
223         g_signal_connect (auth_manager, "authenticate",
224                           G_CALLBACK (auth_manager_authenticate), session);
225         soup_session_feature_add_feature (SOUP_SESSION_FEATURE (auth_manager),
226                                           SOUP_TYPE_AUTH_BASIC);
227         soup_session_feature_add_feature (SOUP_SESSION_FEATURE (auth_manager),
228                                           SOUP_TYPE_AUTH_DIGEST);
229         soup_session_add_feature (session, SOUP_SESSION_FEATURE (auth_manager));
230         g_object_unref (auth_manager);
231
232         /* We'll be doing DNS continuously-ish while the session is active,
233          * so hold a ref on the default GResolver.
234          */
235         priv->resolver = g_resolver_get_default ();
236
237         priv->ssl_strict = TRUE;
238
239         priv->http_aliases = g_new (char *, 2);
240         priv->http_aliases[0] = (char *)g_intern_string ("*");
241         priv->http_aliases[1] = NULL;
242
243         priv->request_types = g_hash_table_new (soup_str_case_hash,
244                                                 soup_str_case_equal);
245         soup_session_add_feature_by_type (session, SOUP_TYPE_REQUEST_HTTP);
246         soup_session_add_feature_by_type (session, SOUP_TYPE_REQUEST_FILE);
247         soup_session_add_feature_by_type (session, SOUP_TYPE_REQUEST_DATA);
248 }
249
250 static GObject *
251 soup_session_constructor (GType                  type,
252                           guint                  n_construct_properties,
253                           GObjectConstructParam *construct_params)
254 {
255         GObject *object;
256
257         object = G_OBJECT_CLASS (soup_session_parent_class)->constructor (type, n_construct_properties, construct_params);
258
259         /* If this is a "plain" SoupSession, fix up the default
260          * properties values, etc.
261          */
262         if (type == SOUP_TYPE_SESSION) {
263                 SoupSession *session = SOUP_SESSION (object);
264                 SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
265
266                 g_clear_object (&priv->tlsdb);
267                 priv->tlsdb = g_tls_backend_get_default_database (g_tls_backend_get_default ());
268
269                 g_clear_pointer (&priv->async_context, g_main_context_unref);
270                 priv->async_context = g_main_context_ref_thread_default ();
271                 priv->use_thread_context = TRUE;
272
273                 priv->io_timeout = priv->idle_timeout = 60;
274
275                 priv->http_aliases[0] = NULL;
276
277                 soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_DECODER);
278                 soup_session_add_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER_DEFAULT);
279         }
280
281         return object;
282 }
283
284 static void
285 soup_session_dispose (GObject *object)
286 {
287         SoupSession *session = SOUP_SESSION (object);
288         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
289         GSList *iter;
290
291         priv->disposed = TRUE;
292
293         for (iter = priv->run_queue_sources; iter; iter = iter->next) {
294                 g_source_destroy (iter->data);
295                 g_source_unref (iter->data);
296         }
297         g_clear_pointer (&priv->run_queue_sources, g_slist_free);
298
299         priv->disposed = TRUE;
300         soup_session_abort (session);
301         g_warn_if_fail (g_hash_table_size (priv->conns) == 0);
302
303         while (priv->features)
304                 soup_session_remove_feature (session, priv->features->data);
305
306         G_OBJECT_CLASS (soup_session_parent_class)->dispose (object);
307 }
308
309 static void
310 soup_session_finalize (GObject *object)
311 {
312         SoupSession *session = SOUP_SESSION (object);
313         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
314
315         soup_message_queue_destroy (priv->queue);
316
317         g_mutex_clear (&priv->conn_lock);
318         g_cond_clear (&priv->conn_cond);
319         g_hash_table_destroy (priv->http_hosts);
320         g_hash_table_destroy (priv->https_hosts);
321         g_hash_table_destroy (priv->conns);
322
323         g_free (priv->user_agent);
324         g_free (priv->accept_language);
325
326         g_clear_object (&priv->tlsdb);
327         g_free (priv->ssl_ca_file);
328
329         g_clear_pointer (&priv->async_context, g_main_context_unref);
330
331         g_hash_table_destroy (priv->features_cache);
332
333         g_object_unref (priv->resolver);
334
335         g_free (priv->http_aliases);
336         g_free (priv->https_aliases);
337
338         g_hash_table_destroy (priv->request_types);
339
340         G_OBJECT_CLASS (soup_session_parent_class)->finalize (object);
341 }
342
343 /* Converts a language in POSIX format and to be RFC2616 compliant    */
344 /* Based on code from epiphany-webkit (ephy_langs_append_languages()) */
345 static gchar *
346 posix_lang_to_rfc2616 (const gchar *language)
347 {
348         /* Don't include charset variants, etc */
349         if (strchr (language, '.') || strchr (language, '@'))
350                 return NULL;
351
352         /* Ignore "C" locale, which g_get_language_names() always
353          * includes as a fallback.
354          */
355         if (!strcmp (language, "C"))
356                 return NULL;
357
358         return g_strdelimit (g_ascii_strdown (language, -1), "_", '-');
359 }
360
361 /* Converts @quality from 0-100 to 0.0-1.0 and appends to @str */
362 static gchar *
363 add_quality_value (const gchar *str, int quality)
364 {
365         g_return_val_if_fail (str != NULL, NULL);
366
367         if (quality >= 0 && quality < 100) {
368                 /* We don't use %.02g because of "." vs "," locale issues */
369                 if (quality % 10)
370                         return g_strdup_printf ("%s;q=0.%02d", str, quality);
371                 else
372                         return g_strdup_printf ("%s;q=0.%d", str, quality / 10);
373         } else
374                 return g_strdup (str);
375 }
376
377 /* Returns a RFC2616 compliant languages list from system locales */
378 static gchar *
379 accept_languages_from_system (void)
380 {
381         const char * const * lang_names;
382         GPtrArray *langs = NULL;
383         char *lang, *langs_str;
384         int delta;
385         int i;
386
387         lang_names = g_get_language_names ();
388         g_return_val_if_fail (lang_names != NULL, NULL);
389
390         /* Build the array of languages */
391         langs = g_ptr_array_new_with_free_func (g_free);
392         for (i = 0; lang_names[i] != NULL; i++) {
393                 lang = posix_lang_to_rfc2616 (lang_names[i]);
394                 if (lang)
395                         g_ptr_array_add (langs, lang);
396         }
397
398         /* Add quality values */
399         if (langs->len < 10)
400                 delta = 10;
401         else if (langs->len < 20)
402                 delta = 5;
403         else
404                 delta = 1;
405
406         for (i = 0; i < langs->len; i++) {
407                 lang = langs->pdata[i];
408                 langs->pdata[i] = add_quality_value (lang, 100 - i * delta);
409                 g_free (lang);
410         }
411
412         /* Fallback: add "en" if list is empty */
413         if (langs->len == 0)
414                 g_ptr_array_add (langs, g_strdup ("en"));
415
416         g_ptr_array_add (langs, NULL);
417         langs_str = g_strjoinv (", ", (char **)langs->pdata);
418         g_ptr_array_free (langs, TRUE);
419
420         return langs_str;
421 }
422
423 static void
424 set_tlsdb (SoupSession *session, GTlsDatabase *tlsdb)
425 {
426         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
427         GTlsDatabase *system_default;
428
429         if (tlsdb == priv->tlsdb)
430                 return;
431
432         g_object_freeze_notify (G_OBJECT (session));
433
434         system_default = g_tls_backend_get_default_database (g_tls_backend_get_default ());
435         if (priv->tlsdb == system_default || tlsdb == system_default) {
436                 g_object_notify (G_OBJECT (session), "ssl-use-system-ca-file");
437         }
438         g_object_unref (system_default);
439
440         if (priv->ssl_ca_file) {
441                 g_free (priv->ssl_ca_file);
442                 priv->ssl_ca_file = NULL;
443                 g_object_notify (G_OBJECT (session), "ssl-ca-file");
444         }
445
446         if (priv->tlsdb)
447                 g_object_unref (priv->tlsdb);
448         priv->tlsdb = tlsdb;
449         if (priv->tlsdb)
450                 g_object_ref (priv->tlsdb);
451
452         g_object_notify (G_OBJECT (session), "tls-database");
453         g_object_thaw_notify (G_OBJECT (session));
454 }
455
456 static void
457 set_use_system_ca_file (SoupSession *session, gboolean use_system_ca_file)
458 {
459         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
460         GTlsDatabase *system_default;
461
462         system_default = g_tls_backend_get_default_database (g_tls_backend_get_default ());
463
464         if (use_system_ca_file)
465                 set_tlsdb (session, system_default);
466         else if (priv->tlsdb == system_default)
467                 set_tlsdb (session, NULL);
468
469         g_object_unref (system_default);
470 }
471
472 static void
473 set_ssl_ca_file (SoupSession *session, const char *ssl_ca_file)
474 {
475         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
476         GTlsDatabase *tlsdb;
477         GError *error = NULL;
478
479         if (!g_strcmp0 (priv->ssl_ca_file, ssl_ca_file))
480                 return;
481
482         g_object_freeze_notify (G_OBJECT (session));
483
484         if (g_path_is_absolute (ssl_ca_file))
485                 tlsdb = g_tls_file_database_new (ssl_ca_file, &error);
486         else {
487                 char *path, *cwd;
488
489                 cwd = g_get_current_dir ();
490                 path = g_build_filename (cwd, ssl_ca_file, NULL);
491                 tlsdb = g_tls_file_database_new (path, &error);
492                 g_free (path);
493         }
494
495         if (error) {
496                 if (!g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_UNAVAILABLE)) {
497                         g_warning ("Could not set SSL credentials from '%s': %s",
498                                    ssl_ca_file, error->message);
499
500                         tlsdb = g_tls_file_database_new ("/dev/null", NULL);
501                 }
502                 g_error_free (error);
503         }
504
505         set_tlsdb (session, tlsdb);
506         g_object_unref (tlsdb);
507
508         priv->ssl_ca_file = g_strdup (ssl_ca_file);
509         g_object_notify (G_OBJECT (session), "ssl-ca-file");
510
511         g_object_thaw_notify (G_OBJECT (session));
512 }
513
514 /* priv->http_aliases and priv->https_aliases are stored as arrays of
515  * *interned* strings, so we can't just use g_strdupv() to set them.
516  */
517 static void
518 set_aliases (char ***variable, char **value)
519 {
520         int len, i;
521
522         if (*variable)
523                 g_free (*variable);
524
525         if (!value) {
526                 *variable = NULL;
527                 return;
528         }
529
530         len = g_strv_length (value);
531         *variable = g_new (char *, len + 1);
532         for (i = 0; i < len; i++)
533                 (*variable)[i] = (char *)g_intern_string (value[i]);
534         (*variable)[i] = NULL;
535 }
536
537 static void
538 soup_session_set_property (GObject *object, guint prop_id,
539                            const GValue *value, GParamSpec *pspec)
540 {
541         SoupSession *session = SOUP_SESSION (object);
542         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
543         SoupURI *uri;
544         const char *user_agent;
545         SoupSessionFeature *feature;
546         GMainContext *async_context;
547
548         switch (prop_id) {
549         case PROP_PROXY_URI:
550                 uri = g_value_get_boxed (value);
551
552                 if (uri) {
553 #ifdef G_GNUC_BEGIN_IGNORE_DEPRECATIONS
554 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
555 #endif
556                         soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER);
557 #ifdef G_GNUC_END_IGNORE_DEPRECATIONS
558 G_GNUC_END_IGNORE_DEPRECATIONS
559 #endif
560                         feature = SOUP_SESSION_FEATURE (soup_proxy_resolver_static_new (uri));
561                         soup_session_add_feature (session, feature);
562                         g_object_unref (feature);
563                 } else
564                         soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER_STATIC);
565
566                 soup_session_abort (session);
567                 break;
568         case PROP_MAX_CONNS:
569                 priv->max_conns = g_value_get_int (value);
570                 break;
571         case PROP_MAX_CONNS_PER_HOST:
572                 priv->max_conns_per_host = g_value_get_int (value);
573                 break;
574         case PROP_USE_NTLM:
575                 g_return_if_fail (!SOUP_IS_PLAIN_SESSION (session));
576                 feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER);
577                 if (feature) {
578                         if (g_value_get_boolean (value))
579                                 soup_session_feature_add_feature (feature, SOUP_TYPE_AUTH_NTLM);
580                         else
581                                 soup_session_feature_remove_feature (feature, SOUP_TYPE_AUTH_NTLM);
582                 } else
583                         g_warning ("Trying to set use-ntlm on session with no auth-manager");
584                 break;
585         case PROP_SSL_CA_FILE:
586                 set_ssl_ca_file (session, g_value_get_string (value));
587                 break;
588         case PROP_SSL_USE_SYSTEM_CA_FILE:
589                 set_use_system_ca_file (session, g_value_get_boolean (value));
590                 break;
591         case PROP_TLS_DATABASE:
592                 set_tlsdb (session, g_value_get_object (value));
593                 break;
594         case PROP_SSL_STRICT:
595                 priv->ssl_strict = g_value_get_boolean (value);
596                 break;
597         case PROP_ASYNC_CONTEXT:
598                 async_context = g_value_get_pointer (value);
599                 if (async_context && async_context != g_main_context_get_thread_default ())
600                         g_return_if_fail (!SOUP_IS_PLAIN_SESSION (session));
601                 priv->async_context = async_context;
602                 if (priv->async_context)
603                         g_main_context_ref (priv->async_context);
604                 break;
605         case PROP_USE_THREAD_CONTEXT:
606                 if (!g_value_get_boolean (value))
607                         g_return_if_fail (!SOUP_IS_PLAIN_SESSION (session));
608                 priv->use_thread_context = g_value_get_boolean (value);
609                 if (priv->use_thread_context) {
610                         if (priv->async_context)
611                                 g_main_context_unref (priv->async_context);
612                         priv->async_context = g_main_context_get_thread_default ();
613                         if (priv->async_context)
614                                 g_main_context_ref (priv->async_context);
615                 }
616                 break;
617         case PROP_TIMEOUT:
618                 priv->io_timeout = g_value_get_uint (value);
619                 break;
620         case PROP_USER_AGENT:
621                 g_free (priv->user_agent);
622                 user_agent = g_value_get_string (value);
623                 if (!user_agent)
624                         priv->user_agent = NULL;
625                 else if (!*user_agent) {
626                         priv->user_agent =
627                                 g_strdup (SOUP_SESSION_USER_AGENT_BASE);
628                 } else if (g_str_has_suffix (user_agent, " ")) {
629                         priv->user_agent =
630                                 g_strdup_printf ("%s%s", user_agent,
631                                                  SOUP_SESSION_USER_AGENT_BASE);
632                 } else
633                         priv->user_agent = g_strdup (user_agent);
634                 break;
635         case PROP_ACCEPT_LANGUAGE:
636                 g_free (priv->accept_language);
637                 priv->accept_language = g_strdup (g_value_get_string (value));
638                 priv->accept_language_auto = FALSE;
639                 break;
640         case PROP_ACCEPT_LANGUAGE_AUTO:
641                 priv->accept_language_auto = g_value_get_boolean (value);
642                 if (priv->accept_language) {
643                         g_free (priv->accept_language);
644                         priv->accept_language = NULL;
645                 }
646
647                 /* Get languages from system if needed */
648                 if (priv->accept_language_auto)
649                         priv->accept_language = accept_languages_from_system ();
650                 break;
651         case PROP_IDLE_TIMEOUT:
652                 priv->idle_timeout = g_value_get_uint (value);
653                 break;
654         case PROP_ADD_FEATURE:
655                 soup_session_add_feature (session, g_value_get_object (value));
656                 break;
657         case PROP_ADD_FEATURE_BY_TYPE:
658                 soup_session_add_feature_by_type (session, g_value_get_gtype (value));
659                 break;
660         case PROP_REMOVE_FEATURE_BY_TYPE:
661                 soup_session_remove_feature_by_type (session, g_value_get_gtype (value));
662                 break;
663         case PROP_HTTP_ALIASES:
664                 set_aliases (&priv->http_aliases, g_value_get_boxed (value));
665                 break;
666         case PROP_HTTPS_ALIASES:
667                 set_aliases (&priv->https_aliases, g_value_get_boxed (value));
668                 break;
669         default:
670                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
671                 break;
672         }
673 }
674
675 static void
676 soup_session_get_property (GObject *object, guint prop_id,
677                            GValue *value, GParamSpec *pspec)
678 {
679         SoupSession *session = SOUP_SESSION (object);
680         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
681         SoupSessionFeature *feature;
682         GTlsDatabase *tlsdb;
683
684         switch (prop_id) {
685         case PROP_PROXY_URI:
686                 feature = soup_session_get_feature (session, SOUP_TYPE_PROXY_RESOLVER_STATIC);
687                 if (feature) {
688                         g_object_get_property (G_OBJECT (feature),
689                                                SOUP_PROXY_RESOLVER_STATIC_PROXY_URI,
690                                                value);
691                 } else
692                         g_value_set_boxed (value, NULL);
693                 break;
694         case PROP_MAX_CONNS:
695                 g_value_set_int (value, priv->max_conns);
696                 break;
697         case PROP_MAX_CONNS_PER_HOST:
698                 g_value_set_int (value, priv->max_conns_per_host);
699                 break;
700         case PROP_USE_NTLM:
701                 feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER);
702                 if (feature)
703                         g_value_set_boolean (value, soup_session_feature_has_feature (feature, SOUP_TYPE_AUTH_NTLM));
704                 else
705                         g_value_set_boolean (value, FALSE);
706                 break;
707         case PROP_SSL_CA_FILE:
708                 g_value_set_string (value, priv->ssl_ca_file);
709                 break;
710         case PROP_SSL_USE_SYSTEM_CA_FILE:
711                 tlsdb = g_tls_backend_get_default_database (g_tls_backend_get_default ());
712                 g_value_set_boolean (value, priv->tlsdb == tlsdb);
713                 g_object_unref (tlsdb);
714                 break;
715         case PROP_TLS_DATABASE:
716                 g_value_set_object (value, priv->tlsdb);
717                 break;
718         case PROP_SSL_STRICT:
719                 g_value_set_boolean (value, priv->ssl_strict);
720                 break;
721         case PROP_ASYNC_CONTEXT:
722                 g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL);
723                 break;
724         case PROP_USE_THREAD_CONTEXT:
725                 g_value_set_boolean (value, priv->use_thread_context);
726                 break;
727         case PROP_TIMEOUT:
728                 g_value_set_uint (value, priv->io_timeout);
729                 break;
730         case PROP_USER_AGENT:
731                 g_value_set_string (value, priv->user_agent);
732                 break;
733         case PROP_ACCEPT_LANGUAGE:
734                 g_value_set_string (value, priv->accept_language);
735                 break;
736         case PROP_ACCEPT_LANGUAGE_AUTO:
737                 g_value_set_boolean (value, priv->accept_language_auto);
738                 break;
739         case PROP_IDLE_TIMEOUT:
740                 g_value_set_uint (value, priv->idle_timeout);
741                 break;
742         case PROP_HTTP_ALIASES:
743                 g_value_set_boxed (value, priv->http_aliases);
744                 break;
745         case PROP_HTTPS_ALIASES:
746                 g_value_set_boxed (value, priv->https_aliases);
747                 break;
748         default:
749                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
750                 break;
751         }
752 }
753
754 /**
755  * soup_session_new:
756  *
757  * Creates a #SoupSession with the default options.
758  *
759  * Return value: the new session.
760  *
761  * Since: 2.42
762  */
763 SoupSession *
764 soup_session_new (void)
765 {
766         return g_object_new (SOUP_TYPE_SESSION, NULL);
767 }
768
769 /**
770  * soup_session_new_with_options:
771  * @optname1: name of first property to set
772  * @...: value of @optname1, followed by additional property/value pairs
773  *
774  * Creates a #SoupSession with the specified options.
775  *
776  * Return value: the new session.
777  *
778  * Since: 2.42
779  */
780 SoupSession *
781 soup_session_new_with_options (const char *optname1,
782                                ...)
783 {
784         SoupSession *session;
785         va_list ap;
786
787         va_start (ap, optname1);
788         session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION,
789                                                       optname1, ap);
790         va_end (ap);
791
792         return session;
793 }
794
795 static gboolean
796 uri_is_http (SoupSessionPrivate *priv, SoupURI *uri)
797 {
798         int i;
799
800         if (uri->scheme == SOUP_URI_SCHEME_HTTP)
801                 return TRUE;
802         else if (uri->scheme == SOUP_URI_SCHEME_HTTPS)
803                 return FALSE;
804         else if (!priv->http_aliases)
805                 return FALSE;
806
807         for (i = 0; priv->http_aliases[i]; i++) {
808                 if (uri->scheme == priv->http_aliases[i])
809                         return TRUE;
810         }
811
812         if (!priv->http_aliases[1] && !strcmp (priv->http_aliases[0], "*"))
813                 return TRUE;
814         else
815                 return FALSE;
816 }
817
818 static gboolean
819 uri_is_https (SoupSessionPrivate *priv, SoupURI *uri)
820 {
821         int i;
822
823         if (uri->scheme == SOUP_URI_SCHEME_HTTPS)
824                 return TRUE;
825         else if (uri->scheme == SOUP_URI_SCHEME_HTTP)
826                 return FALSE;
827         else if (!priv->https_aliases)
828                 return FALSE;
829
830         for (i = 0; priv->https_aliases[i]; i++) {
831                 if (uri->scheme == priv->https_aliases[i])
832                         return TRUE;
833         }
834
835         return FALSE;
836 }
837
838 /**
839  * soup_session_get_async_context:
840  * @session: a #SoupSession
841  *
842  * Gets @session's #SoupSession:async-context. This does not add a ref
843  * to the context, so you will need to ref it yourself if you want it
844  * to outlive its session.
845  *
846  * For a modern #SoupSession, this will always just return the
847  * thread-default #GMainContext, and so is not especially useful.
848  *
849  * Return value: (transfer none): @session's #GMainContext, which may
850  * be %NULL
851  **/
852 GMainContext *
853 soup_session_get_async_context (SoupSession *session)
854 {
855         SoupSessionPrivate *priv;
856
857         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
858         priv = SOUP_SESSION_GET_PRIVATE (session);
859
860         if (priv->use_thread_context)
861                 return g_main_context_get_thread_default ();
862         else
863                 return priv->async_context;
864 }
865
866 /* Hosts */
867
868 /* Note that we can't use soup_uri_host_hash() and soup_uri_host_equal()
869  * because we want to ignore the protocol; http://example.com and
870  * webcal://example.com are the same host.
871  */
872 static guint
873 soup_host_uri_hash (gconstpointer key)
874 {
875         const SoupURI *uri = key;
876
877         g_return_val_if_fail (uri != NULL && uri->host != NULL, 0);
878
879         return uri->port + soup_str_case_hash (uri->host);
880 }
881
882 static gboolean
883 soup_host_uri_equal (gconstpointer v1, gconstpointer v2)
884 {
885         const SoupURI *one = v1;
886         const SoupURI *two = v2;
887
888         g_return_val_if_fail (one != NULL && two != NULL, one == two);
889         g_return_val_if_fail (one->host != NULL && two->host != NULL, one->host == two->host);
890
891         if (one->port != two->port)
892                 return FALSE;
893
894         return g_ascii_strcasecmp (one->host, two->host) == 0;
895 }
896
897
898 static SoupSessionHost *
899 soup_session_host_new (SoupSession *session, SoupURI *uri)
900 {
901         SoupSessionHost *host;
902
903         host = g_slice_new0 (SoupSessionHost);
904         host->uri = soup_uri_copy_host (uri);
905         if (host->uri->scheme != SOUP_URI_SCHEME_HTTP &&
906             host->uri->scheme != SOUP_URI_SCHEME_HTTPS) {
907                 SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
908
909                 if (uri_is_https (priv, host->uri))
910                         host->uri->scheme = SOUP_URI_SCHEME_HTTPS;
911                 else
912                         host->uri->scheme = SOUP_URI_SCHEME_HTTP;
913         }
914
915         host->addr = g_object_new (SOUP_TYPE_ADDRESS,
916                                    SOUP_ADDRESS_NAME, host->uri->host,
917                                    SOUP_ADDRESS_PORT, host->uri->port,
918                                    SOUP_ADDRESS_PROTOCOL, host->uri->scheme,
919                                    NULL);
920         host->keep_alive_src = NULL;
921         host->session = session;
922
923         return host;
924 }
925
926 /* Requires conn_lock to be locked */
927 static SoupSessionHost *
928 get_host_for_uri (SoupSession *session, SoupURI *uri)
929 {
930         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
931         SoupSessionHost *host;
932
933         if (uri_is_https (priv, uri))
934                 host = g_hash_table_lookup (priv->https_hosts, uri);
935         else
936                 host = g_hash_table_lookup (priv->http_hosts, uri);
937         if (host)
938                 return host;
939
940         host = soup_session_host_new (session, uri);
941
942         if (uri_is_https (priv, uri))
943                 g_hash_table_insert (priv->https_hosts, host->uri, host);
944         else
945                 g_hash_table_insert (priv->http_hosts, host->uri, host);
946
947         return host;
948 }
949
950 /* Requires conn_lock to be locked */
951 static SoupSessionHost *
952 get_host_for_message (SoupSession *session, SoupMessage *msg)
953 {
954         return get_host_for_uri (session, soup_message_get_uri (msg));
955 }
956
957 static void
958 free_host (SoupSessionHost *host)
959 {
960         g_warn_if_fail (host->connections == NULL);
961
962         if (host->keep_alive_src) {
963                 g_source_destroy (host->keep_alive_src);
964                 g_source_unref (host->keep_alive_src);
965         }
966
967         soup_uri_free (host->uri);
968         g_object_unref (host->addr);
969         g_slice_free (SoupSessionHost, host);
970 }
971
972 static void
973 auth_manager_authenticate (SoupAuthManager *manager, SoupMessage *msg,
974                            SoupAuth *auth, gboolean retrying,
975                            gpointer session)
976 {
977         g_signal_emit (session, signals[AUTHENTICATE], 0, msg, auth, retrying);
978 }
979
980 #define SOUP_SESSION_WOULD_REDIRECT_AS_GET(session, msg) \
981         ((msg)->status_code == SOUP_STATUS_SEE_OTHER || \
982          ((msg)->status_code == SOUP_STATUS_FOUND && \
983           !SOUP_METHOD_IS_SAFE ((msg)->method)) || \
984          ((msg)->status_code == SOUP_STATUS_MOVED_PERMANENTLY && \
985           (msg)->method == SOUP_METHOD_POST))
986
987 #define SOUP_SESSION_WOULD_REDIRECT_AS_SAFE(session, msg) \
988         (((msg)->status_code == SOUP_STATUS_MOVED_PERMANENTLY || \
989           (msg)->status_code == SOUP_STATUS_TEMPORARY_REDIRECT || \
990           (msg)->status_code == SOUP_STATUS_FOUND) && \
991          SOUP_METHOD_IS_SAFE ((msg)->method))
992
993 static inline SoupURI *
994 redirection_uri (SoupMessage *msg)
995 {
996         const char *new_loc;
997         SoupURI *new_uri;
998
999         new_loc = soup_message_headers_get_one (msg->response_headers,
1000                                                 "Location");
1001         if (!new_loc)
1002                 return NULL;
1003         new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
1004         if (!new_uri || !new_uri->host) {
1005                 if (new_uri)
1006                         soup_uri_free (new_uri);
1007                 return NULL;
1008         }
1009
1010         return new_uri;
1011 }
1012
1013 /**
1014  * soup_session_would_redirect:
1015  * @session: a #SoupSession
1016  * @msg: a #SoupMessage that has response headers
1017  *
1018  * Checks if @msg contains a response that would cause @session to
1019  * redirect it to a new URL (ignoring @msg's %SOUP_MESSAGE_NO_REDIRECT
1020  * flag, and the number of times it has already been redirected).
1021  *
1022  * Return value: whether @msg would be redirected
1023  *
1024  * Since: 2.38
1025  */
1026 gboolean
1027 soup_session_would_redirect (SoupSession *session, SoupMessage *msg)
1028 {
1029         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1030         SoupURI *new_uri;
1031
1032         /* It must have an appropriate status code and method */
1033         if (!SOUP_SESSION_WOULD_REDIRECT_AS_GET (session, msg) &&
1034             !SOUP_SESSION_WOULD_REDIRECT_AS_SAFE (session, msg))
1035                 return FALSE;
1036
1037         /* and a Location header that parses to an http URI */
1038         if (!soup_message_headers_get_one (msg->response_headers, "Location"))
1039                 return FALSE;
1040         new_uri = redirection_uri (msg);
1041         if (!new_uri)
1042                 return FALSE;
1043         if (!new_uri->host || !*new_uri->host ||
1044             (!uri_is_http (priv, new_uri) && !uri_is_https (priv, new_uri))) {
1045                 soup_uri_free (new_uri);
1046                 return FALSE;
1047         }
1048
1049         soup_uri_free (new_uri);
1050         return TRUE;
1051 }
1052
1053 /**
1054  * soup_session_redirect_message:
1055  * @session: the session
1056  * @msg: a #SoupMessage that has received a 3xx response
1057  *
1058  * Updates @msg's URI according to its status code and "Location"
1059  * header, and requeues it on @session. Use this when you have set
1060  * %SOUP_MESSAGE_NO_REDIRECT on a message, but have decided to allow a
1061  * particular redirection to occur, or if you want to allow a
1062  * redirection that #SoupSession will not perform automatically (eg,
1063  * redirecting a non-safe method such as DELETE).
1064  *
1065  * If @msg's status code indicates that it should be retried as a GET
1066  * request, then @msg will be modified accordingly.
1067  *
1068  * If @msg has already been redirected too many times, this will
1069  * cause it to fail with %SOUP_STATUS_TOO_MANY_REDIRECTS.
1070  *
1071  * Return value: %TRUE if a redirection was applied, %FALSE if not
1072  * (eg, because there was no Location header, or it could not be
1073  * parsed).
1074  *
1075  * Since: 2.38
1076  */
1077 gboolean
1078 soup_session_redirect_message (SoupSession *session, SoupMessage *msg)
1079 {
1080         SoupMessageQueueItem *item;
1081         SoupURI *new_uri;
1082
1083         new_uri = redirection_uri (msg);
1084         if (!new_uri)
1085                 return FALSE;
1086
1087         item = soup_message_queue_lookup (soup_session_get_queue (session), msg);
1088         if (!item) {
1089                 soup_uri_free (new_uri);
1090                 return FALSE;
1091         }
1092         if (item->redirection_count >= SOUP_SESSION_MAX_REDIRECTION_COUNT) {
1093                 soup_uri_free (new_uri);
1094                 soup_session_cancel_message (session, msg, SOUP_STATUS_TOO_MANY_REDIRECTS);
1095                 soup_message_queue_item_unref (item);
1096                 return FALSE;
1097         }
1098         item->redirection_count++;
1099         soup_message_queue_item_unref (item);
1100
1101         if (SOUP_SESSION_WOULD_REDIRECT_AS_GET (session, msg)) {
1102                 if (msg->method != SOUP_METHOD_HEAD) {
1103                         g_object_set (msg,
1104                                       SOUP_MESSAGE_METHOD, SOUP_METHOD_GET,
1105                                       NULL);
1106                 }
1107                 soup_message_set_request (msg, NULL,
1108                                           SOUP_MEMORY_STATIC, NULL, 0);
1109                 soup_message_headers_set_encoding (msg->request_headers,
1110                                                    SOUP_ENCODING_NONE);
1111         }
1112
1113         soup_message_set_uri (msg, new_uri);
1114         soup_uri_free (new_uri);
1115
1116         soup_session_requeue_message (session, msg);
1117         return TRUE;
1118 }
1119
1120 static void
1121 redirect_handler (SoupMessage *msg, gpointer user_data)
1122 {
1123         SoupMessageQueueItem *item = user_data;
1124         SoupSession *session = item->session;
1125
1126         if (!soup_session_would_redirect (session, msg)) {
1127                 SoupURI *new_uri = redirection_uri (msg);
1128                 gboolean invalid = !new_uri || !new_uri->host;
1129
1130                 if (new_uri)
1131                         soup_uri_free (new_uri);
1132                 if (invalid && !item->new_api) {
1133                         soup_message_set_status_full (msg,
1134                                                       SOUP_STATUS_MALFORMED,
1135                                                       "Invalid Redirect URL");
1136                 }
1137                 return;
1138         }
1139
1140         soup_session_redirect_message (session, msg);
1141 }
1142
1143 static void
1144 proxy_connection_event (SoupConnection      *conn,
1145                         GSocketClientEvent   event,
1146                         GIOStream           *connection,
1147                         gpointer             user_data)
1148 {
1149         SoupMessageQueueItem *item = user_data;
1150
1151         soup_message_network_event (item->msg, event, connection);
1152 }
1153
1154 static void
1155 soup_session_set_item_connection (SoupSession          *session,
1156                                   SoupMessageQueueItem *item,
1157                                   SoupConnection       *conn)
1158 {
1159         if (item->conn) {
1160                 g_signal_handlers_disconnect_by_func (item->conn, proxy_connection_event, item);
1161                 g_object_unref (item->conn);
1162         }
1163
1164         item->conn = conn;
1165         soup_message_set_connection (item->msg, conn);
1166
1167         if (item->conn) {
1168                 g_object_ref (item->conn);
1169                 g_signal_connect (item->conn, "event",
1170                                   G_CALLBACK (proxy_connection_event), item);
1171         }
1172 }
1173
1174 static void
1175 message_restarted (SoupMessage *msg, gpointer user_data)
1176 {
1177         SoupMessageQueueItem *item = user_data;
1178
1179         if (item->conn &&
1180             (!soup_message_is_keepalive (msg) ||
1181              SOUP_STATUS_IS_REDIRECTION (msg->status_code))) {
1182                 if (soup_connection_get_state (item->conn) == SOUP_CONNECTION_IN_USE)
1183                         soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE);
1184                 soup_session_set_item_connection (item->session, item, NULL);
1185         }
1186
1187         soup_message_cleanup_response (msg);
1188 }
1189
1190 SoupMessageQueueItem *
1191 soup_session_append_queue_item (SoupSession *session, SoupMessage *msg,
1192                                 gboolean async, gboolean new_api,
1193                                 SoupSessionCallback callback, gpointer user_data)
1194 {
1195         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1196         SoupMessageQueueItem *item;
1197         SoupSessionHost *host;
1198
1199         soup_message_cleanup_response (msg);
1200
1201         item = soup_message_queue_append (priv->queue, msg, callback, user_data);
1202         item->async = async;
1203         item->new_api = new_api;
1204
1205         g_mutex_lock (&priv->conn_lock);
1206         host = get_host_for_message (session, item->msg);
1207         host->num_messages++;
1208         g_mutex_unlock (&priv->conn_lock);
1209
1210         if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_NO_REDIRECT)) {
1211                 soup_message_add_header_handler (
1212                         msg, "got_body", "Location",
1213                         G_CALLBACK (redirect_handler), item);
1214         }
1215         g_signal_connect (msg, "restarted",
1216                           G_CALLBACK (message_restarted), item);
1217
1218         g_signal_emit (session, signals[REQUEST_QUEUED], 0, msg);
1219
1220         soup_message_queue_item_ref (item);
1221         return item;
1222 }
1223
1224 static void
1225 soup_session_send_queue_item (SoupSession *session,
1226                               SoupMessageQueueItem *item,
1227                               SoupMessageCompletionFn completion_cb)
1228 {
1229         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1230         const char *conn_header;
1231
1232         if (priv->user_agent) {
1233                 soup_message_headers_replace (item->msg->request_headers,
1234                                               "User-Agent", priv->user_agent);
1235         }
1236
1237         if (priv->accept_language &&
1238             !soup_message_headers_get_list (item->msg->request_headers,
1239                                             "Accept-Language")) {
1240                 soup_message_headers_append (item->msg->request_headers,
1241                                              "Accept-Language",
1242                                              priv->accept_language);
1243         }
1244
1245         /* Force keep alive connections for HTTP 1.0. Performance will
1246          * improve when issuing multiple requests to the same host in
1247          * a short period of time, as we wouldn't need to establish
1248          * new connections. Keep alive is implicit for HTTP 1.1.
1249          */
1250         conn_header = soup_message_headers_get_list (item->msg->request_headers, "Connection");
1251         if (!conn_header ||
1252             (!soup_header_contains (conn_header, "Keep-Alive") &&
1253              !soup_header_contains (conn_header, "close")))
1254                 soup_message_headers_append (item->msg->request_headers,
1255                                              "Connection", "Keep-Alive");
1256
1257         g_signal_emit (session, signals[REQUEST_STARTED], 0,
1258                        item->msg, soup_connection_get_socket (item->conn));
1259         if (item->state == SOUP_MESSAGE_RUNNING)
1260                 soup_connection_send_request (item->conn, item, completion_cb, item);
1261 }
1262
1263 static gboolean
1264 soup_session_cleanup_connections (SoupSession *session,
1265                                   gboolean     cleanup_idle)
1266 {
1267         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1268         GSList *conns = NULL, *c;
1269         GHashTableIter iter;
1270         gpointer conn, host;
1271         SoupConnectionState state;
1272
1273         g_mutex_lock (&priv->conn_lock);
1274         g_hash_table_iter_init (&iter, priv->conns);
1275         while (g_hash_table_iter_next (&iter, &conn, &host)) {
1276                 state = soup_connection_get_state (conn);
1277                 if (state == SOUP_CONNECTION_REMOTE_DISCONNECTED ||
1278                     (cleanup_idle && state == SOUP_CONNECTION_IDLE)) {
1279                         conns = g_slist_prepend (conns, g_object_ref (conn));
1280                         g_hash_table_iter_remove (&iter);
1281                         drop_connection (session, host, conn);
1282                 }
1283         }
1284         g_mutex_unlock (&priv->conn_lock);
1285
1286         if (!conns)
1287                 return FALSE;
1288
1289         for (c = conns; c; c = c->next) {
1290                 conn = c->data;
1291                 soup_connection_disconnect (conn);
1292                 g_object_unref (conn);
1293         }
1294         g_slist_free (conns);
1295
1296         return TRUE;
1297 }
1298
1299 static gboolean
1300 free_unused_host (gpointer user_data)
1301 {
1302         SoupSessionHost *host = (SoupSessionHost *) user_data;
1303         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (host->session);
1304
1305         g_mutex_lock (&priv->conn_lock);
1306
1307         /* In a multithreaded session, a connection might have been
1308          * added while we were waiting for conn_lock.
1309          */
1310         if (host->connections) {
1311                 g_mutex_unlock (&priv->conn_lock);
1312                 return FALSE;
1313         }
1314
1315         /* This will free the host in addition to removing it from the
1316          * hash table
1317          */
1318         if (host->uri->scheme == SOUP_URI_SCHEME_HTTPS)
1319                 g_hash_table_remove (priv->https_hosts, host->uri);
1320         else
1321                 g_hash_table_remove (priv->http_hosts, host->uri);
1322         g_mutex_unlock (&priv->conn_lock);
1323
1324         return FALSE;
1325 }
1326
1327 static void
1328 drop_connection (SoupSession *session, SoupSessionHost *host, SoupConnection *conn)
1329 {
1330         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1331
1332         /* Note: caller must hold conn_lock, and must remove @conn
1333          * from priv->conns itself.
1334          */
1335
1336         if (host) {
1337                 host->connections = g_slist_remove (host->connections, conn);
1338                 host->num_conns--;
1339
1340                 /* Free the SoupHost (and its SoupAddress) if there
1341                  * has not been any new connection to the host during
1342                  * the last HOST_KEEP_ALIVE msecs.
1343                  */
1344                 if (host->num_conns == 0) {
1345                         g_assert (host->keep_alive_src == NULL);
1346                         host->keep_alive_src = soup_add_timeout (priv->async_context,
1347                                                                  HOST_KEEP_ALIVE,
1348                                                                  free_unused_host,
1349                                                                  host);
1350                         host->keep_alive_src = g_source_ref (host->keep_alive_src);
1351                 }
1352
1353                 if (soup_connection_get_ssl_fallback (conn))
1354                         host->ssl_fallback = TRUE;
1355         }
1356
1357         g_signal_handlers_disconnect_by_func (conn, connection_disconnected, session);
1358         g_signal_handlers_disconnect_by_func (conn, connection_state_changed, session);
1359         priv->num_conns--;
1360
1361         g_object_unref (conn);
1362 }
1363
1364 static void
1365 connection_disconnected (SoupConnection *conn, gpointer user_data)
1366 {
1367         SoupSession *session = user_data;
1368         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1369         SoupSessionHost *host;
1370
1371         g_mutex_lock (&priv->conn_lock);
1372
1373         host = g_hash_table_lookup (priv->conns, conn);
1374         if (host)
1375                 g_hash_table_remove (priv->conns, conn);
1376         drop_connection (session, host, conn);
1377
1378         g_mutex_unlock (&priv->conn_lock);
1379
1380         soup_session_kick_queue (session);
1381 }
1382
1383 static void
1384 connection_state_changed (GObject *object, GParamSpec *param, gpointer user_data)
1385 {
1386         SoupSession *session = user_data;
1387         SoupConnection *conn = SOUP_CONNECTION (object);
1388
1389         if (soup_connection_get_state (conn) == SOUP_CONNECTION_IDLE)
1390                 soup_session_kick_queue (session);
1391 }
1392
1393 SoupMessageQueue *
1394 soup_session_get_queue (SoupSession *session)
1395 {
1396         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1397
1398         return priv->queue;
1399 }
1400
1401 static void
1402 soup_session_unqueue_item (SoupSession          *session,
1403                            SoupMessageQueueItem *item)
1404 {
1405         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1406         SoupSessionHost *host;
1407
1408         if (item->conn) {
1409                 if (item->msg->method != SOUP_METHOD_CONNECT ||
1410                     !SOUP_STATUS_IS_SUCCESSFUL (item->msg->status_code))
1411                         soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE);
1412                 soup_session_set_item_connection (session, item, NULL);
1413         }
1414
1415         if (item->state != SOUP_MESSAGE_FINISHED) {
1416                 g_warning ("finished an item with state %d", item->state);
1417                 return;
1418         }
1419
1420         soup_message_queue_remove (priv->queue, item);
1421
1422         g_mutex_lock (&priv->conn_lock);
1423         host = get_host_for_message (session, item->msg);
1424         host->num_messages--;
1425         g_mutex_unlock (&priv->conn_lock);
1426
1427         /* g_signal_handlers_disconnect_by_func doesn't work if you
1428          * have a metamarshal, meaning it doesn't work with
1429          * soup_message_add_header_handler()
1430          */
1431         g_signal_handlers_disconnect_matched (item->msg, G_SIGNAL_MATCH_DATA,
1432                                               0, 0, NULL, NULL, item);
1433         g_signal_emit (session, signals[REQUEST_UNQUEUED], 0, item->msg);
1434         soup_message_queue_item_unref (item);
1435 }
1436
1437 static void
1438 soup_session_set_item_status (SoupSession          *session,
1439                               SoupMessageQueueItem *item,
1440                               guint                 status_code)
1441 {
1442         SoupURI *uri;
1443         char *msg;
1444
1445         switch (status_code) {
1446         case SOUP_STATUS_CANT_RESOLVE:
1447         case SOUP_STATUS_CANT_CONNECT:
1448                 uri = soup_message_get_uri (item->msg);
1449                 msg = g_strdup_printf ("%s (%s)",
1450                                        soup_status_get_phrase (status_code),
1451                                        uri->host);
1452                 soup_message_set_status_full (item->msg, status_code, msg);
1453                 g_free (msg);
1454                 break;
1455
1456         case SOUP_STATUS_CANT_RESOLVE_PROXY:
1457         case SOUP_STATUS_CANT_CONNECT_PROXY:
1458                 if (item->proxy_uri && item->proxy_uri->host) {
1459                         msg = g_strdup_printf ("%s (%s)",
1460                                                soup_status_get_phrase (status_code),
1461                                                item->proxy_uri->host);
1462                         soup_message_set_status_full (item->msg, status_code, msg);
1463                         g_free (msg);
1464                         break;
1465                 }
1466                 soup_message_set_status (item->msg, status_code);
1467                 break;
1468
1469         case SOUP_STATUS_SSL_FAILED:
1470                 if (!g_tls_backend_supports_tls (g_tls_backend_get_default ())) {
1471                         soup_message_set_status_full (item->msg, status_code,
1472                                                       "TLS/SSL support not available; install glib-networking");
1473                 } else
1474                         soup_message_set_status (item->msg, status_code);
1475                 break;
1476
1477         default:
1478                 soup_message_set_status (item->msg, status_code);
1479                 break;
1480         }
1481 }
1482
1483
1484 static void
1485 message_completed (SoupMessage *msg, gpointer user_data)
1486 {
1487         SoupMessageQueueItem *item = user_data;
1488
1489         if (item->async)
1490                 soup_session_kick_queue (item->session);
1491
1492         if (item->state != SOUP_MESSAGE_RESTARTING) {
1493                 item->state = SOUP_MESSAGE_FINISHING;
1494
1495                 if (item->new_api && !item->async)
1496                         soup_session_process_queue_item (item->session, item, NULL, TRUE);
1497         }
1498 }
1499
1500 static void
1501 tunnel_complete (SoupConnection *conn, guint status, gpointer user_data)
1502 {
1503         SoupMessageQueueItem *tunnel_item = user_data;
1504         SoupMessageQueueItem *item = tunnel_item->related;
1505         SoupSession *session = tunnel_item->session;
1506
1507         soup_message_finished (tunnel_item->msg);
1508         soup_message_queue_item_unref (tunnel_item);
1509
1510         if (item->msg->status_code)
1511                 item->state = SOUP_MESSAGE_FINISHING;
1512         else
1513                 soup_message_set_https_status (item->msg, item->conn);
1514
1515         if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
1516                 soup_session_set_item_connection (session, item, NULL);
1517                 soup_session_set_item_status (session, item, status);
1518         }
1519
1520         item->state = SOUP_MESSAGE_READY;
1521         if (item->async)
1522                 soup_session_kick_queue (session);
1523         soup_message_queue_item_unref (item);
1524 }
1525
1526 static void
1527 tunnel_message_completed (SoupMessage *msg, gpointer user_data)
1528 {
1529         SoupMessageQueueItem *tunnel_item = user_data;
1530         SoupMessageQueueItem *item = tunnel_item->related;
1531         SoupSession *session = tunnel_item->session;
1532         guint status;
1533
1534         if (tunnel_item->state == SOUP_MESSAGE_RESTARTING) {
1535                 soup_message_restarted (msg);
1536                 if (tunnel_item->conn) {
1537                         tunnel_item->state = SOUP_MESSAGE_RUNNING;
1538                         soup_session_send_queue_item (session, tunnel_item,
1539                                                       tunnel_message_completed);
1540                         return;
1541                 }
1542
1543                 soup_message_set_status (msg, SOUP_STATUS_TRY_AGAIN);
1544         }
1545
1546         tunnel_item->state = SOUP_MESSAGE_FINISHED;
1547         soup_session_unqueue_item (session, tunnel_item);
1548
1549         status = tunnel_item->msg->status_code;
1550         if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
1551                 tunnel_complete (item->conn, status, tunnel_item);
1552                 return;
1553         }
1554
1555         if (tunnel_item->async) {
1556                 soup_connection_start_ssl_async (item->conn, item->cancellable,
1557                                                  tunnel_complete, tunnel_item);
1558         } else {
1559                 status = soup_connection_start_ssl_sync (item->conn, item->cancellable);
1560                 tunnel_complete (item->conn, status, tunnel_item);
1561         }
1562 }
1563
1564 static void
1565 tunnel_connect (SoupMessageQueueItem *item)
1566 {
1567         SoupSession *session = item->session;
1568         SoupMessageQueueItem *tunnel_item;
1569         SoupURI *uri;
1570         SoupMessage *msg;
1571
1572         item->state = SOUP_MESSAGE_TUNNELING;
1573
1574         uri = soup_connection_get_remote_uri (item->conn);
1575         msg = soup_message_new_from_uri (SOUP_METHOD_CONNECT, uri);
1576         soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
1577
1578         tunnel_item = soup_session_append_queue_item (session, msg,
1579                                                       item->async, FALSE,
1580                                                       NULL, NULL);
1581         g_object_unref (msg);
1582         tunnel_item->related = item;
1583         soup_message_queue_item_ref (item);
1584         soup_session_set_item_connection (session, tunnel_item, item->conn);
1585         tunnel_item->state = SOUP_MESSAGE_RUNNING;
1586
1587         g_signal_emit (session, signals[TUNNELING], 0, tunnel_item->conn);
1588
1589         soup_session_send_queue_item (session, tunnel_item,
1590                                       tunnel_message_completed);
1591 }
1592
1593 static void
1594 got_connection (SoupConnection *conn, guint status, gpointer user_data)
1595 {
1596         SoupMessageQueueItem *item = user_data;
1597         SoupSession *session = item->session;
1598
1599         if (status != SOUP_STATUS_OK) {
1600                 if (item->state == SOUP_MESSAGE_CONNECTING) {
1601                         soup_session_set_item_status (session, item, status);
1602                         soup_session_set_item_connection (session, item, NULL);
1603                         item->state = SOUP_MESSAGE_READY;
1604                 }
1605         } else
1606                 item->state = SOUP_MESSAGE_CONNECTED;
1607
1608         if (item->async) {
1609                 if (item->state == SOUP_MESSAGE_CONNECTED ||
1610                     item->state == SOUP_MESSAGE_READY)
1611                         async_run_queue (item->session);
1612                 else
1613                         soup_session_kick_queue (item->session);
1614
1615                 soup_message_queue_item_unref (item);
1616         }
1617 }
1618
1619 /* requires conn_lock */
1620 static SoupConnection *
1621 get_connection_for_host (SoupSession *session,
1622                          SoupMessageQueueItem *item,
1623                          SoupSessionHost *host,
1624                          gboolean need_new_connection,
1625                          gboolean *try_cleanup)
1626 {
1627         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1628         SoupConnection *conn;
1629         GSList *conns;
1630         int num_pending = 0;
1631
1632         if (priv->disposed)
1633                 return FALSE;
1634
1635         if (item->conn) {
1636                 g_return_val_if_fail (soup_connection_get_state (item->conn) != SOUP_CONNECTION_DISCONNECTED, FALSE);
1637                 return item->conn;
1638         }
1639
1640         for (conns = host->connections; conns; conns = conns->next) {
1641                 conn = conns->data;
1642
1643                 if (!need_new_connection && soup_connection_get_state (conn) == SOUP_CONNECTION_IDLE) {
1644                         soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE);
1645                         return conn;
1646                 } else if (soup_connection_get_state (conn) == SOUP_CONNECTION_CONNECTING)
1647                         num_pending++;
1648         }
1649
1650         /* Limit the number of pending connections; num_messages / 2
1651          * is somewhat arbitrary...
1652          */
1653         if (num_pending > host->num_messages / 2)
1654                 return NULL;
1655
1656         if (host->num_conns >= priv->max_conns_per_host) {
1657                 if (need_new_connection)
1658                         *try_cleanup = TRUE;
1659                 return NULL;
1660         }
1661
1662         if (priv->num_conns >= priv->max_conns) {
1663                 *try_cleanup = TRUE;
1664                 return NULL;
1665         }
1666
1667         conn = g_object_new (
1668                 SOUP_TYPE_CONNECTION,
1669                 SOUP_CONNECTION_REMOTE_URI, host->uri,
1670                 SOUP_CONNECTION_PROXY_RESOLVER, soup_session_get_feature (session, SOUP_TYPE_PROXY_URI_RESOLVER),
1671                 SOUP_CONNECTION_SSL, uri_is_https (priv, soup_message_get_uri (item->msg)),
1672                 SOUP_CONNECTION_SSL_CREDENTIALS, priv->tlsdb,
1673                 SOUP_CONNECTION_SSL_STRICT, priv->ssl_strict && (priv->tlsdb != NULL || SOUP_IS_PLAIN_SESSION (session)),
1674                 SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context,
1675                 SOUP_CONNECTION_USE_THREAD_CONTEXT, priv->use_thread_context,
1676                 SOUP_CONNECTION_TIMEOUT, priv->io_timeout,
1677                 SOUP_CONNECTION_IDLE_TIMEOUT, priv->idle_timeout,
1678                 SOUP_CONNECTION_SSL_FALLBACK, host->ssl_fallback,
1679                 NULL);
1680         g_signal_connect (conn, "disconnected",
1681                           G_CALLBACK (connection_disconnected),
1682                           session);
1683         g_signal_connect (conn, "notify::state",
1684                           G_CALLBACK (connection_state_changed),
1685                           session);
1686
1687         /* This is a debugging-related signal, and so can ignore the
1688          * usual rule about not emitting signals while holding
1689          * conn_lock.
1690          */
1691         g_signal_emit (session, signals[CONNECTION_CREATED], 0, conn);
1692
1693         g_hash_table_insert (priv->conns, conn, host);
1694
1695         priv->num_conns++;
1696         host->num_conns++;
1697         host->connections = g_slist_prepend (host->connections, conn);
1698
1699         if (host->keep_alive_src) {
1700                 g_source_destroy (host->keep_alive_src);
1701                 g_source_unref (host->keep_alive_src);
1702                 host->keep_alive_src = NULL;
1703         }
1704
1705         return conn;
1706 }
1707
1708 static gboolean
1709 get_connection (SoupMessageQueueItem *item, gboolean *should_cleanup)
1710 {
1711         SoupSession *session = item->session;
1712         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1713         SoupSessionHost *host;
1714         SoupConnection *conn = NULL;
1715         gboolean my_should_cleanup = FALSE;
1716         gboolean need_new_connection;
1717
1718         need_new_connection =
1719                 (soup_message_get_flags (item->msg) & SOUP_MESSAGE_NEW_CONNECTION) ||
1720                 (!(soup_message_get_flags (item->msg) & SOUP_MESSAGE_IDEMPOTENT) &&
1721                  !SOUP_METHOD_IS_IDEMPOTENT (item->msg->method));
1722
1723         g_mutex_lock (&priv->conn_lock);
1724         host = get_host_for_message (session, item->msg);
1725         while (TRUE) {
1726                 conn = get_connection_for_host (session, item, host,
1727                                                 need_new_connection,
1728                                                 &my_should_cleanup);
1729                 if (conn || item->async)
1730                         break;
1731
1732                 if (my_should_cleanup) {
1733                         g_mutex_unlock (&priv->conn_lock);
1734                         soup_session_cleanup_connections (session, TRUE);
1735                         g_mutex_lock (&priv->conn_lock);
1736
1737                         my_should_cleanup = FALSE;
1738                         continue;
1739                 }
1740
1741                 g_cond_wait (&priv->conn_cond, &priv->conn_lock);
1742         }
1743         g_mutex_unlock (&priv->conn_lock);
1744
1745         if (!conn) {
1746                 if (should_cleanup)
1747                         *should_cleanup = my_should_cleanup;
1748                 return FALSE;
1749         }
1750
1751         soup_session_set_item_connection (session, item, conn);
1752         soup_message_set_https_status (item->msg, item->conn);
1753
1754         if (soup_connection_get_state (item->conn) != SOUP_CONNECTION_NEW) {
1755                 item->state = SOUP_MESSAGE_READY;
1756                 return TRUE;
1757         }
1758
1759         item->state = SOUP_MESSAGE_CONNECTING;
1760
1761         if (item->async) {
1762                 soup_message_queue_item_ref (item);
1763                 soup_connection_connect_async (item->conn, item->cancellable,
1764                                                got_connection, item);
1765                 return FALSE;
1766         } else {
1767                 guint status;
1768
1769                 status = soup_connection_connect_sync (item->conn, item->cancellable);
1770                 got_connection (item->conn, status, item);
1771
1772                 return TRUE;
1773         }
1774 }
1775
1776 void
1777 soup_session_process_queue_item (SoupSession          *session,
1778                                  SoupMessageQueueItem *item,
1779                                  gboolean             *should_cleanup,
1780                                  gboolean              loop)
1781 {
1782         g_assert (item->session == session);
1783
1784         do {
1785                 if (item->paused)
1786                         return;
1787
1788                 switch (item->state) {
1789                 case SOUP_MESSAGE_STARTING:
1790                         if (!get_connection (item, should_cleanup))
1791                                 return;
1792                         break;
1793
1794                 case SOUP_MESSAGE_CONNECTED:
1795                         if (soup_connection_is_tunnelled (item->conn))
1796                                 tunnel_connect (item);
1797                         else
1798                                 item->state = SOUP_MESSAGE_READY;
1799                         break;
1800
1801                 case SOUP_MESSAGE_READY:
1802                         soup_message_set_https_status (item->msg, item->conn);
1803                         if (item->msg->status_code) {
1804                                 if (item->msg->status_code == SOUP_STATUS_TRY_AGAIN) {
1805                                         soup_message_cleanup_response (item->msg);
1806                                         item->state = SOUP_MESSAGE_STARTING;
1807                                 } else
1808                                         item->state = SOUP_MESSAGE_FINISHING;
1809                                 break;
1810                         }
1811
1812                         item->state = SOUP_MESSAGE_RUNNING;
1813
1814                         soup_session_send_queue_item (session, item, message_completed);
1815
1816                         if (item->new_api) {
1817                                 if (item->async)
1818                                         async_send_request_running (session, item);
1819                                 return;
1820                         }
1821                         break;
1822
1823                 case SOUP_MESSAGE_RUNNING:
1824                         if (item->async)
1825                                 return;
1826
1827                         g_warn_if_fail (item->new_api);
1828                         item->state = SOUP_MESSAGE_FINISHING;
1829                         break;
1830
1831                 case SOUP_MESSAGE_CACHED:
1832                         /* Will be handled elsewhere */
1833                         return;
1834
1835                 case SOUP_MESSAGE_RESTARTING:
1836                         item->state = SOUP_MESSAGE_STARTING;
1837                         soup_message_restarted (item->msg);
1838                         break;
1839
1840                 case SOUP_MESSAGE_FINISHING:
1841                         item->state = SOUP_MESSAGE_FINISHED;
1842                         soup_message_finished (item->msg);
1843                         if (item->state != SOUP_MESSAGE_FINISHED) {
1844                                 g_return_if_fail (!item->new_api);
1845                                 break;
1846                         }
1847
1848                         soup_message_queue_item_ref (item);
1849                         soup_session_unqueue_item (session, item);
1850                         if (item->async && item->callback)
1851                                 item->callback (session, item->msg, item->callback_data);
1852                         soup_message_queue_item_unref (item);
1853                         return;
1854
1855                 default:
1856                         /* Nothing to do with this message in any
1857                          * other state.
1858                          */
1859                         g_warn_if_fail (item->async);
1860                         return;
1861                 }
1862         } while (loop && item->state != SOUP_MESSAGE_FINISHED);
1863 }
1864
1865 static void
1866 async_run_queue (SoupSession *session)
1867 {
1868         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1869         SoupMessageQueueItem *item;
1870         SoupMessage *msg;
1871         gboolean try_cleanup = TRUE, should_cleanup = FALSE;
1872
1873         g_object_ref (session);
1874         soup_session_cleanup_connections (session, FALSE);
1875
1876  try_again:
1877         for (item = soup_message_queue_first (priv->queue);
1878              item;
1879              item = soup_message_queue_next (priv->queue, item)) {
1880                 msg = item->msg;
1881
1882                 /* CONNECT messages are handled specially */
1883                 if (msg->method == SOUP_METHOD_CONNECT)
1884                         continue;
1885
1886                 if (item->async_context != soup_session_get_async_context (session))
1887                         continue;
1888
1889                 soup_session_process_queue_item (session, item, &should_cleanup, TRUE);
1890         }
1891
1892         if (try_cleanup && should_cleanup) {
1893                 /* There is at least one message in the queue that
1894                  * could be sent if we cleanupd an idle connection from
1895                  * some other server.
1896                  */
1897                 if (soup_session_cleanup_connections (session, TRUE)) {
1898                         try_cleanup = should_cleanup = FALSE;
1899                         goto try_again;
1900                 }
1901         }
1902
1903         g_object_unref (session);
1904 }
1905
1906 static gboolean
1907 idle_run_queue (gpointer user_data)
1908 {
1909         SoupSessionPrivate *priv = user_data;
1910         GSource *source;
1911
1912         if (priv->disposed)
1913                 return FALSE;
1914
1915         source = g_main_current_source ();
1916         priv->run_queue_sources = g_slist_remove (priv->run_queue_sources, source);
1917
1918         /* Ensure that the source is destroyed before running the queue */
1919         g_source_destroy (source);
1920         g_source_unref (source);
1921
1922         g_assert (priv->session);
1923         async_run_queue (priv->session);
1924         return FALSE;
1925 }
1926
1927 /**
1928  * SoupSessionCallback:
1929  * @session: the session
1930  * @msg: the message that has finished
1931  * @user_data: the data passed to soup_session_queue_message
1932  *
1933  * Prototype for the callback passed to soup_session_queue_message(),
1934  * qv.
1935  **/
1936
1937 static void
1938 soup_session_real_queue_message (SoupSession *session, SoupMessage *msg,
1939                                  SoupSessionCallback callback, gpointer user_data)
1940 {
1941         SoupMessageQueueItem *item;
1942
1943         item = soup_session_append_queue_item (session, msg, TRUE, FALSE,
1944                                                callback, user_data);
1945         soup_session_kick_queue (session);
1946         soup_message_queue_item_unref (item);
1947 }
1948
1949 /**
1950  * soup_session_queue_message:
1951  * @session: a #SoupSession
1952  * @msg: (transfer full): the message to queue
1953  * @callback: (allow-none) (scope async): a #SoupSessionCallback which will
1954  * be called after the message completes or when an unrecoverable error occurs.
1955  * @user_data: (allow-none): a pointer passed to @callback.
1956  * 
1957  * Queues the message @msg for asynchronously sending the request and
1958  * receiving a response in the current thread-default #GMainContext.
1959  * If @msg has been processed before, any resources related to the
1960  * time it was last sent are freed.
1961  *
1962  * Upon message completion, the callback specified in @callback will
1963  * be invoked. If after returning from this callback the message has not
1964  * been requeued, @msg will be unreffed.
1965  *
1966  * (The behavior above applies to a plain #SoupSession; if you are
1967  * using #SoupSessionAsync or #SoupSessionSync, then the #GMainContext
1968  * that is used depends on the settings of #SoupSession:async-context
1969  * and #SoupSession:use-thread-context, and for #SoupSessionSync, the
1970  * message will actually be sent and processed in another thread, with
1971  * only the final callback occurring in the indicated #GMainContext.)
1972  */
1973 void
1974 soup_session_queue_message (SoupSession *session, SoupMessage *msg,
1975                             SoupSessionCallback callback, gpointer user_data)
1976 {
1977         g_return_if_fail (SOUP_IS_SESSION (session));
1978         g_return_if_fail (SOUP_IS_MESSAGE (msg));
1979
1980         SOUP_SESSION_GET_CLASS (session)->queue_message (session, msg,
1981                                                          callback, user_data);
1982         /* The SoupMessageQueueItem will hold a ref on @msg until it is
1983          * finished, so we can drop the ref adopted from the caller now.
1984          */
1985         g_object_unref (msg);
1986 }
1987
1988 static void
1989 soup_session_real_requeue_message (SoupSession *session, SoupMessage *msg)
1990 {
1991         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
1992         SoupMessageQueueItem *item;
1993
1994         item = soup_message_queue_lookup (priv->queue, msg);
1995         g_return_if_fail (item != NULL);
1996         item->state = SOUP_MESSAGE_RESTARTING;
1997         soup_message_queue_item_unref (item);
1998 }
1999
2000 /**
2001  * soup_session_requeue_message:
2002  * @session: a #SoupSession
2003  * @msg: the message to requeue
2004  *
2005  * This causes @msg to be placed back on the queue to be attempted
2006  * again.
2007  **/
2008 void
2009 soup_session_requeue_message (SoupSession *session, SoupMessage *msg)
2010 {
2011         g_return_if_fail (SOUP_IS_SESSION (session));
2012         g_return_if_fail (SOUP_IS_MESSAGE (msg));
2013
2014         SOUP_SESSION_GET_CLASS (session)->requeue_message (session, msg);
2015 }
2016
2017 static guint
2018 soup_session_real_send_message (SoupSession *session, SoupMessage *msg)
2019 {
2020         SoupMessageQueueItem *item;
2021         guint status;
2022
2023         item = soup_session_append_queue_item (session, msg, FALSE, FALSE,
2024                                                NULL, NULL);
2025         soup_session_process_queue_item (session, item, NULL, TRUE);
2026         status = msg->status_code;
2027         soup_message_queue_item_unref (item);
2028         return status;
2029 }
2030
2031 /**
2032  * soup_session_send_message:
2033  * @session: a #SoupSession
2034  * @msg: the message to send
2035  * 
2036  * Synchronously send @msg. This call will not return until the
2037  * transfer is finished successfully or there is an unrecoverable
2038  * error.
2039  *
2040  * Unlike with soup_session_queue_message(), @msg is not freed upon
2041  * return.
2042  *
2043  * (Note that if you call this method on a #SoupSessionAsync, it will
2044  * still use asynchronous I/O internally, running the glib main loop
2045  * to process the message, which may also cause other events to be
2046  * processed.)
2047  *
2048  * Return value: the HTTP status code of the response
2049  */
2050 guint
2051 soup_session_send_message (SoupSession *session, SoupMessage *msg)
2052 {
2053         g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
2054         g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_STATUS_MALFORMED);
2055
2056         return SOUP_SESSION_GET_CLASS (session)->send_message (session, msg);
2057 }
2058
2059
2060 /**
2061  * soup_session_pause_message:
2062  * @session: a #SoupSession
2063  * @msg: a #SoupMessage currently running on @session
2064  *
2065  * Pauses HTTP I/O on @msg. Call soup_session_unpause_message() to
2066  * resume I/O.
2067  **/
2068 void
2069 soup_session_pause_message (SoupSession *session,
2070                             SoupMessage *msg)
2071 {
2072         SoupSessionPrivate *priv;
2073         SoupMessageQueueItem *item;
2074
2075         g_return_if_fail (SOUP_IS_SESSION (session));
2076         g_return_if_fail (SOUP_IS_MESSAGE (msg));
2077
2078         priv = SOUP_SESSION_GET_PRIVATE (session);
2079         item = soup_message_queue_lookup (priv->queue, msg);
2080         g_return_if_fail (item != NULL);
2081
2082         item->paused = TRUE;
2083         if (item->state == SOUP_MESSAGE_RUNNING)
2084                 soup_message_io_pause (msg);
2085         soup_message_queue_item_unref (item);
2086 }
2087
2088 static void
2089 soup_session_real_kick_queue (SoupSession *session)
2090 {
2091         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
2092         SoupMessageQueueItem *item;
2093         gboolean have_sync_items = FALSE;
2094
2095         if (priv->disposed)
2096                 return;
2097
2098         for (item = soup_message_queue_first (priv->queue);
2099              item;
2100              item = soup_message_queue_next (priv->queue, item)) {
2101                 if (item->async) {
2102                         GSource *source;
2103
2104                         /* We use priv rather than session as the
2105                          * source data, because other parts of libsoup
2106                          * (or the calling app) may have sources using
2107                          * the session as the source data.
2108                          */
2109                         source = g_main_context_find_source_by_user_data (item->async_context, priv);
2110                         if (!source) {
2111                                 source = soup_add_completion_reffed (item->async_context,
2112                                                                      idle_run_queue, priv);
2113                                 priv->run_queue_sources = g_slist_prepend (priv->run_queue_sources,
2114                                                                            source);
2115                         }
2116                 } else
2117                         have_sync_items = TRUE;
2118         }
2119
2120         if (have_sync_items)
2121                 g_cond_broadcast (&priv->conn_cond);
2122 }
2123
2124 void
2125 soup_session_kick_queue (SoupSession *session)
2126 {
2127         SOUP_SESSION_GET_CLASS (session)->kick (session);
2128 }
2129
2130 /**
2131  * soup_session_unpause_message:
2132  * @session: a #SoupSession
2133  * @msg: a #SoupMessage currently running on @session
2134  *
2135  * Resumes HTTP I/O on @msg. Use this to resume after calling
2136  * soup_session_pause_message().
2137  *
2138  * If @msg is being sent via blocking I/O, this will resume reading or
2139  * writing immediately. If @msg is using non-blocking I/O, then
2140  * reading or writing won't resume until you return to the main loop.
2141  **/
2142 void
2143 soup_session_unpause_message (SoupSession *session,
2144                               SoupMessage *msg)
2145 {
2146         SoupSessionPrivate *priv;
2147         SoupMessageQueueItem *item;
2148
2149         g_return_if_fail (SOUP_IS_SESSION (session));
2150         g_return_if_fail (SOUP_IS_MESSAGE (msg));
2151
2152         priv = SOUP_SESSION_GET_PRIVATE (session);
2153         item = soup_message_queue_lookup (priv->queue, msg);
2154         g_return_if_fail (item != NULL);
2155
2156         item->paused = FALSE;
2157         if (item->state == SOUP_MESSAGE_RUNNING)
2158                 soup_message_io_unpause (msg);
2159         soup_message_queue_item_unref (item);
2160
2161         soup_session_kick_queue (session);
2162 }
2163
2164
2165 static void
2166 soup_session_real_cancel_message (SoupSession *session, SoupMessage *msg, guint status_code)
2167 {
2168         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
2169         SoupMessageQueueItem *item;
2170
2171         item = soup_message_queue_lookup (priv->queue, msg);
2172         g_return_if_fail (item != NULL);
2173
2174         item->paused = FALSE;
2175         soup_message_set_status (msg, status_code);
2176         g_cancellable_cancel (item->cancellable);
2177
2178         soup_session_kick_queue (item->session);
2179         soup_message_queue_item_unref (item);
2180 }
2181
2182 /**
2183  * soup_session_cancel_message:
2184  * @session: a #SoupSession
2185  * @msg: the message to cancel
2186  * @status_code: status code to set on @msg (generally
2187  * %SOUP_STATUS_CANCELLED)
2188  *
2189  * Causes @session to immediately finish processing @msg (regardless
2190  * of its current state) with a final status_code of @status_code. You
2191  * may call this at any time after handing @msg off to @session; if
2192  * @session has started sending the request but has not yet received
2193  * the complete response, then it will close the request's connection.
2194  * Note that with requests that have side effects (eg,
2195  * <literal>POST</literal>, <literal>PUT</literal>,
2196  * <literal>DELETE</literal>) it is possible that you might cancel the
2197  * request after the server acts on it, but before it returns a
2198  * response, leaving the remote resource in an unknown state.
2199  *
2200  * If the message is cancelled while its response body is being read,
2201  * then the response body in @msg will be left partially-filled-in.
2202  * The response headers, on the other hand, will always be either
2203  * empty or complete.
2204  *
2205  * Beware that with the deprecated #SoupSessionAsync, messages queued
2206  * with soup_session_queue_message() will have their callbacks invoked
2207  * before soup_session_cancel_message() returns. The plain
2208  * #SoupSession does not have this behavior; cancelling an
2209  * asynchronous message will merely queue its callback to be run after
2210  * returning to the main loop.
2211  **/
2212 void
2213 soup_session_cancel_message (SoupSession *session, SoupMessage *msg,
2214                              guint status_code)
2215 {
2216         SoupSessionPrivate *priv;
2217         SoupMessageQueueItem *item;
2218
2219         g_return_if_fail (SOUP_IS_SESSION (session));
2220         g_return_if_fail (SOUP_IS_MESSAGE (msg));
2221
2222         priv = SOUP_SESSION_GET_PRIVATE (session);
2223         item = soup_message_queue_lookup (priv->queue, msg);
2224         /* If the message is already ending, don't do anything */
2225         if (!item)
2226                 return;
2227         if (item->state == SOUP_MESSAGE_FINISHED) {
2228                 soup_message_queue_item_unref (item);
2229                 return;
2230         }
2231
2232         SOUP_SESSION_GET_CLASS (session)->cancel_message (session, msg, status_code);
2233         soup_message_queue_item_unref (item);
2234 }
2235
2236 static void
2237 soup_session_real_flush_queue (SoupSession *session)
2238 {
2239         SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
2240         SoupMessageQueueItem *item;
2241         GHashTable *current = NULL;
2242         gboolean done = FALSE;
2243
2244         if (SOUP_IS_SESSION_SYNC (session)) {
2245                 /* Record the current contents of the queue */
2246                 current = g_hash_table_new (NULL, NULL);
2247                 for (item = soup_message_queue_first (priv->queue);
2248                      item;
2249                      item = soup_message_queue_next (priv->queue, item))
2250                         g_hash_table_insert (current, item, item);
2251         }
2252
2253         /* Cancel everything */
2254         for (item = soup_message_queue_first (priv->queue);
2255              item;
2256              item = soup_message_queue_next (priv->queue, item)) {
2257                 soup_session_cancel_message (session, item->msg,
2258                                              SOUP_STATUS_CANCELLED);
2259         }
2260
2261         if (SOUP_IS_SESSION_SYNC (session)) {
2262                 /* Wait until all of the items in @current have been
2263                  * removed from the queue. (This is not the same as
2264                  * "wait for the queue to be empty", because the app
2265                  * may queue new requests in response to the
2266                  * cancellation of the old ones. We don't try to
2267                  * cancel those requests as well, since we'd likely
2268                  * just end up looping forever.)
2269                  */
2270                 g_mutex_lock (&priv->conn_lock);
2271                 do {
2272                         done = TRUE;
2273                         for (item = soup_message_queue_first (priv->queue);
2274                              item;
2275                              item = soup_message_queue_next (priv->queue, item)) {
2276                                 if (g_hash_table_lookup (current, item))
2277                                         done = FALSE;
2278                         }
2279
2280                         if (!done)
2281                                 g_cond_wait (&priv->conn_cond, &priv->conn_lock);
2282                 } while (!done);
2283                 g_mutex_unlock (&priv->conn_lock);
2284
2285                 g_hash_table_destroy (current);
2286         }
2287 }
2288
2289 /**
2290  * soup_session_abort:
2291  * @session: the session
2292  *
2293  * Cancels all pending requests in @session and closes all idle
2294  * persistent connections.
2295  *
2296  * The message cancellation has the same semantics as with
2297  * soup_session_cancel_message(); asynchronous requests on a
2298  * #SoupSessionAsync will have their callback called before
2299  * soup_session_abort() returns. Requests on a plain #SoupSession will
2300  * not.
2301  **/
2302 void
2303 soup_session_abort (SoupSession *session)
2304 {
2305         SoupSessionPrivate *priv;
2306         GSList *conns, *c;
2307         GHashTableIter iter;
2308         gpointer conn, host;
2309
2310         g_return_if_fail (SOUP_IS_SESSION (session));
2311         priv = SOUP_SESSION_GET_PRIVATE (session);
2312
2313         SOUP_SESSION_GET_CLASS (session)->flush_queue (session);
2314
2315         /* Close all connections */
2316         g_mutex_lock (&priv->conn_lock);
2317         conns = NULL;
2318         g_hash_table_iter_init (&iter, priv->conns);
2319         while (g_hash_table_iter_next (&iter, &conn, &host)) {
2320                 conns = g_slist_prepend (conns, g_object_ref (conn));
2321                 g_hash_table_iter_remove (&iter);
2322                 drop_connection (session, host, conn);
2323         }
2324         g_mutex_unlock (&priv->conn_lock);
2325
2326         for (c = conns; c; c = c->next) {
2327                 soup_connection_disconnect (c->data);
2328                 g_object_unref (c->data);
2329         }
2330
2331         g_slist_free (conns);
2332 }
2333
2334 static void
2335 prefetch_uri (SoupSession *session, SoupURI *uri,
2336               GCancellable *cancellable,
2337               SoupAddressCallback callback, gpointer user_data)
2338 {
2339         SoupSessionPrivate *priv;
2340         SoupSessionHost *host;
2341         SoupAddress *addr;
2342
2343         priv = SOUP_SESSION_GET_PRIVATE (session);
2344
2345         g_mutex_lock (&priv->conn_lock);
2346         host = get_host_for_uri (session, uri);
2347         addr = g_object_ref (host->addr);
2348         g_mutex_unlock (&priv->conn_lock);
2349
2350         soup_address_resolve_async (addr,
2351                                     soup_session_get_async_context (session),
2352                                     cancellable, callback, user_data);
2353         g_object_unref (addr);
2354 }
2355
2356 /**
2357  * soup_session_prepare_for_uri:
2358  * @session: a #SoupSession
2359  * @uri: a #SoupURI which may be required
2360  *
2361  * Tells @session that @uri may be requested shortly, and so the
2362  * session can try to prepare (resolving the domain name, obtaining
2363  * proxy address, etc.) in order to work more quickly once the URI is
2364  * actually requested.
2365  *
2366  * Since: 2.30
2367  *
2368  * Deprecated: 2.38: use soup_session_prefetch_dns() instead
2369  **/
2370 void
2371 soup_session_prepare_for_uri (SoupSession *session, SoupURI *uri)
2372 {
2373         g_return_if_fail (SOUP_IS_SESSION (session));
2374         g_return_if_fail (uri != NULL);
2375
2376         if (!uri->host)
2377                 return;
2378
2379         prefetch_uri (session, uri, NULL, NULL, NULL);
2380 }
2381
2382 /**
2383 * soup_session_prefetch_dns:
2384 * @session: a #SoupSession
2385 * @hostname: a hostname to be resolved
2386 * @cancellable: (allow-none): a #GCancellable object, or %NULL
2387 * @callback: (scope async) (allow-none): callback to call with the
2388 *     result, or %NULL
2389 * @user_data: data for @callback
2390 *
2391 * Tells @session that an URI from the given @hostname may be requested
2392 * shortly, and so the session can try to prepare by resolving the
2393 * domain name in advance, in order to work more quickly once the URI
2394 * is actually requested.
2395 *
2396 * If @cancellable is non-%NULL, it can be used to cancel the
2397 * resolution. @callback will still be invoked in this case, with a
2398 * status of %SOUP_STATUS_CANCELLED.
2399 *
2400 * Since: 2.38
2401 **/
2402 void
2403 soup_session_prefetch_dns (SoupSession *session, const char *hostname,
2404                            GCancellable *cancellable,
2405                            SoupAddressCallback callback, gpointer user_data)
2406 {
2407         SoupURI *uri;
2408
2409         g_return_if_fail (SOUP_IS_SESSION (session));
2410         g_return_if_fail (hostname != NULL);
2411
2412         /* FIXME: Prefetching should work for both HTTP and HTTPS */
2413         uri = soup_uri_new (NULL);
2414         soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTP);
2415         soup_uri_set_host (uri, hostname);
2416         soup_uri_set_path (uri, "");
2417
2418         prefetch_uri (session, uri, cancellable, callback, user_data);
2419         soup_uri_free (uri);
2420 }
2421
2422 /**
2423  * soup_session_add_feature:
2424  * @session: a #SoupSession
2425  * @feature: an object that implements #SoupSessionFeature
2426  *
2427  * Adds @feature's functionality to @session. You can also add a
2428  * feature to the session at construct time by using the
2429  * %SOUP_SESSION_ADD_FEATURE property.
2430  *
2431  * Note that a #SoupProxyResolverDefault and a #SoupContentDecoder are
2432  * added to the session by default (unless you are using one of the
2433  * deprecated session subclasses).
2434  *
2435  * Since: 2.24
2436  **/
2437 void
2438 soup_session_add_feature (SoupSession *session, SoupSessionFeature *feature)
2439 {
2440         SoupSessionPrivate *priv;
2441
2442         g_return_if_fail (SOUP_IS_SESSION (session));
2443         g_return_if_fail (SOUP_IS_SESSION_FEATURE (feature));
2444
2445         priv = SOUP_SESSION_GET_PRIVATE (session);
2446         priv->features = g_slist_prepend (priv->features, g_object_ref (feature));
2447         g_hash_table_remove_all (priv->features_cache);
2448         soup_session_feature_attach (feature, session);
2449 }
2450
2451 /**
2452  * soup_session_add_feature_by_type:
2453  * @session: a #SoupSession
2454  * @feature_type: a #GType
2455  *
2456  * If @feature_type is the type of a class that implements
2457  * #SoupSessionFeature, this creates a new feature of that type and
2458  * adds it to @session as with soup_session_add_feature(). You can use
2459  * this when you don't need to customize the new feature in any way.
2460  *
2461  * If @feature_type is not a #SoupSessionFeature type, this gives each
2462  * existing feature on @session the chance to accept @feature_type as
2463  * a "subfeature". This can be used to add new #SoupAuth or
2464  * #SoupRequest types, for instance.
2465  *
2466  * You can also add a feature to the session at construct time by
2467  * using the %SOUP_SESSION_ADD_FEATURE_BY_TYPE property.
2468  *
2469  * Note that a #SoupProxyResolverDefault and a #SoupContentDecoder are
2470  * added to the session by default (unless you are using one of the
2471  * deprecated session subclasses).
2472  *
2473  * Since: 2.24
2474  **/
2475 void
2476 soup_session_add_feature_by_type (SoupSession *session, GType feature_type)
2477 {
2478         SoupSessionPrivate *priv;
2479
2480         g_return_if_fail (SOUP_IS_SESSION (session));
2481
2482         priv = SOUP_SESSION_GET_PRIVATE (session);
2483
2484         if (g_type_is_a (feature_type, SOUP_TYPE_SESSION_FEATURE)) {
2485                 SoupSessionFeature *feature;
2486
2487                 feature = g_object_new (feature_type, NULL);
2488                 soup_session_add_feature (session, feature);
2489                 g_object_unref (feature);
2490         } else if (g_type_is_a (feature_type, SOUP_TYPE_REQUEST)) {
2491                 SoupRequestClass *request_class;
2492                 int i;
2493
2494                 request_class = g_type_class_ref (feature_type);
2495                 for (i = 0; request_class->schemes[i]; i++) {
2496                         g_hash_table_insert (priv->request_types,
2497                                              (char *)request_class->schemes[i],
2498                                              GSIZE_TO_POINTER (feature_type));
2499                 }
2500         } else {
2501                 GSList *f;
2502
2503                 for (f = priv->features; f; f = f->next) {
2504                         if (soup_session_feature_add_feature (f->data, feature_type))
2505                                 return;
2506                 }
2507                 g_warning ("No feature manager for feature of type '%s'", g_type_name (feature_type));
2508         }
2509 }
2510
2511 /**
2512  * soup_session_remove_feature:
2513  * @session: a #SoupSession
2514  * @feature: a feature that has previously been added to @session
2515  *
2516  * Removes @feature's functionality from @session.
2517  *
2518  * Since: 2.24
2519  **/
2520 void
2521 soup_session_remove_feature (SoupSession *session, SoupSessionFeature *feature)
2522 {
2523         SoupSessionPrivate *priv;
2524
2525         g_return_if_fail (SOUP_IS_SESSION (session));
2526
2527         priv = SOUP_SESSION_GET_PRIVATE (session);
2528         if (g_slist_find (priv->features, feature)) {
2529                 priv->features = g_slist_remove (priv->features, feature);
2530                 g_hash_table_remove_all (priv->features_cache);
2531                 soup_session_feature_detach (feature, session);
2532                 g_object_unref (feature);
2533         }
2534 }
2535
2536 /**
2537  * soup_session_remove_feature_by_type:
2538  * @session: a #SoupSession
2539  * @feature_type: a #GType
2540  *
2541  * Removes all features of type @feature_type (or any subclass of
2542  * @feature_type) from @session. You can also remove standard features
2543  * from the session at construct time by using the
2544  * %SOUP_SESSION_REMOVE_FEATURE_BY_TYPE property.
2545  *
2546  * Since: 2.24
2547  **/
2548 void
2549 soup_session_remove_feature_by_type (SoupSession *session, GType feature_type)
2550 {
2551         SoupSessionPrivate *priv;
2552         GSList *f;
2553
2554         g_return_if_fail (SOUP_IS_SESSION (session));
2555
2556         priv = SOUP_SESSION_GET_PRIVATE (session);
2557
2558         if (g_type_is_a (feature_type, SOUP_TYPE_SESSION_FEATURE)) {
2559         restart:
2560                 for (f = priv->features; f; f = f->next) {
2561                         if (G_TYPE_CHECK_INSTANCE_TYPE (f->data, feature_type)) {
2562                                 soup_session_remove_feature (session, f->data);
2563                                 goto restart;
2564                         }
2565                 }
2566         } else if (g_type_is_a (feature_type, SOUP_TYPE_REQUEST)) {
2567                 SoupRequestClass *request_class;
2568                 int i;
2569
2570                 request_class = g_type_class_peek (feature_type);
2571                 if (!request_class)
2572                         return;
2573                 for (i = 0; request_class->schemes[i]; i++) {
2574                         g_hash_table_remove (priv->request_types,
2575                                              request_class->schemes[i]);
2576                 }
2577         } else {
2578                 for (f = priv->features; f; f = f->next) {
2579                         if (soup_session_feature_remove_feature (f->data, feature_type))
2580                                 return;
2581                 }
2582                 g_warning ("No feature manager for feature of type '%s'", g_type_name (feature_type));
2583         }
2584 }
2585
2586 /**
2587  * soup_session_has_feature:
2588  * @session: a #SoupSession
2589  * @feature_type: the #GType of the class of features to check for
2590  *
2591  * Tests if @session has at a feature of type @feature_type (which can
2592  * be the type of either a #SoupSessionFeature, or else a subtype of
2593  * some class managed by another feature, such as #SoupAuth or
2594  * #SoupRequest).
2595  *
2596  * Return value: %TRUE or %FALSE
2597  *
2598  * Since: 2.42
2599  **/
2600 gboolean
2601 soup_session_has_feature (SoupSession *session,
2602                           GType        feature_type)
2603 {
2604         SoupSessionPrivate *priv;
2605         GSList *f;
2606
2607         g_return_val_if_fail (SOUP_IS_SESSION (session), FALSE);
2608
2609         priv = SOUP_SESSION_GET_PRIVATE (session);
2610
2611         if (g_type_is_a (feature_type, SOUP_TYPE_SESSION_FEATURE)) {
2612                 for (f = priv->features; f; f = f->next) {
2613                         if (G_TYPE_CHECK_INSTANCE_TYPE (f->data, feature_type))
2614                                 return TRUE;
2615                 }
2616         } else if (g_type_is_a (feature_type, SOUP_TYPE_REQUEST)) {
2617                 return g_hash_table_lookup (priv->request_types,
2618                                             GSIZE_TO_POINTER (feature_type)) != NULL;
2619         } else {
2620                 for (f = priv->features; f; f = f->next) {
2621                         if (soup_session_feature_has_feature (f->data, feature_type))
2622                                 return TRUE;
2623                 }
2624         }
2625
2626         return FALSE;
2627 }
2628
2629 /**
2630  * soup_session_get_features:
2631  * @session: a #SoupSession
2632  * @feature_type: the #GType of the class of features to get
2633  *
2634  * Generates a list of @session's features of type @feature_type. (If
2635  * you want to see all features, you can pass %SOUP_TYPE_SESSION_FEATURE
2636  * for @feature_type.)
2637  *
2638  * Return value: (transfer container) (element-type Soup.SessionFeature):
2639  * a list of features. You must free the list, but not its contents
2640  *
2641  * Since: 2.26
2642  **/
2643 GSList *
2644 soup_session_get_features (SoupSession *session, GType feature_type)
2645 {
2646         SoupSessionPrivate *priv;
2647         GSList *f, *ret;
2648
2649         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
2650
2651         priv = SOUP_SESSION_GET_PRIVATE (session);
2652         for (f = priv->features, ret = NULL; f; f = f->next) {
2653                 if (G_TYPE_CHECK_INSTANCE_TYPE (f->data, feature_type))
2654                         ret = g_slist_prepend (ret, f->data);
2655         }
2656         return g_slist_reverse (ret);
2657 }
2658
2659 /**
2660  * soup_session_get_feature:
2661  * @session: a #SoupSession
2662  * @feature_type: the #GType of the feature to get
2663  *
2664  * Gets the first feature in @session of type @feature_type. For
2665  * features where there may be more than one feature of a given type,
2666  * use soup_session_get_features().
2667  *
2668  * Return value: (transfer none): a #SoupSessionFeature, or %NULL. The
2669  * feature is owned by @session.
2670  *
2671  * Since: 2.26
2672  **/
2673 SoupSessionFeature *
2674 soup_session_get_feature (SoupSession *session, GType feature_type)
2675 {
2676         SoupSessionPrivate *priv;
2677         SoupSessionFeature *feature;
2678         GSList *f;
2679
2680         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
2681
2682         priv = SOUP_SESSION_GET_PRIVATE (session);
2683
2684         feature = g_hash_table_lookup (priv->features_cache,
2685                                        GSIZE_TO_POINTER (feature_type));
2686         if (feature)
2687                 return feature;
2688
2689         for (f = priv->features; f; f = f->next) {
2690                 feature = f->data;
2691                 if (G_TYPE_CHECK_INSTANCE_TYPE (feature, feature_type)) {
2692                         g_hash_table_insert (priv->features_cache,
2693                                              GSIZE_TO_POINTER (feature_type),
2694                                              feature);
2695                         return feature;
2696                 }
2697         }
2698         return NULL;
2699 }
2700
2701 /**
2702  * soup_session_get_feature_for_message:
2703  * @session: a #SoupSession
2704  * @feature_type: the #GType of the feature to get
2705  * @msg: a #SoupMessage
2706  *
2707  * Gets the first feature in @session of type @feature_type, provided
2708  * that it is not disabled for @msg. As with
2709  * soup_session_get_feature(), this should only be used for features
2710  * where @feature_type is only expected to match a single feature. In
2711  * particular, if there are two matching features, and the first is
2712  * disabled on @msg, and the second is not, then this will return
2713  * %NULL, not the second feature.
2714  *
2715  * Return value: (transfer none): a #SoupSessionFeature, or %NULL. The
2716  * feature is owned by @session.
2717  *
2718  * Since: 2.28
2719  **/
2720 SoupSessionFeature *
2721 soup_session_get_feature_for_message (SoupSession *session, GType feature_type,
2722                                       SoupMessage *msg)
2723 {
2724         SoupSessionFeature *feature;
2725
2726         feature = soup_session_get_feature (session, feature_type);
2727         if (feature && soup_message_disables_feature (msg, feature))
2728                 return NULL;
2729         return feature;
2730 }
2731
2732 static void
2733 soup_session_class_init (SoupSessionClass *session_class)
2734 {
2735         GObjectClass *object_class = G_OBJECT_CLASS (session_class);
2736
2737         g_type_class_add_private (session_class, sizeof (SoupSessionPrivate));
2738
2739         /* virtual method definition */
2740         session_class->queue_message = soup_session_real_queue_message;
2741         session_class->send_message = soup_session_real_send_message;
2742         session_class->requeue_message = soup_session_real_requeue_message;
2743         session_class->cancel_message = soup_session_real_cancel_message;
2744         session_class->flush_queue = soup_session_real_flush_queue;
2745         session_class->kick = soup_session_real_kick_queue;
2746
2747         /* virtual method override */
2748         object_class->constructor = soup_session_constructor;
2749         object_class->dispose = soup_session_dispose;
2750         object_class->finalize = soup_session_finalize;
2751         object_class->set_property = soup_session_set_property;
2752         object_class->get_property = soup_session_get_property;
2753
2754         /* signals */
2755
2756         /**
2757          * SoupSession::request-queued:
2758          * @session: the session
2759          * @msg: the request that was queued
2760          *
2761          * Emitted when a request is queued on @session. (Note that
2762          * "queued" doesn't just mean soup_session_queue_message();
2763          * soup_session_send_message() implicitly queues the message
2764          * as well.)
2765          *
2766          * When sending a request, first #SoupSession::request_queued
2767          * is emitted, indicating that the session has become aware of
2768          * the request.
2769          *
2770          * Once a connection is available to send the request on, the
2771          * session emits #SoupSession::request_started. Then, various
2772          * #SoupMessage signals are emitted as the message is
2773          * processed. If the message is requeued, it will emit
2774          * #SoupMessage::restarted, which will then be followed by
2775          * another #SoupSession::request_started and another set of
2776          * #SoupMessage signals when the message is re-sent.
2777          *
2778          * Eventually, the message will emit #SoupMessage::finished.
2779          * Normally, this signals the completion of message
2780          * processing. However, it is possible that the application
2781          * will requeue the message from the "finished" handler (or
2782          * equivalently, from the soup_session_queue_message()
2783          * callback). In that case, the process will loop back to
2784          * #SoupSession::request_started.
2785          *
2786          * Eventually, a message will reach "finished" and not be
2787          * requeued. At that point, the session will emit
2788          * #SoupSession::request_unqueued to indicate that it is done
2789          * with the message.
2790          *
2791          * To sum up: #SoupSession::request_queued and
2792          * #SoupSession::request_unqueued are guaranteed to be emitted
2793          * exactly once, but #SoupSession::request_started and
2794          * #SoupMessage::finished (and all of the other #SoupMessage
2795          * signals) may be invoked multiple times for a given message.
2796          *
2797          * Since: 2.24
2798          **/
2799         signals[REQUEST_QUEUED] =
2800                 g_signal_new ("request-queued",
2801                               G_OBJECT_CLASS_TYPE (object_class),
2802                               G_SIGNAL_RUN_FIRST,
2803                               0, /* FIXME? */
2804                               NULL, NULL,
2805                               _soup_marshal_NONE__OBJECT,
2806                               G_TYPE_NONE, 1,
2807                               SOUP_TYPE_MESSAGE);
2808
2809         /**
2810          * SoupSession::request-started:
2811          * @session: the session
2812          * @msg: the request being sent
2813          * @socket: the socket the request is being sent on
2814          *
2815          * Emitted just before a request is sent. See
2816          * #SoupSession::request_queued for a detailed description of
2817          * the message lifecycle within a session.
2818          **/
2819         signals[REQUEST_STARTED] =
2820                 g_signal_new ("request-started",
2821                               G_OBJECT_CLASS_TYPE (object_class),
2822                               G_SIGNAL_RUN_FIRST,
2823                               G_STRUCT_OFFSET (SoupSessionClass, request_started),
2824                               NULL, NULL,
2825                               _soup_marshal_NONE__OBJECT_OBJECT,
2826                               G_TYPE_NONE, 2,
2827                               SOUP_TYPE_MESSAGE,
2828                               SOUP_TYPE_SOCKET);
2829
2830         /**
2831          * SoupSession::request-unqueued:
2832          * @session: the session
2833          * @msg: the request that was unqueued
2834          *
2835          * Emitted when a request is removed from @session's queue,
2836          * indicating that @session is done with it. See
2837          * #SoupSession::request_queued for a detailed description of the
2838          * message lifecycle within a session.
2839          *
2840          * Since: 2.24
2841          **/
2842         signals[REQUEST_UNQUEUED] =
2843                 g_signal_new ("request-unqueued",
2844                               G_OBJECT_CLASS_TYPE (object_class),
2845                               G_SIGNAL_RUN_FIRST,
2846                               0, /* FIXME? */
2847                               NULL, NULL,
2848                               _soup_marshal_NONE__OBJECT,
2849                               G_TYPE_NONE, 1,
2850                               SOUP_TYPE_MESSAGE);
2851
2852         /**
2853          * SoupSession::authenticate:
2854          * @session: the session
2855          * @msg: the #SoupMessage being sent
2856          * @auth: the #SoupAuth to authenticate
2857          * @retrying: %TRUE if this is the second (or later) attempt
2858          *
2859          * Emitted when the session requires authentication. If
2860          * credentials are available call soup_auth_authenticate() on
2861          * @auth. If these credentials fail, the signal will be
2862          * emitted again, with @retrying set to %TRUE, which will
2863          * continue until you return without calling
2864          * soup_auth_authenticate() on @auth.
2865          *
2866          * Note that this may be emitted before @msg's body has been
2867          * fully read.
2868          *
2869          * If you call soup_session_pause_message() on @msg before
2870          * returning, then you can authenticate @auth asynchronously
2871          * (as long as you g_object_ref() it to make sure it doesn't
2872          * get destroyed), and then unpause @msg when you are ready
2873          * for it to continue.
2874          **/
2875         signals[AUTHENTICATE] =
2876                 g_signal_new ("authenticate",
2877                               G_OBJECT_CLASS_TYPE (object_class),
2878                               G_SIGNAL_RUN_FIRST,
2879                               G_STRUCT_OFFSET (SoupSessionClass, authenticate),
2880                               NULL, NULL,
2881                               _soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN,
2882                               G_TYPE_NONE, 3,
2883                               SOUP_TYPE_MESSAGE,
2884                               SOUP_TYPE_AUTH,
2885                               G_TYPE_BOOLEAN);
2886
2887         /**
2888          * SoupSession::connection-created:
2889          * @session: the #SoupSession
2890          * @connection: the connection
2891          *
2892          * Emitted when a new connection is created. This is an
2893          * internal signal intended only to be used for debugging
2894          * purposes, and may go away in the future.
2895          *
2896          * Since: 2.30
2897          */
2898         signals[CONNECTION_CREATED] =
2899                 g_signal_new ("connection-created",
2900                               G_OBJECT_CLASS_TYPE (object_class),
2901                               G_SIGNAL_RUN_FIRST,
2902                               0,
2903                               NULL, NULL,
2904                               _soup_marshal_NONE__OBJECT,
2905                               G_TYPE_NONE, 1,
2906                               /* SoupConnection is private, so we can't use
2907                                * SOUP_TYPE_CONNECTION here.
2908                                */
2909                               G_TYPE_OBJECT);
2910
2911         /**
2912          * SoupSession::tunneling:
2913          * @session: the #SoupSession
2914          * @connection: the connection
2915          *
2916          * Emitted when an SSL tunnel is being created on a proxy
2917          * connection. This is an internal signal intended only to be
2918          * used for debugging purposes, and may go away in the future.
2919          *
2920          * Since: 2.30
2921          */
2922         signals[TUNNELING] =
2923                 g_signal_new ("tunneling",
2924                               G_OBJECT_CLASS_TYPE (object_class),
2925                               G_SIGNAL_RUN_FIRST,
2926                               0,
2927                               NULL, NULL,
2928                               _soup_marshal_NONE__OBJECT,
2929                               G_TYPE_NONE, 1,
2930                               /* SoupConnection is private, so we can't use
2931                                * SOUP_TYPE_CONNECTION here.
2932                                */
2933                               G_TYPE_OBJECT);
2934
2935
2936         /* properties */
2937         /**
2938          * SoupSession:proxy-uri:
2939          *
2940          * An http proxy to use for all http and https requests in
2941          * this session. Setting this will remove any
2942          * #SoupProxyURIResolver features that have been added to the
2943          * session.
2944          *
2945          * Note that #SoupProxyResolverDefault will handle looking up
2946          * the user's proxy settings for you; you should only use
2947          * #SoupSession:proxy-uri if you need to override the user's
2948          * normal proxy settings.
2949          */
2950         /**
2951          * SOUP_SESSION_PROXY_URI:
2952          *
2953          * Alias for the #SoupSession:proxy-uri property, qv.
2954          **/
2955         g_object_class_install_property (
2956                 object_class, PROP_PROXY_URI,
2957                 g_param_spec_boxed (SOUP_SESSION_PROXY_URI,
2958                                     "Proxy URI",
2959                                     "The HTTP Proxy to use for this session",
2960                                     SOUP_TYPE_URI,
2961                                     G_PARAM_READWRITE));
2962         /**
2963          * SOUP_SESSION_MAX_CONNS:
2964          *
2965          * Alias for the #SoupSession:max-conns property, qv.
2966          **/
2967         g_object_class_install_property (
2968                 object_class, PROP_MAX_CONNS,
2969                 g_param_spec_int (SOUP_SESSION_MAX_CONNS,
2970                                   "Max Connection Count",
2971                                   "The maximum number of connections that the session can open at once",
2972                                   1,
2973                                   G_MAXINT,
2974                                   SOUP_SESSION_MAX_CONNS_DEFAULT,
2975                                   G_PARAM_READWRITE));
2976         /**
2977          * SOUP_SESSION_MAX_CONNS_PER_HOST:
2978          *
2979          * Alias for the #SoupSession:max-conns-per-host property, qv.
2980          **/
2981         g_object_class_install_property (
2982                 object_class, PROP_MAX_CONNS_PER_HOST,
2983                 g_param_spec_int (SOUP_SESSION_MAX_CONNS_PER_HOST,
2984                                   "Max Per-Host Connection Count",
2985                                   "The maximum number of connections that the session can open at once to a given host",
2986                                   1,
2987                                   G_MAXINT,
2988                                   SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT,
2989                                   G_PARAM_READWRITE));
2990         /**
2991          * SoupSession:idle-timeout:
2992          *
2993          * Connection lifetime (in seconds) when idle. Any connection
2994          * left idle longer than this will be closed.
2995          *
2996          * Although you can change this property at any time, it will
2997          * only affect newly-created connections, not currently-open
2998          * ones. You can call soup_session_abort() after setting this
2999          * if you want to ensure that all future connections will have
3000          * this timeout value.
3001          *
3002          * Note that the default value of 60 seconds only applies to
3003          * plain #SoupSessions. If you are using #SoupSessionAsync or
3004          * #SoupSessionSync, the default value is 0 (meaning idle
3005          * connections will never time out).
3006          *
3007          * Since: 2.24
3008          **/
3009         /**
3010          * SOUP_SESSION_IDLE_TIMEOUT:
3011          *
3012          * Alias for the #SoupSession:idle-timeout property, qv.
3013          *
3014          * Since: 2.24
3015          **/
3016         g_object_class_install_property (
3017                 object_class, PROP_IDLE_TIMEOUT,
3018                 g_param_spec_uint (SOUP_SESSION_IDLE_TIMEOUT,
3019                                    "Idle Timeout",
3020                                    "Connection lifetime when idle",
3021                                    0, G_MAXUINT, 60,
3022                                    G_PARAM_READWRITE));
3023         /**
3024          * SoupSession:use-ntlm:
3025          *
3026          * Whether or not to use NTLM authentication.
3027          *
3028          * Deprecated: use soup_session_add_feature_by_type() with
3029          * #SOUP_TYPE_AUTH_NTLM.
3030          **/
3031         /**
3032          * SOUP_SESSION_USE_NTLM:
3033          *
3034          * Alias for the #SoupSession:use-ntlm property, qv.
3035          **/
3036         g_object_class_install_property (
3037                 object_class, PROP_USE_NTLM,
3038                 g_param_spec_boolean (SOUP_SESSION_USE_NTLM,
3039                                       "Use NTLM",
3040                                       "Whether or not to use NTLM authentication",
3041                                       FALSE,
3042                                       G_PARAM_READWRITE | G_PARAM_DEPRECATED));
3043         /**
3044          * SoupSession:ssl-ca-file:
3045          *
3046          * File containing SSL CA certificates.
3047          *
3048          * If the specified file does not exist or cannot be read,
3049          * then libsoup will print a warning, and then behave as
3050          * though it had read in a empty CA file, meaning that all SSL
3051          * certificates will be considered invalid.
3052          *
3053          * Deprecated: use #SoupSession:ssl-use-system-ca-file, or
3054          * else #SoupSession:tls-database with a #GTlsFileDatabase
3055          * (which allows you to do explicit error handling).
3056          **/
3057         /**
3058          * SOUP_SESSION_SSL_CA_FILE:
3059          *
3060          * Alias for the #SoupSession:ssl-ca-file property, qv.
3061          **/
3062         g_object_class_install_property (
3063                 object_class, PROP_SSL_CA_FILE,
3064                 g_param_spec_string (SOUP_SESSION_SSL_CA_FILE,
3065                                      "SSL CA file",
3066                                      "File containing SSL CA certificates",
3067                                      NULL,
3068                                      G_PARAM_READWRITE | G_PARAM_DEPRECATED));
3069         /**
3070          * SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE:
3071          *
3072          * Alias for the #SoupSession:ssl-use-system-ca-file property,
3073          * qv.
3074          *
3075          * Since: 2.38
3076          **/
3077         /**
3078          * SoupSession:ssl-use-system-ca-file:
3079          *
3080          * Setting this to %TRUE is equivalent to setting
3081          * #SoupSession:tls-database to the default system CA database.
3082          * (and likewise, setting #SoupSession:tls-database to the
3083          * default database by hand will cause this property to
3084          * become %TRUE).
3085          *
3086          * Setting this to %FALSE (when it was previously %TRUE) will
3087          * clear the #SoupSession:tls-database field.
3088          *
3089          * See #SoupSession:ssl-strict for more information on how
3090          * https certificate validation is handled.
3091          *
3092          * Note that the default value of %TRUE only applies to plain
3093          * #SoupSessions. If you are using #SoupSessionAsync or
3094          * #SoupSessionSync, the default value is %FALSE, for backward
3095          * compatibility.
3096          *
3097          * Since: 2.38
3098          **/
3099         g_object_class_install_property (
3100                 object_class, PROP_SSL_USE_SYSTEM_CA_FILE,
3101                 g_param_spec_boolean (SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE,
3102                                       "Use system CA file",
3103                                       "Use the system certificate database",
3104                                       TRUE,
3105                                       G_PARAM_READWRITE));
3106         /**
3107          * SOUP_SESSION_TLS_DATABASE:
3108          *
3109          * Alias for the #SoupSession:tls-database property, qv.
3110          *
3111          * Since: 2.38
3112          **/
3113         /**
3114          * SoupSession:tls-database:
3115          *
3116          * Sets the #GTlsDatabase to use for validating SSL/TLS
3117          * certificates.
3118          *
3119          * Note that setting the #SoupSession:ssl-ca-file or
3120          * #SoupSession:ssl-use-system-ca-file property will cause
3121          * this property to be set to a #GTlsDatabase corresponding to
3122          * the indicated file or system default.
3123          *
3124          * See #SoupSession:ssl-strict for more information on how
3125          * https certificate validation is handled.
3126          *
3127          * If you are using a plain #SoupSession then
3128          * #SoupSession:ssl-use-system-ca-file will be %TRUE by
3129          * default, and so this property will be a copy of the system
3130          * CA database. If you are using #SoupSessionAsync or
3131          * #SoupSessionSync, this property will be %NULL by default.
3132          *
3133          * Since: 2.38
3134          **/
3135         g_object_class_install_property (
3136                 object_class, PROP_TLS_DATABASE,
3137                 g_param_spec_object (SOUP_SESSION_TLS_DATABASE,
3138                                      "TLS Database",
3139                                      "TLS database to use",
3140                                      G_TYPE_TLS_DATABASE,
3141                                      G_PARAM_READWRITE));
3142         /**
3143          * SOUP_SESSION_SSL_STRICT:
3144          *
3145          * Alias for the #SoupSession:ssl-strict property, qv.
3146          *
3147          * Since: 2.30
3148          **/
3149         /**
3150          * SoupSession:ssl-strict:
3151          *
3152          * Normally, if #SoupSession:tls-database is set (including if
3153          * it was set via #SoupSession:ssl-use-system-ca-file or
3154          * #SoupSession:ssl-ca-file), then libsoup will reject any
3155          * certificate that is invalid (ie, expired) or that is not
3156          * signed by one of the given CA certificates, and the
3157          * #SoupMessage will fail with the status
3158          * %SOUP_STATUS_SSL_FAILED.
3159          *
3160          * If you set #SoupSession:ssl-strict to %FALSE, then all
3161          * certificates will be accepted, and you will need to call
3162          * soup_message_get_https_status() to distinguish valid from
3163          * invalid certificates. (This can be used, eg, if you want to
3164          * accept invalid certificates after giving some sort of
3165          * warning.)
3166          *
3167          * For a plain #SoupSession, if the session has no CA file or
3168          * TLS database, and this property is %TRUE, then all
3169          * certificates will be rejected. However, beware that the
3170          * deprecated #SoupSession subclasses (#SoupSessionAsync and
3171          * #SoupSessionSync) have the opposite behavior: if there is
3172          * no CA file or TLS database, then all certificates are always
3173          * accepted, and this property has no effect.
3174          *
3175          * Since: 2.30
3176          */
3177         g_object_class_install_property (
3178                 object_class, PROP_SSL_STRICT,
3179                 g_param_spec_boolean (SOUP_SESSION_SSL_STRICT,
3180                                       "Strictly validate SSL certificates",
3181                                       "Whether certificate errors should be considered a connection error",
3182                                       TRUE,
3183                                       G_PARAM_READWRITE));
3184         /**
3185          * SoupSession:async-context:
3186          *
3187          * The #GMainContext that miscellaneous session-related
3188          * asynchronous callbacks are invoked on. (Eg, setting
3189          * #SoupSession:idle-timeout will add a timeout source on this
3190          * context.)
3191          *
3192          * For a plain #SoupSession, this property is always set to
3193          * the #GMainContext that is the thread-default at the time
3194          * the session was created, and cannot be overridden. For the
3195          * deprecated #SoupSession subclasses, the default value is
3196          * %NULL, meaning to use the global default #GMainContext.
3197          *
3198          * If #SoupSession:use-thread-context is %FALSE, this context
3199          * will also be used for asynchronous HTTP I/O.
3200          */
3201         /**
3202          * SOUP_SESSION_ASYNC_CONTEXT:
3203          *
3204          * Alias for the #SoupSession:async-context property, qv.
3205          */
3206         g_object_class_install_property (
3207                 object_class, PROP_ASYNC_CONTEXT,
3208                 g_param_spec_pointer (SOUP_SESSION_ASYNC_CONTEXT,
3209                                       "Async GMainContext",
3210                                       "The GMainContext to dispatch async I/O in",
3211                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
3212         /**
3213          * SOUP_SESSION_USE_THREAD_CONTEXT:
3214          *
3215          * Alias for the #SoupSession:use-thread-context property, qv.
3216          *
3217          * Since: 2.38
3218          */
3219         /**
3220          * SoupSession:use-thread-context:
3221          *
3222          * If %TRUE (which it always is on a plain #SoupSession),
3223          * asynchronous HTTP requests in this session will run in
3224          * whatever the thread-default #GMainContext is at the time
3225          * they are started, rather than always occurring in
3226          * #SoupSession:async-context.
3227          *
3228          * Since: 2.38
3229          */
3230         g_object_class_install_property (
3231                 object_class, PROP_USE_THREAD_CONTEXT,
3232                 g_param_spec_boolean (SOUP_SESSION_USE_THREAD_CONTEXT,
3233                                       "Use thread-default GMainContext",
3234                                       "Whether to use thread-default main contexts",
3235                                       FALSE,
3236                                       G_PARAM_READWRITE));
3237         /**
3238          * SoupSession:timeout:
3239          *
3240          * The timeout (in seconds) for socket I/O operations
3241          * (including connecting to a server, and waiting for a reply
3242          * to an HTTP request).
3243          *
3244          * Although you can change this property at any time, it will
3245          * only affect newly-created connections, not currently-open
3246          * ones. You can call soup_session_abort() after setting this
3247          * if you want to ensure that all future connections will have
3248          * this timeout value.
3249          *
3250          * Note that the default value of 60 seconds only applies to
3251          * plain #SoupSessions. If you are using #SoupSessionAsync or
3252          * #SoupSessionSync, the default value is 0 (meaning socket I/O
3253          * will not time out).
3254          *
3255          * Not to be confused with #SoupSession:idle-timeout (which is
3256          * the length of time that idle persistent connections will be
3257          * kept open).
3258          */
3259         /**
3260          * SOUP_SESSION_TIMEOUT:
3261          *
3262          * Alias for the #SoupSession:timeout property, qv.
3263          **/
3264         g_object_class_install_property (
3265                 object_class, PROP_TIMEOUT,
3266                 g_param_spec_uint (SOUP_SESSION_TIMEOUT,
3267                                    "Timeout value",
3268                                    "Value in seconds to timeout a blocking I/O",
3269                                    0, G_MAXUINT, 0,
3270                                    G_PARAM_READWRITE));
3271
3272         /**
3273          * SoupSession:user-agent:
3274          *
3275          * If non-%NULL, the value to use for the "User-Agent" header
3276          * on #SoupMessage<!-- -->s sent from this session.
3277          *
3278          * RFC 2616 says: "The User-Agent request-header field
3279          * contains information about the user agent originating the
3280          * request. This is for statistical purposes, the tracing of
3281          * protocol violations, and automated recognition of user
3282          * agents for the sake of tailoring responses to avoid
3283          * particular user agent limitations. User agents SHOULD
3284          * include this field with requests."
3285          *
3286          * The User-Agent header contains a list of one or more
3287          * product tokens, separated by whitespace, with the most
3288          * significant product token coming first. The tokens must be
3289          * brief, ASCII, and mostly alphanumeric (although "-", "_",
3290          * and "." are also allowed), and may optionally include a "/"
3291          * followed by a version string. You may also put comments,
3292          * enclosed in parentheses, between or after the tokens.
3293          *
3294          * If you set a #SoupSession:user_agent property that has trailing
3295          * whitespace, #SoupSession will append its own product token
3296          * (eg, "<literal>libsoup/2.3.2</literal>") to the end of the
3297          * header for you.
3298          **/
3299         /**
3300          * SOUP_SESSION_USER_AGENT:
3301          *
3302          * Alias for the #SoupSession:user-agent property, qv.
3303          **/
3304         g_object_class_install_property (
3305                 object_class, PROP_USER_AGENT,
3306                 g_param_spec_string (SOUP_SESSION_USER_AGENT,
3307                                      "User-Agent string",
3308                                      "User-Agent string",
3309                                      NULL,
3310                                      G_PARAM_READWRITE));
3311
3312         /**
3313          * SoupSession:accept-language:
3314          *
3315          * If non-%NULL, the value to use for the "Accept-Language" header
3316          * on #SoupMessage<!-- -->s sent from this session.
3317          *
3318          * Setting this will disable
3319          * #SoupSession:accept-language-auto.
3320          *
3321          * Since: 2.30
3322          **/
3323         /**
3324          * SOUP_SESSION_ACCEPT_LANGUAGE:
3325          *
3326          * Alias for the #SoupSession:accept-language property, qv.
3327          *
3328          * Since: 2.30
3329          **/
3330         g_object_class_install_property (
3331                 object_class, PROP_ACCEPT_LANGUAGE,
3332                 g_param_spec_string (SOUP_SESSION_ACCEPT_LANGUAGE,
3333                                      "Accept-Language string",
3334                                      "Accept-Language string",
3335                                      NULL,
3336                                      G_PARAM_READWRITE));
3337
3338         /**
3339          * SoupSession:accept-language-auto:
3340          *
3341          * If %TRUE, #SoupSession will automatically set the string
3342          * for the "Accept-Language" header on every #SoupMessage
3343          * sent, based on the return value of g_get_language_names().
3344          *
3345          * Setting this will override any previous value of
3346          * #SoupSession:accept-language.
3347          *
3348          * Since: 2.30
3349          **/
3350         /**
3351          * SOUP_SESSION_ACCEPT_LANGUAGE_AUTO:
3352          *
3353          * Alias for the #SoupSession:accept-language-auto property, qv.
3354          *
3355          * Since: 2.30
3356          **/
3357         g_object_class_install_property (
3358                 object_class, PROP_ACCEPT_LANGUAGE_AUTO,
3359                 g_param_spec_boolean (SOUP_SESSION_ACCEPT_LANGUAGE_AUTO,
3360                                       "Accept-Language automatic mode",
3361                                       "Accept-Language automatic mode",
3362                                       FALSE,
3363                                       G_PARAM_READWRITE));
3364
3365         /**
3366          * SoupSession:add-feature: (skip)
3367          *
3368          * Add a feature object to the session. (Shortcut for calling
3369          * soup_session_add_feature().)
3370          *
3371          * Since: 2.24
3372          **/
3373         /**
3374          * SOUP_SESSION_ADD_FEATURE: (skip)
3375          *
3376          * Alias for the #SoupSession:add-feature property, qv.
3377          *
3378          * Since: 2.24
3379          **/
3380         g_object_class_install_property (
3381                 object_class, PROP_ADD_FEATURE,
3382                 g_param_spec_object (SOUP_SESSION_ADD_FEATURE,
3383                                      "Add Feature",
3384                                      "Add a feature object to the session",
3385                                      SOUP_TYPE_SESSION_FEATURE,
3386                                      G_PARAM_READWRITE));
3387         /**
3388          * SoupSession:add-feature-by-type: (skip)
3389          *
3390          * Add a feature object of the given type to the session.
3391          * (Shortcut for calling soup_session_add_feature_by_type().)
3392          *
3393          * Since: 2.24
3394          **/
3395         /**
3396          * SOUP_SESSION_ADD_FEATURE_BY_TYPE: (skip)
3397          *
3398          * Alias for the #SoupSession:add-feature-by-type property, qv.
3399          *
3400          * Since: 2.24
3401          **/
3402         g_object_class_install_property (
3403                 object_class, PROP_ADD_FEATURE_BY_TYPE,
3404                 g_param_spec_gtype (SOUP_SESSION_ADD_FEATURE_BY_TYPE,
3405                                     "Add Feature By Type",
3406                                     "Add a feature object of the given type to the session",
3407                                     SOUP_TYPE_SESSION_FEATURE,
3408                                     G_PARAM_READWRITE));
3409         /**
3410          * SoupSession:remove-feature-by-type: (skip)
3411          *
3412          * Remove feature objects from the session. (Shortcut for
3413          * calling soup_session_remove_feature_by_type().)
3414          *
3415          * Since: 2.24
3416          **/
3417         /**
3418          * SOUP_SESSION_REMOVE_FEATURE_BY_TYPE: (skip)
3419          *
3420          * Alias for the #SoupSession:remove-feature-by-type property,
3421          * qv.
3422          *
3423          * Since: 2.24
3424          **/
3425         g_object_class_install_property (
3426                 object_class, PROP_REMOVE_FEATURE_BY_TYPE,
3427                 g_param_spec_gtype (SOUP_SESSION_REMOVE_FEATURE_BY_TYPE,
3428                                     "Remove Feature By Type",
3429                                     "Remove features of the given type from the session",
3430                                     SOUP_TYPE_SESSION_FEATURE,
3431                                     G_PARAM_READWRITE));
3432         /**
3433          * SoupSession:http-aliases:
3434          *
3435          * A %NULL-terminated array of URI schemes that should be
3436          * considered to be aliases for "http". Eg, if this included
3437          * <literal>"dav"</literal>, than a URI of
3438          * <literal>dav://example.com/path</literal> would be treated
3439          * identically to <literal>http://example.com/path</literal>.
3440          *
3441          * In a plain #SoupSession, the default value is %NULL,
3442          * meaning that only "http" is recognized as meaning "http".
3443          * In #SoupSessionAsync and #SoupSessionSync, for backward
3444          * compatibility, the default value is an array containing the
3445          * single element <literal>"*"</literal>, a special value
3446          * which means that any scheme except "https" is considered to
3447          * be an alias for "http".
3448          *
3449          * See also #SoupSession:https-aliases.
3450          *
3451          * Since: 2.38
3452          */
3453         /**
3454          * SOUP_SESSION_HTTP_ALIASES:
3455          *
3456          * Alias for the #SoupSession:http-aliases property, qv.
3457          *
3458          * Since: 2.38
3459          */
3460         g_object_class_install_property (
3461                 object_class, PROP_HTTP_ALIASES,
3462                 g_param_spec_boxed (SOUP_SESSION_HTTP_ALIASES,
3463                                     "http aliases",
3464                                     "URI schemes that are considered aliases for 'http'",
3465                                     G_TYPE_STRV,
3466                                     G_PARAM_READWRITE));
3467         /**
3468          * SoupSession:https-aliases:
3469          *
3470          * A comma-delimited list of URI schemes that should be
3471          * considered to be aliases for "https". See
3472          * #SoupSession:http-aliases for more information.
3473          *
3474          * The default value is %NULL, meaning that no URI schemes
3475          * are considered aliases for "https".
3476          *
3477          * Since: 2.38
3478          */
3479         /**
3480          * SOUP_SESSION_HTTPS_ALIASES:
3481          *
3482          * Alias for the #SoupSession:https-aliases property, qv.
3483          *
3484          * Since: 2.38
3485          **/
3486         g_object_class_install_property (
3487                 object_class, PROP_HTTPS_ALIASES,
3488                 g_param_spec_boxed (SOUP_SESSION_HTTPS_ALIASES,
3489                                     "https aliases",
3490                                     "URI schemes that are considered aliases for 'https'",
3491                                     G_TYPE_STRV,
3492                                     G_PARAM_READWRITE));
3493 }
3494
3495
3496 /* send_request_async */
3497
3498 static void
3499 async_send_request_return_result (SoupMessageQueueItem *item,
3500                                   gpointer stream, GError *error)
3501 {
3502         GTask *task;
3503
3504         g_return_if_fail (item->task != NULL);
3505
3506         g_signal_handlers_disconnect_matched (item->msg, G_SIGNAL_MATCH_DATA,
3507                                               0, 0, NULL, NULL, item);
3508
3509         task = item->task;
3510         item->task = NULL;
3511
3512         if (item->io_source) {
3513                 g_source_destroy (item->io_source);
3514                 g_clear_pointer (&item->io_source, g_source_unref);
3515         }
3516
3517         if (error)
3518                 g_task_return_error (task, error);
3519         else if (SOUP_STATUS_IS_TRANSPORT_ERROR (item->msg->status_code)) {
3520                 if (stream)
3521                         g_object_unref (stream);
3522                 g_task_return_new_error (task, SOUP_HTTP_ERROR,
3523                                          item->msg->status_code,
3524                                          "%s",
3525                                          item->msg->reason_phrase);
3526         } else
3527                 g_task_return_pointer (task, stream, g_object_unref);
3528         g_object_unref (task);
3529 }
3530
3531 static void
3532 async_send_request_restarted (SoupMessage *msg, gpointer user_data)
3533 {
3534         SoupMessageQueueItem *item = user_data;
3535
3536         /* We won't be needing this, then. */
3537         g_object_set_data (G_OBJECT (item->msg), "SoupSession:ostream", NULL);
3538         item->io_started = FALSE;
3539 }
3540
3541 static void
3542 async_send_request_finished (SoupMessage *msg, gpointer user_data)
3543 {
3544         SoupMessageQueueItem *item = user_data;
3545         GMemoryOutputStream *mostream;
3546         GInputStream *istream = NULL;
3547         GError *error = NULL;
3548
3549         if (!item->task) {
3550                 /* Something else already took care of it. */
3551                 return;
3552         }
3553
3554         mostream = g_object_get_data (G_OBJECT (item->task), "SoupSession:ostream");
3555         if (mostream) {
3556                 gpointer data;
3557                 gssize size;
3558
3559                 /* We thought it would be requeued, but it wasn't, so
3560                  * return the original body.
3561                  */
3562                 size = g_memory_output_stream_get_data_size (mostream);
3563                 data = size ? g_memory_output_stream_steal_data (mostream) : g_strdup ("");
3564                 istream = g_memory_input_stream_new_from_data (data, size, g_free);
3565         } else if (item->io_started) {
3566                 /* The message finished before becoming readable. This
3567                  * will happen, eg, if it's cancelled from got-headers.
3568                  * Do nothing; the op will complete via read_ready_cb()
3569                  * after we return;
3570                  */
3571                 return;
3572         } else {
3573                 /* The message finished before even being started;
3574                  * probably a tunnel connect failure.
3575                  */
3576                 istream = g_memory_input_stream_new ();
3577         }
3578
3579         async_send_request_return_result (item, istream, error);
3580 }
3581
3582 static void
3583 send_async_spliced (GObject *source, GAsyncResult *result, gpointer user_data)
3584 {
3585         SoupMessageQueueItem *item = user_data;
3586         GInputStream *istream = g_object_get_data (source, "istream");
3587         GError *error = NULL;
3588
3589         /* It should be safe to call the sync close() method here since
3590          * the message body has already been written.
3591          */
3592         g_input_stream_close (istream, NULL, NULL);
3593         g_object_unref (istream);
3594
3595         /* If the message was cancelled, it will be completed via other means */
3596         if (g_cancellable_is_cancelled (item->cancellable) ||
3597             !item->task) {
3598                 soup_message_queue_item_unref (item);
3599                 return;
3600         }
3601
3602         if (g_output_stream_splice_finish (G_OUTPUT_STREAM (source),
3603                                            result, &error) == -1) {
3604                 async_send_request_return_result (item, NULL, error);
3605                 soup_message_queue_item_unref (item);
3606                 return;
3607         }
3608
3609         /* Otherwise either restarted or finished will eventually be called. */
3610         soup_session_kick_queue (item->session);
3611         soup_message_queue_item_unref (item);
3612 }
3613
3614 static void
3615 send_async_maybe_complete (SoupMessageQueueItem *item,
3616                            GInputStream         *stream)
3617 {
3618         if (item->msg->status_code == SOUP_STATUS_UNAUTHORIZED ||
3619             item->msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED ||
3620             soup_session_would_redirect (item->session, item->msg)) {
3621                 GOutputStream *ostream;
3622
3623                 /* Message may be requeued, so gather the current message body... */
3624                 ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
3625                 g_object_set_data_full (G_OBJECT (item->task), "SoupSession:ostream",
3626                                         ostream, g_object_unref);
3627
3628                 g_object_set_data (G_OBJECT (ostream), "istream", stream);
3629
3630                 /* Give the splice op its own ref on item */
3631                 soup_message_queue_item_ref (item);
3632                 g_output_stream_splice_async (ostream, stream,
3633                                               /* We can't use CLOSE_SOURCE because it
3634                                                * might get closed in the wrong thread.
3635                                                */
3636                                               G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
3637                                               G_PRIORITY_DEFAULT,
3638                                               item->cancellable,
3639                                               send_async_spliced, item);
3640                 return;
3641         }
3642
3643         async_send_request_return_result (item, stream, NULL);
3644 }
3645
3646 static void try_run_until_read (SoupMessageQueueItem *item);
3647
3648 static gboolean
3649 read_ready_cb (SoupMessage *msg, gpointer user_data)
3650 {
3651         SoupMessageQueueItem *item = user_data;
3652
3653         g_clear_pointer (&item->io_source, g_source_unref);
3654         try_run_until_read (item);
3655         return FALSE;
3656 }
3657
3658 static void
3659 try_run_until_read (SoupMessageQueueItem *item)
3660 {
3661         GError *error = NULL;
3662         GInputStream *stream = NULL;
3663
3664         if (soup_message_io_run_until_read (item->msg, item->cancellable, &error))
3665                 stream = soup_message_io_get_response_istream (item->msg, &error);
3666         if (stream) {
3667                 send_async_maybe_complete (item, stream);
3668                 return;
3669         }
3670
3671         if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN)) {
3672                 item->state = SOUP_MESSAGE_RESTARTING;
3673                 soup_message_io_finished (item->msg);
3674                 g_error_free (error);
3675                 return;
3676         }
3677
3678         if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
3679                 if (item->state != SOUP_MESSAGE_FINISHED) {
3680                         if (soup_message_io_in_progress (item->msg))
3681                                 soup_message_io_finished (item->msg);
3682                         item->state = SOUP_MESSAGE_FINISHING;
3683                         soup_session_process_queue_item (item->session, item, NULL, FALSE);
3684                 }
3685                 async_send_request_return_result (item, NULL, error);
3686                 return;
3687         }
3688
3689         g_clear_error (&error);
3690         item->io_source = soup_message_io_get_source (item->msg, item->cancellable,
3691                                                       read_ready_cb, item);
3692         g_source_attach (item->io_source, soup_session_get_async_context (item->session));
3693 }
3694
3695 static void
3696 async_send_request_running (SoupSession *session, SoupMessageQueueItem *item)
3697 {
3698         item->io_started = TRUE;
3699         try_run_until_read (item);
3700 }
3701
3702 static void
3703 async_return_from_cache (SoupMessageQueueItem *item,
3704                          GInputStream         *stream)
3705 {
3706         const char *content_type;
3707         GHashTable *params;
3708
3709         soup_message_got_headers (item->msg);
3710
3711         content_type = soup_message_headers_get_content_type (item->msg->response_headers, &params);
3712         soup_message_content_sniffed (item->msg, content_type, params);
3713         g_hash_table_unref (params);
3714
3715         item->state = SOUP_MESSAGE_FINISHING;
3716         async_send_request_return_result (item, g_object_ref (stream), NULL);
3717 }
3718
3719 static void
3720 conditional_get_ready_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
3721 {
3722         SoupMessageQueueItem *item = user_data;
3723         GInputStream *stream;
3724
3725         if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) {
3726                 SoupCache *cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE);
3727
3728                 stream = soup_cache_send_response (cache, item->msg);
3729                 if (stream) {
3730                         async_return_from_cache (item, stream);
3731                         g_object_unref (stream);
3732                         return;
3733                 }
3734         }
3735
3736         /* The resource was modified or the server returned a 200
3737          * OK. Either way we reload it. FIXME.
3738          */
3739         item->state = SOUP_MESSAGE_STARTING;
3740         soup_session_kick_queue (session);
3741 }
3742
3743 typedef struct {
3744         SoupMessageQueueItem *item;
3745         GInputStream *stream;
3746 } SendAsyncCacheData;
3747
3748 static void
3749 free_send_async_cache_data (SendAsyncCacheData *sacd)
3750 {
3751         soup_message_queue_item_unref (sacd->item);
3752         g_object_unref (sacd->stream);
3753         g_slice_free (SendAsyncCacheData, sacd);
3754 }
3755
3756 static gboolean
3757 idle_return_from_cache_cb (gpointer data)
3758 {
3759         GTask *task = data;
3760         SendAsyncCacheData *sacd = g_task_get_task_data (task);
3761
3762         async_return_from_cache (sacd->item, sacd->stream);
3763         return FALSE;
3764 }
3765
3766 static gboolean
3767 async_respond_from_cache (SoupSession          *session,
3768                           SoupMessageQueueItem *item)
3769 {
3770         SoupCache *cache;
3771         SoupCacheResponse response;
3772
3773         cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE);
3774         if (!cache)
3775                 return FALSE;
3776
3777         response = soup_cache_has_response (cache, item->msg);
3778         if (response == SOUP_CACHE_RESPONSE_FRESH) {
3779                 GInputStream *stream;
3780                 SendAsyncCacheData *sacd;
3781                 GSource *source;
3782
3783                 stream = soup_cache_send_response (cache, item->msg);
3784                 if (!stream) {
3785                         /* Cached file was deleted? */
3786                         return FALSE;
3787                 }
3788
3789                 sacd = g_slice_new (SendAsyncCacheData);
3790                 sacd->item = item;
3791                 soup_message_queue_item_ref (item);
3792                 sacd->stream = stream;
3793
3794                 g_task_set_task_data (item->task, sacd,
3795                                       (GDestroyNotify) free_send_async_cache_data);
3796
3797                 source = g_timeout_source_new (0);
3798                 g_task_attach_source (item->task, source,
3799                                       (GSourceFunc) idle_return_from_cache_cb);
3800                 g_source_unref (source);
3801                 return TRUE;
3802         } else if (response == SOUP_CACHE_RESPONSE_NEEDS_VALIDATION) {
3803                 SoupMessage *conditional_msg;
3804
3805                 conditional_msg = soup_cache_generate_conditional_request (cache, item->msg);
3806                 if (!conditional_msg)
3807                         return FALSE;
3808
3809                 soup_session_queue_message (session, conditional_msg,
3810                                             conditional_get_ready_cb,
3811                                             item);
3812                 return TRUE;
3813         } else
3814                 return FALSE;
3815 }
3816
3817 void
3818 soup_session_send_request_async (SoupSession         *session,
3819                                  SoupMessage         *msg,
3820                                  GCancellable        *cancellable,
3821                                  GAsyncReadyCallback  callback,
3822                                  gpointer             user_data)
3823 {
3824         SoupMessageQueueItem *item;
3825         gboolean use_thread_context;
3826
3827         g_return_if_fail (SOUP_IS_SESSION (session));
3828         g_return_if_fail (!SOUP_IS_SESSION_SYNC (session));
3829
3830         g_object_get (G_OBJECT (session),
3831                       SOUP_SESSION_USE_THREAD_CONTEXT, &use_thread_context,
3832                       NULL);
3833         g_return_if_fail (use_thread_context);
3834
3835         item = soup_session_append_queue_item (session, msg, TRUE, TRUE,
3836                                                NULL, NULL);
3837         g_signal_connect (msg, "restarted",
3838                           G_CALLBACK (async_send_request_restarted), item);
3839         g_signal_connect (msg, "finished",
3840                           G_CALLBACK (async_send_request_finished), item);
3841
3842         item->new_api = TRUE;
3843         item->task = g_task_new (session, cancellable, callback, user_data);
3844         g_task_set_task_data (item->task, item, (GDestroyNotify) soup_message_queue_item_unref);
3845
3846         if (cancellable) {
3847                 g_object_unref (item->cancellable);
3848                 item->cancellable = g_object_ref (cancellable);
3849         }
3850
3851         if (async_respond_from_cache (session, item))
3852                 item->state = SOUP_MESSAGE_CACHED;
3853         else
3854                 soup_session_kick_queue (session);
3855 }
3856
3857 GInputStream *
3858 soup_session_send_request_finish (SoupSession   *session,
3859                                   GAsyncResult  *result,
3860                                   GError       **error)
3861 {
3862         GTask *task;
3863
3864         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
3865         g_return_val_if_fail (!SOUP_IS_SESSION_SYNC (session), NULL);
3866         g_return_val_if_fail (g_task_is_valid (result, session), NULL);
3867
3868         task = G_TASK (result);
3869         if (g_task_had_error (task)) {
3870                 SoupMessageQueueItem *item = g_task_get_task_data (task);
3871
3872                 if (soup_message_io_in_progress (item->msg))
3873                         soup_message_io_finished (item->msg);
3874                 else if (item->state != SOUP_MESSAGE_FINISHED)
3875                         item->state = SOUP_MESSAGE_FINISHING;
3876
3877                 if (item->state != SOUP_MESSAGE_FINISHED)
3878                         soup_session_process_queue_item (session, item, NULL, FALSE);
3879         }
3880
3881         return g_task_propagate_pointer (task, error);
3882 }
3883
3884 GInputStream *
3885 soup_session_send_request (SoupSession   *session,
3886                            SoupMessage   *msg,
3887                            GCancellable  *cancellable,
3888                            GError       **error)
3889 {
3890         SoupMessageQueueItem *item;
3891         GInputStream *stream = NULL;
3892         GOutputStream *ostream;
3893         GMemoryOutputStream *mostream;
3894         gssize size;
3895         GError *my_error = NULL;
3896
3897         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
3898         g_return_val_if_fail (!SOUP_IS_SESSION_ASYNC (session), NULL);
3899
3900         item = soup_session_append_queue_item (session, msg, FALSE, TRUE,
3901                                                NULL, NULL);
3902
3903         item->new_api = TRUE;
3904         if (cancellable) {
3905                 g_object_unref (item->cancellable);
3906                 item->cancellable = g_object_ref (cancellable);
3907         }
3908
3909         while (!stream) {
3910                 /* Get a connection, etc */
3911                 soup_session_process_queue_item (session, item, NULL, TRUE);
3912                 if (item->state != SOUP_MESSAGE_RUNNING)
3913                         break;
3914
3915                 /* Send request, read headers */
3916                 if (!soup_message_io_run_until_read (msg, item->cancellable, &my_error)) {
3917                         if (g_error_matches (my_error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN)) {
3918                                 item->state = SOUP_MESSAGE_RESTARTING;
3919                                 soup_message_io_finished (item->msg);
3920                                 g_clear_error (&my_error);
3921                                 continue;
3922                         } else
3923                                 break;
3924                 }
3925
3926                 stream = soup_message_io_get_response_istream (msg, &my_error);
3927                 if (!stream)
3928                         break;
3929
3930                 /* Break if the message doesn't look likely-to-be-requeued */
3931                 if (msg->status_code != SOUP_STATUS_UNAUTHORIZED &&
3932                     msg->status_code != SOUP_STATUS_PROXY_UNAUTHORIZED &&
3933                     !soup_session_would_redirect (session, msg))
3934                         break;
3935
3936                 /* Gather the current message body... */
3937                 ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
3938                 if (g_output_stream_splice (ostream, stream,
3939                                             G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
3940                                             G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
3941                                             item->cancellable, &my_error) == -1) {
3942                         g_object_unref (stream);
3943                         g_object_unref (ostream);
3944                         stream = NULL;
3945                         break;
3946                 }
3947                 g_object_unref (stream);
3948                 stream = NULL;
3949
3950                 /* If the message was requeued, loop */
3951                 if (item->state == SOUP_MESSAGE_RESTARTING) {
3952                         g_object_unref (ostream);
3953                         continue;
3954                 }
3955
3956                 /* Not requeued, so return the original body */
3957                 mostream = G_MEMORY_OUTPUT_STREAM (ostream);
3958                 size = g_memory_output_stream_get_data_size (mostream);
3959                 stream = g_memory_input_stream_new ();
3960                 if (size) {
3961                         g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (stream),
3962                                                         g_memory_output_stream_steal_data (mostream),
3963                                                         size, g_free);
3964                 }
3965                 g_object_unref (ostream);
3966         }
3967
3968         if (my_error)
3969                 g_propagate_error (error, my_error);
3970         else if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
3971                 if (stream) {
3972                         g_object_unref (stream);
3973                         stream = NULL;
3974                 }
3975                 g_set_error_literal (error, SOUP_HTTP_ERROR, msg->status_code,
3976                                      msg->reason_phrase);
3977         } else if (!stream)
3978                 stream = g_memory_input_stream_new ();
3979
3980         if (!stream) {
3981                 if (soup_message_io_in_progress (msg))
3982                         soup_message_io_finished (msg);
3983                 else if (item->state != SOUP_MESSAGE_FINISHED)
3984                         item->state = SOUP_MESSAGE_FINISHING;
3985
3986                 if (item->state != SOUP_MESSAGE_FINISHED)
3987                         soup_session_process_queue_item (session, item, NULL, TRUE);
3988         }
3989
3990         soup_message_queue_item_unref (item);
3991         return stream;
3992 }
3993
3994 /**
3995  * soup_session_request:
3996  * @session: a #SoupSession
3997  * @uri_string: a URI, in string form
3998  * @error: return location for a #GError, or %NULL
3999  *
4000  * Creates a #SoupRequest for retrieving @uri_string.
4001  *
4002  * Return value: (transfer full): a new #SoupRequest, or
4003  *   %NULL on error.
4004  *
4005  * Since: 2.42
4006  */
4007 SoupRequest *
4008 soup_session_request (SoupSession *session, const char *uri_string,
4009                       GError **error)
4010 {
4011         SoupURI *uri;
4012         SoupRequest *req;
4013
4014         uri = soup_uri_new (uri_string);
4015         if (!uri) {
4016                 g_set_error (error, SOUP_REQUEST_ERROR,
4017                              SOUP_REQUEST_ERROR_BAD_URI,
4018                              _("Could not parse URI '%s'"), uri_string);
4019                 return NULL;
4020         }
4021
4022         req = soup_session_request_uri (session, uri, error);
4023         soup_uri_free (uri);
4024         return req;
4025 }
4026
4027 /**
4028  * soup_session_request_uri:
4029  * @session: a #SoupSession
4030  * @uri: a #SoupURI representing the URI to retrieve
4031  * @error: return location for a #GError, or %NULL
4032  *
4033  * Creates a #SoupRequest for retrieving @uri.
4034  *
4035  * Return value: (transfer full): a new #SoupRequest, or
4036  *   %NULL on error.
4037  *
4038  * Since: 2.42
4039  */
4040 SoupRequest *
4041 soup_session_request_uri (SoupSession *session, SoupURI *uri,
4042                           GError **error)
4043 {
4044         SoupSessionPrivate *priv;
4045         GType request_type;
4046
4047         g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
4048
4049         priv = SOUP_SESSION_GET_PRIVATE (session);
4050
4051         request_type = (GType)GPOINTER_TO_SIZE (g_hash_table_lookup (priv->request_types, uri->scheme));
4052         if (!request_type) {
4053                 g_set_error (error, SOUP_REQUEST_ERROR,
4054                              SOUP_REQUEST_ERROR_UNSUPPORTED_URI_SCHEME,
4055                              _("Unsupported URI scheme '%s'"), uri->scheme);
4056                 return NULL;
4057         }
4058
4059         return g_initable_new (request_type, NULL, error,
4060                                "uri", uri,
4061                                "session", session,
4062                                NULL);
4063 }
4064
4065 static SoupRequestHTTP *
4066 initialize_http_request (SoupRequest  *req,
4067                          const char   *method,
4068                          GError      **error)
4069 {
4070         SoupRequestHTTP *http;
4071         SoupMessage *msg;
4072
4073         if (!SOUP_IS_REQUEST_HTTP (req)) {
4074                 g_object_unref (req);
4075                 g_set_error (error, SOUP_REQUEST_ERROR,
4076                              SOUP_REQUEST_ERROR_BAD_URI,
4077                              _("Not an HTTP URI"));
4078                 return NULL;
4079         }
4080
4081         http = SOUP_REQUEST_HTTP (req);
4082         msg = soup_request_http_get_message (http);
4083         g_object_set (G_OBJECT (msg),
4084                       SOUP_MESSAGE_METHOD, method,
4085                       NULL);
4086         g_object_unref (msg);
4087
4088         return http;
4089 }
4090
4091 /**
4092  * soup_session_request_http:
4093  * @session: a #SoupSession
4094  * @method: an HTTP method
4095  * @uri_string: a URI, in string form
4096  * @error: return location for a #GError, or %NULL
4097  *
4098  * Creates a #SoupRequest for retrieving @uri_string, which must be an
4099  * "http" or "https" URI (or another protocol listed in @session's
4100  * #SoupSession:http-aliases or #SoupSession:https-aliases).
4101  *
4102  * Return value: (transfer full): a new #SoupRequestHTTP, or
4103  *   %NULL on error.
4104  *
4105  * Since: 2.42
4106  */
4107 SoupRequestHTTP *
4108 soup_session_request_http (SoupSession  *session,
4109                            const char   *method,
4110                            const char   *uri_string,
4111                            GError      **error)
4112 {
4113         SoupRequest *req;
4114
4115         req = soup_session_request (session, uri_string, error);
4116         if (!req)
4117                 return NULL;
4118
4119         return initialize_http_request (req, method, error);
4120 }
4121
4122 /**
4123  * soup_session_request_http_uri:
4124  * @session: a #SoupSession
4125  * @method: an HTTP method
4126  * @uri: a #SoupURI representing the URI to retrieve
4127  * @error: return location for a #GError, or %NULL
4128  *
4129  * Creates a #SoupRequest for retrieving @uri, which must be an
4130  * "http" or "https" URI (or another protocol listed in @session's
4131  * #SoupSession:http-aliases or #SoupSession:https-aliases).
4132  *
4133  * Return value: (transfer full): a new #SoupRequestHTTP, or
4134  *   %NULL on error.
4135  *
4136  * Since: 2.42
4137  */
4138 SoupRequestHTTP *
4139 soup_session_request_http_uri (SoupSession  *session,
4140                                const char   *method,
4141                                SoupURI      *uri,
4142                                GError      **error)
4143 {
4144         SoupRequest *req;
4145
4146         req = soup_session_request_uri (session, uri, error);
4147         if (!req)
4148                 return NULL;
4149
4150         return initialize_http_request (req, method, error);
4151 }
4152
4153 /**
4154  * SOUP_REQUEST_ERROR:
4155  *
4156  * A #GError domain for #SoupRequest<!-- -->-related errors. Used with
4157  * #SoupRequestError.
4158  *
4159  * Since: 2.42
4160  */
4161 /**
4162  * SoupRequestError:
4163  * @SOUP_REQUEST_ERROR_BAD_URI: the URI could not be parsed
4164  * @SOUP_REQUEST_ERROR_UNSUPPORTED_URI_SCHEME: the URI scheme is not
4165  *   supported by this #SoupSession
4166  * @SOUP_REQUEST_ERROR_PARSING: the server's response could not
4167  *   be parsed
4168  * @SOUP_REQUEST_ERROR_ENCODING: the server's response was in an
4169  *   unsupported format
4170  *
4171  * A #SoupRequest error.
4172  *
4173  * Since: 2.42
4174  */
4175
4176 GQuark
4177 soup_request_error_quark (void)
4178 {
4179         static GQuark error;
4180         if (!error)
4181                 error = g_quark_from_static_string ("soup_request_error_quark");
4182         return error;
4183 }