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