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