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