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