• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-cookie-jar.c
4  *
5  * Copyright (C) 2008 Red Hat, Inc.
6  */
7 
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11 
12 #include <string.h>
13 
14 #include "soup-cookie-jar.h"
15 #include "soup-message-private.h"
16 #include "soup-misc-private.h"
17 #include "soup.h"
18 
19 /**
20  * SECTION:soup-cookie-jar
21  * @short_description: Automatic cookie handling for SoupSession
22  *
23  * A #SoupCookieJar stores #SoupCookie<!-- -->s and arrange for them
24  * to be sent with the appropriate #SoupMessage<!-- -->s.
25  * #SoupCookieJar implements #SoupSessionFeature, so you can add a
26  * cookie jar to a session with soup_session_add_feature() or
27  * soup_session_add_feature_by_type().
28  *
29  * Note that the base #SoupCookieJar class does not support any form
30  * of long-term cookie persistence.
31  **/
32 
33 enum {
34 	CHANGED,
35 	LAST_SIGNAL
36 };
37 
38 static guint signals[LAST_SIGNAL] = { 0 };
39 
40 enum {
41 	PROP_0,
42 
43 	PROP_READ_ONLY,
44 	PROP_ACCEPT_POLICY,
45 
46 	LAST_PROP
47 };
48 
49 typedef struct {
50 	gboolean constructed, read_only;
51 	GHashTable *domains, *serials;
52 	guint serial;
53 	SoupCookieJarAcceptPolicy accept_policy;
54 } SoupCookieJarPrivate;
55 
56 static void soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
57 
G_DEFINE_TYPE_WITH_CODE(SoupCookieJar,soup_cookie_jar,G_TYPE_OBJECT,G_ADD_PRIVATE (SoupCookieJar)G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,soup_cookie_jar_session_feature_init))58 G_DEFINE_TYPE_WITH_CODE (SoupCookieJar, soup_cookie_jar, G_TYPE_OBJECT,
59                          G_ADD_PRIVATE (SoupCookieJar)
60 			 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
61 						soup_cookie_jar_session_feature_init))
62 
63 static void
64 soup_cookie_jar_init (SoupCookieJar *jar)
65 {
66 	SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
67 
68 	priv->domains = g_hash_table_new_full (soup_str_case_hash,
69 					       soup_str_case_equal,
70 					       g_free, NULL);
71 	priv->serials = g_hash_table_new (NULL, NULL);
72 	priv->accept_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
73 }
74 
75 static void
soup_cookie_jar_constructed(GObject * object)76 soup_cookie_jar_constructed (GObject *object)
77 {
78 	SoupCookieJarPrivate *priv =
79 		soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
80 
81 	priv->constructed = TRUE;
82 }
83 
84 static void
soup_cookie_jar_finalize(GObject * object)85 soup_cookie_jar_finalize (GObject *object)
86 {
87 	SoupCookieJarPrivate *priv =
88 		soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
89 	GHashTableIter iter;
90 	gpointer key, value;
91 
92 	g_hash_table_iter_init (&iter, priv->domains);
93 	while (g_hash_table_iter_next (&iter, &key, &value))
94 		soup_cookies_free (value);
95 	g_hash_table_destroy (priv->domains);
96 	g_hash_table_destroy (priv->serials);
97 
98 	G_OBJECT_CLASS (soup_cookie_jar_parent_class)->finalize (object);
99 }
100 
101 static void
soup_cookie_jar_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)102 soup_cookie_jar_set_property (GObject *object, guint prop_id,
103 			      const GValue *value, GParamSpec *pspec)
104 {
105 	SoupCookieJarPrivate *priv =
106 		soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
107 
108 	switch (prop_id) {
109 	case PROP_READ_ONLY:
110 		priv->read_only = g_value_get_boolean (value);
111 		break;
112 	case PROP_ACCEPT_POLICY:
113 		priv->accept_policy = g_value_get_enum (value);
114 		break;
115 	default:
116 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
117 		break;
118 	}
119 }
120 
121 static void
soup_cookie_jar_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)122 soup_cookie_jar_get_property (GObject *object, guint prop_id,
123 			      GValue *value, GParamSpec *pspec)
124 {
125 	SoupCookieJarPrivate *priv =
126 		soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
127 
128 	switch (prop_id) {
129 	case PROP_READ_ONLY:
130 		g_value_set_boolean (value, priv->read_only);
131 		break;
132 	case PROP_ACCEPT_POLICY:
133 		g_value_set_enum (value, priv->accept_policy);
134 		break;
135 	default:
136 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
137 		break;
138 	}
139 }
140 
141 static gboolean
soup_cookie_jar_real_is_persistent(SoupCookieJar * jar)142 soup_cookie_jar_real_is_persistent (SoupCookieJar *jar)
143 {
144 	return FALSE;
145 }
146 
147 static void
soup_cookie_jar_class_init(SoupCookieJarClass * jar_class)148 soup_cookie_jar_class_init (SoupCookieJarClass *jar_class)
149 {
150 	GObjectClass *object_class = G_OBJECT_CLASS (jar_class);
151 
152 	object_class->constructed = soup_cookie_jar_constructed;
153 	object_class->finalize = soup_cookie_jar_finalize;
154 	object_class->set_property = soup_cookie_jar_set_property;
155 	object_class->get_property = soup_cookie_jar_get_property;
156 
157 	jar_class->is_persistent = soup_cookie_jar_real_is_persistent;
158 
159 	/**
160 	 * SoupCookieJar::changed:
161 	 * @jar: the #SoupCookieJar
162 	 * @old_cookie: the old #SoupCookie value
163 	 * @new_cookie: the new #SoupCookie value
164 	 *
165 	 * Emitted when @jar changes. If a cookie has been added,
166 	 * @new_cookie will contain the newly-added cookie and
167 	 * @old_cookie will be %NULL. If a cookie has been deleted,
168 	 * @old_cookie will contain the to-be-deleted cookie and
169 	 * @new_cookie will be %NULL. If a cookie has been changed,
170 	 * @old_cookie will contain its old value, and @new_cookie its
171 	 * new value.
172 	 **/
173 	signals[CHANGED] =
174 		g_signal_new ("changed",
175 			      G_OBJECT_CLASS_TYPE (object_class),
176 			      G_SIGNAL_RUN_FIRST,
177 			      G_STRUCT_OFFSET (SoupCookieJarClass, changed),
178 			      NULL, NULL,
179 			      NULL,
180 			      G_TYPE_NONE, 2,
181 			      SOUP_TYPE_COOKIE | G_SIGNAL_TYPE_STATIC_SCOPE,
182 			      SOUP_TYPE_COOKIE | G_SIGNAL_TYPE_STATIC_SCOPE);
183 
184 	/**
185 	 * SOUP_COOKIE_JAR_READ_ONLY:
186 	 *
187 	 * Alias for the #SoupCookieJar:read-only property. (Whether
188 	 * or not the cookie jar is read-only.)
189 	 **/
190 	g_object_class_install_property (
191 		object_class, PROP_READ_ONLY,
192 		g_param_spec_boolean (SOUP_COOKIE_JAR_READ_ONLY,
193 				      "Read-only",
194 				      "Whether or not the cookie jar is read-only",
195 				      FALSE,
196 				      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
197 				      G_PARAM_STATIC_STRINGS));
198 
199 	/**
200 	 * SOUP_COOKIE_JAR_ACCEPT_POLICY:
201 	 *
202 	 * Alias for the #SoupCookieJar:accept-policy property.
203 	 *
204 	 * Since: 2.30
205 	 */
206 	/**
207 	 * SoupCookieJar:accept-policy:
208 	 *
209 	 * The policy the jar should follow to accept or reject cookies
210 	 *
211 	 * Since: 2.30
212 	 */
213 	g_object_class_install_property (
214 		object_class, PROP_ACCEPT_POLICY,
215 		g_param_spec_enum (SOUP_COOKIE_JAR_ACCEPT_POLICY,
216 				   "Accept-policy",
217 				   "The policy the jar should follow to accept or reject cookies",
218 				   SOUP_TYPE_COOKIE_JAR_ACCEPT_POLICY,
219 				   SOUP_COOKIE_JAR_ACCEPT_ALWAYS,
220 				   G_PARAM_READWRITE |
221 				   G_PARAM_STATIC_STRINGS));
222 }
223 
224 /**
225  * soup_cookie_jar_new:
226  *
227  * Creates a new #SoupCookieJar. The base #SoupCookieJar class does
228  * not support persistent storage of cookies; use a subclass for that.
229  *
230  * Returns: a new #SoupCookieJar
231  *
232  * Since: 2.24
233  **/
234 SoupCookieJar *
soup_cookie_jar_new(void)235 soup_cookie_jar_new (void)
236 {
237 	return g_object_new (SOUP_TYPE_COOKIE_JAR, NULL);
238 }
239 
240 /**
241  * soup_cookie_jar_save:
242  * @jar: a #SoupCookieJar
243  *
244  * This function exists for backward compatibility, but does not do
245  * anything any more; cookie jars are saved automatically when they
246  * are changed.
247  *
248  * Since: 2.24
249  *
250  * Deprecated: This is a no-op.
251  */
252 void
soup_cookie_jar_save(SoupCookieJar * jar)253 soup_cookie_jar_save (SoupCookieJar *jar)
254 {
255 	/* Does nothing, obsolete */
256 }
257 
258 static void
soup_cookie_jar_changed(SoupCookieJar * jar,SoupCookie * old,SoupCookie * new)259 soup_cookie_jar_changed (SoupCookieJar *jar,
260 			 SoupCookie *old, SoupCookie *new)
261 {
262 	SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
263 
264 	if (old && old != new)
265 		g_hash_table_remove (priv->serials, old);
266 	if (new) {
267 		priv->serial++;
268 		g_hash_table_insert (priv->serials, new, GUINT_TO_POINTER (priv->serial));
269 	}
270 
271 	if (priv->read_only || !priv->constructed)
272 		return;
273 
274 	g_signal_emit (jar, signals[CHANGED], 0, old, new);
275 }
276 
277 static int
compare_cookies(gconstpointer a,gconstpointer b,gpointer jar)278 compare_cookies (gconstpointer a, gconstpointer b, gpointer jar)
279 {
280 	SoupCookie *ca = (SoupCookie *)a;
281 	SoupCookie *cb = (SoupCookie *)b;
282 	SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
283 	int alen, blen;
284 	guint aserial, bserial;
285 
286 	/* "Cookies with longer path fields are listed before cookies
287 	 * with shorter path field."
288 	 */
289 	alen = ca->path ? strlen (ca->path) : 0;
290 	blen = cb->path ? strlen (cb->path) : 0;
291 	if (alen != blen)
292 		return blen - alen;
293 
294 	/* "Among cookies that have equal length path fields, cookies
295 	 * with earlier creation dates are listed before cookies with
296 	 * later creation dates."
297 	 */
298 	aserial = GPOINTER_TO_UINT (g_hash_table_lookup (priv->serials, ca));
299 	bserial = GPOINTER_TO_UINT (g_hash_table_lookup (priv->serials, cb));
300 	return aserial - bserial;
301 }
302 
303 static gboolean
cookie_is_valid_for_same_site_policy(SoupCookie * cookie,gboolean is_safe_method,SoupURI * uri,SoupURI * top_level,SoupURI * cookie_uri,gboolean is_top_level_navigation,gboolean for_http)304 cookie_is_valid_for_same_site_policy (SoupCookie *cookie,
305                                       gboolean    is_safe_method,
306                                       SoupURI    *uri,
307                                       SoupURI    *top_level,
308                                       SoupURI    *cookie_uri,
309                                       gboolean    is_top_level_navigation,
310                                       gboolean    for_http)
311 {
312 	SoupSameSitePolicy policy = soup_cookie_get_same_site_policy (cookie);
313 
314 	if (policy == SOUP_SAME_SITE_POLICY_NONE)
315 		return TRUE;
316 
317 	if (top_level == NULL)
318 		return TRUE;
319 
320 	if (policy == SOUP_SAME_SITE_POLICY_LAX && is_top_level_navigation &&
321 	    (is_safe_method || for_http == FALSE))
322 		return TRUE;
323 
324 	if (is_top_level_navigation && cookie_uri == NULL)
325 		return FALSE;
326 
327 	return soup_host_matches_host (soup_uri_get_host (cookie_uri ? cookie_uri : top_level), soup_uri_get_host (uri));
328 }
329 
330 static GSList *
get_cookies(SoupCookieJar * jar,SoupURI * uri,SoupURI * top_level,SoupURI * site_for_cookies,gboolean is_safe_method,gboolean for_http,gboolean is_top_level_navigation,gboolean copy_cookies)331 get_cookies (SoupCookieJar *jar,
332              SoupURI       *uri,
333              SoupURI       *top_level,
334              SoupURI       *site_for_cookies,
335              gboolean       is_safe_method,
336              gboolean       for_http,
337              gboolean       is_top_level_navigation,
338              gboolean       copy_cookies)
339 {
340 	SoupCookieJarPrivate *priv;
341 	GSList *cookies, *domain_cookies;
342 	char *domain, *cur, *next_domain;
343 	GSList *new_head, *cookies_to_remove = NULL, *p;
344 
345 	priv = soup_cookie_jar_get_instance_private (jar);
346 
347 	if (!uri->host || !uri->host[0])
348 		return NULL;
349 
350 	/* The logic here is a little weird, but the plan is that if
351 	 * uri->host is "www.foo.com", we will end up looking up
352 	 * cookies for ".www.foo.com", "www.foo.com", ".foo.com", and
353 	 * ".com", in that order. (Logic stolen from Mozilla.)
354 	 */
355 	cookies = NULL;
356 	domain = cur = g_strdup_printf (".%s", uri->host);
357 	next_domain = domain + 1;
358 	do {
359 		new_head = domain_cookies = g_hash_table_lookup (priv->domains, cur);
360 		while (domain_cookies) {
361 			GSList *next = domain_cookies->next;
362 			SoupCookie *cookie = domain_cookies->data;
363 
364 			if (cookie->expires && soup_date_is_past (cookie->expires)) {
365 				cookies_to_remove = g_slist_append (cookies_to_remove,
366 								    cookie);
367 				new_head = g_slist_delete_link (new_head, domain_cookies);
368 				g_hash_table_insert (priv->domains,
369 						     g_strdup (cur),
370 						     new_head);
371 			} else if (soup_cookie_applies_to_uri (cookie, uri) &&
372 			           cookie_is_valid_for_same_site_policy (cookie, is_safe_method, uri, top_level,
373 				                                         site_for_cookies, is_top_level_navigation,
374 									 for_http) &&
375 				   (for_http || !cookie->http_only))
376 				cookies = g_slist_append (cookies, copy_cookies ? soup_cookie_copy (cookie) : cookie);
377 
378 			domain_cookies = next;
379 		}
380 		cur = next_domain;
381 		if (cur)
382 			next_domain = strchr (cur + 1, '.');
383 	} while (cur);
384 	g_free (domain);
385 
386 	for (p = cookies_to_remove; p; p = p->next) {
387 		SoupCookie *cookie = p->data;
388 
389 		soup_cookie_jar_changed (jar, cookie, NULL);
390 		soup_cookie_free (cookie);
391 	}
392 	g_slist_free (cookies_to_remove);
393 
394 	return g_slist_sort_with_data (cookies, compare_cookies, jar);
395 }
396 
397 /**
398  * soup_cookie_jar_get_cookies:
399  * @jar: a #SoupCookieJar
400  * @uri: a #SoupURI
401  * @for_http: whether or not the return value is being passed directly
402  * to an HTTP operation
403  *
404  * Retrieves (in Cookie-header form) the list of cookies that would
405  * be sent with a request to @uri.
406  *
407  * If @for_http is %TRUE, the return value will include cookies marked
408  * "HttpOnly" (that is, cookies that the server wishes to keep hidden
409  * from client-side scripting operations such as the JavaScript
410  * document.cookies property). Since #SoupCookieJar sets the Cookie
411  * header itself when making the actual HTTP request, you should
412  * almost certainly be setting @for_http to %FALSE if you are calling
413  * this.
414  *
415  * Return value: (nullable): the cookies, in string form, or %NULL if
416  * there are no cookies for @uri.
417  *
418  * Since: 2.24
419  **/
420 char *
soup_cookie_jar_get_cookies(SoupCookieJar * jar,SoupURI * uri,gboolean for_http)421 soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri,
422 			     gboolean for_http)
423 {
424 	GSList *cookies;
425 
426 	g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
427 	g_return_val_if_fail (uri != NULL, NULL);
428 
429 	cookies = get_cookies (jar, uri, NULL, NULL, TRUE, for_http, FALSE, FALSE);
430 
431 	if (cookies) {
432 		char *result = soup_cookies_to_cookie_header (cookies);
433 		g_slist_free (cookies);
434 
435 		if (!*result) {
436 			g_free (result);
437 			result = NULL;
438 		}
439 		return result;
440 	} else
441 		return NULL;
442 }
443 
444 /**
445  * soup_cookie_jar_get_cookie_list:
446  * @jar: a #SoupCookieJar
447  * @uri: a #SoupURI
448  * @for_http: whether or not the return value is being passed directly
449  * to an HTTP operation
450  *
451  * Retrieves the list of cookies that would be sent with a request to @uri
452  * as a #GSList of #SoupCookie objects.
453  *
454  * If @for_http is %TRUE, the return value will include cookies marked
455  * "HttpOnly" (that is, cookies that the server wishes to keep hidden
456  * from client-side scripting operations such as the JavaScript
457  * document.cookies property). Since #SoupCookieJar sets the Cookie
458  * header itself when making the actual HTTP request, you should
459  * almost certainly be setting @for_http to %FALSE if you are calling
460  * this.
461  *
462  * Return value: (transfer full) (element-type Soup.Cookie): a #GSList
463  * with the cookies in the @jar that would be sent with a request to @uri.
464  *
465  * Since: 2.40
466  **/
467 GSList *
soup_cookie_jar_get_cookie_list(SoupCookieJar * jar,SoupURI * uri,gboolean for_http)468 soup_cookie_jar_get_cookie_list (SoupCookieJar *jar, SoupURI *uri, gboolean for_http)
469 {
470 	g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
471 	g_return_val_if_fail (uri != NULL, NULL);
472 
473 	return get_cookies (jar, uri, NULL, NULL, TRUE, for_http, FALSE, TRUE);
474 }
475 
476 /**
477  * soup_cookie_jar_get_cookie_list_with_same_site_info:
478  * @jar: a #SoupCookieJar
479  * @uri: a #SoupURI
480  * @top_level: (nullable): a #SoupURI for the top level document
481  * @site_for_cookies: (nullable): a #SoupURI indicating the origin to get cookies for
482  * @for_http: whether or not the return value is being passed directly
483  * to an HTTP operation
484  * @is_safe_method: if the HTTP method is safe, as defined by RFC 7231, ignored when @for_http is %FALSE
485  * @is_top_level_navigation: whether or not the HTTP request is part of
486  * top level navigation
487  *
488  * This is an extended version of soup_cookie_jar_get_cookie_list() that
489  * provides more information required to use SameSite cookies. See the
490  * [SameSite cookies spec](https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00)
491  * for more detailed information.
492  *
493  * Return value: (transfer full) (element-type Soup.Cookie): a #GSList
494  * with the cookies in the @jar that would be sent with a request to @uri.
495  *
496  * Since: 2.70
497  */
498 GSList *
soup_cookie_jar_get_cookie_list_with_same_site_info(SoupCookieJar * jar,SoupURI * uri,SoupURI * top_level,SoupURI * site_for_cookies,gboolean for_http,gboolean is_safe_method,gboolean is_top_level_navigation)499 soup_cookie_jar_get_cookie_list_with_same_site_info (SoupCookieJar *jar,
500                                                      SoupURI       *uri,
501                                                      SoupURI       *top_level,
502                                                      SoupURI       *site_for_cookies,
503                                                      gboolean       for_http,
504                                                      gboolean       is_safe_method,
505                                                      gboolean       is_top_level_navigation)
506 {
507 	g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
508 	g_return_val_if_fail (uri != NULL, NULL);
509 
510 	return get_cookies (jar,  uri, top_level, site_for_cookies, is_safe_method, for_http, is_top_level_navigation, TRUE);
511 }
512 
513 static const char *
normalize_cookie_domain(const char * domain)514 normalize_cookie_domain (const char *domain)
515 {
516 	/* Trim any leading dot if present to transform the cookie
517          * domain into a valid hostname.
518          */
519 	if (domain != NULL && domain[0] == '.')
520 		return domain + 1;
521 	return domain;
522 }
523 
524 static gboolean
incoming_cookie_is_third_party(SoupCookieJar * jar,SoupCookie * cookie,SoupURI * first_party,SoupCookieJarAcceptPolicy policy)525 incoming_cookie_is_third_party (SoupCookieJar            *jar,
526 				SoupCookie               *cookie,
527 				SoupURI                  *first_party,
528 				SoupCookieJarAcceptPolicy policy)
529 {
530 	SoupCookieJarPrivate *priv;
531 	const char *normalized_cookie_domain;
532 	const char *cookie_base_domain;
533 	const char *first_party_base_domain;
534 
535 	if (policy != SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY &&
536 	    policy != SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY)
537 		return FALSE;
538 
539 	if (first_party == NULL || first_party->host == NULL)
540 		return TRUE;
541 
542 	normalized_cookie_domain = normalize_cookie_domain (cookie->domain);
543 	cookie_base_domain = soup_tld_get_base_domain (normalized_cookie_domain, NULL);
544 	if (cookie_base_domain == NULL)
545 		cookie_base_domain = cookie->domain;
546 
547 	first_party_base_domain = soup_tld_get_base_domain (first_party->host, NULL);
548 	if (first_party_base_domain == NULL)
549 		first_party_base_domain = first_party->host;
550 
551 	if (soup_host_matches_host (cookie_base_domain, first_party_base_domain))
552 		return FALSE;
553 
554 	if (policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
555 		return TRUE;
556 
557 	/* Now we know the cookie's base domain and the first party's base domain
558 	 * are different, but for SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY
559 	 * policy we want to grandfather in any domain that's already in the jar.
560 	 * That is, we never want to block cookies from domains the user has
561 	 * previously visited directly.
562 	 */
563 	priv = soup_cookie_jar_get_instance_private (jar);
564 	return !g_hash_table_lookup (priv->domains, cookie->domain);
565 }
566 
567 /**
568  * soup_cookie_jar_add_cookie_full:
569  * @jar: a #SoupCookieJar
570  * @cookie: (transfer full): a #SoupCookie
571  * @uri: (nullable): the URI setting the cookie
572  * @first_party: (nullable): the URI for the main document
573  *
574  * Adds @cookie to @jar, emitting the 'changed' signal if we are modifying
575  * an existing cookie or adding a valid new cookie ('valid' means
576  * that the cookie's expire date is not in the past).
577  *
578  * @first_party will be used to reject cookies coming from third party
579  * resources in case such a security policy is set in the @jar.
580  *
581  * @uri will be used to reject setting or overwriting secure cookies
582  * from insecure origins. %NULL is treated as secure.
583  *
584  * @cookie will be 'stolen' by the jar, so don't free it afterwards.
585  *
586  * Since: 2.68
587  **/
588 void
soup_cookie_jar_add_cookie_full(SoupCookieJar * jar,SoupCookie * cookie,SoupURI * uri,SoupURI * first_party)589 soup_cookie_jar_add_cookie_full (SoupCookieJar *jar, SoupCookie *cookie, SoupURI *uri, SoupURI *first_party)
590 {
591 	SoupCookieJarPrivate *priv;
592 	GSList *old_cookies, *oc, *last = NULL;
593 	SoupCookie *old_cookie;
594 
595 	g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
596 	g_return_if_fail (cookie != NULL);
597 
598 	/* Never accept cookies for public domains. */
599 	if (!g_hostname_is_ip_address (cookie->domain) &&
600 	    soup_tld_domain_is_public_suffix (cookie->domain)) {
601 		soup_cookie_free (cookie);
602 		return;
603 	}
604 
605 	priv = soup_cookie_jar_get_instance_private (jar);
606 
607         if (first_party != NULL) {
608                 if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER ||
609                     incoming_cookie_is_third_party (jar, cookie, first_party, priv->accept_policy)) {
610                         soup_cookie_free (cookie);
611                         return;
612                 }
613         }
614 
615 	/* Cannot set a secure cookie over http */
616 	if (uri != NULL && !soup_uri_is_https (uri, NULL) && soup_cookie_get_secure (cookie)) {
617 		soup_cookie_free (cookie);
618 		return;
619 	}
620 
621 	old_cookies = g_hash_table_lookup (priv->domains, cookie->domain);
622 	for (oc = old_cookies; oc; oc = oc->next) {
623 		old_cookie = oc->data;
624 		if (!strcmp (cookie->name, old_cookie->name) &&
625 		    !g_strcmp0 (cookie->path, old_cookie->path)) {
626 			if (soup_cookie_get_secure (oc->data) && uri != NULL && !soup_uri_is_https (uri, NULL)) {
627 				/* We do not allow overwriting secure cookies from an insecure origin
628 				 * https://tools.ietf.org/html/draft-ietf-httpbis-cookie-alone-01
629 				 */
630 				soup_cookie_free (cookie);
631 			} else if (cookie->expires && soup_date_is_past (cookie->expires)) {
632 				/* The new cookie has an expired date,
633 				 * this is the way the the server has
634 				 * of telling us that we have to
635 				 * remove the cookie.
636 				 */
637 				old_cookies = g_slist_delete_link (old_cookies, oc);
638 				g_hash_table_insert (priv->domains,
639 						     g_strdup (cookie->domain),
640 						     old_cookies);
641 				soup_cookie_jar_changed (jar, old_cookie, NULL);
642 				soup_cookie_free (old_cookie);
643 				soup_cookie_free (cookie);
644 			} else {
645 				oc->data = cookie;
646 				soup_cookie_jar_changed (jar, old_cookie, cookie);
647 				soup_cookie_free (old_cookie);
648 			}
649 
650 			return;
651 		}
652 		last = oc;
653 	}
654 
655 	/* The new cookie is... a new cookie */
656 	if (cookie->expires && soup_date_is_past (cookie->expires)) {
657 		soup_cookie_free (cookie);
658 		return;
659 	}
660 
661 	if (last)
662 		last->next = g_slist_append (NULL, cookie);
663 	else {
664 		old_cookies = g_slist_append (NULL, cookie);
665 		g_hash_table_insert (priv->domains, g_strdup (cookie->domain),
666 				     old_cookies);
667 	}
668 
669 	soup_cookie_jar_changed (jar, NULL, cookie);
670 }
671 
672 /**
673  * soup_cookie_jar_add_cookie:
674  * @jar: a #SoupCookieJar
675  * @cookie: (transfer full): a #SoupCookie
676  *
677  * Adds @cookie to @jar, emitting the 'changed' signal if we are modifying
678  * an existing cookie or adding a valid new cookie ('valid' means
679  * that the cookie's expire date is not in the past).
680  *
681  * @cookie will be 'stolen' by the jar, so don't free it afterwards.
682  *
683  * Since: 2.26
684  **/
685 void
soup_cookie_jar_add_cookie(SoupCookieJar * jar,SoupCookie * cookie)686 soup_cookie_jar_add_cookie (SoupCookieJar *jar, SoupCookie *cookie)
687 {
688 	soup_cookie_jar_add_cookie_full (jar, cookie, NULL, NULL);
689 }
690 
691 /**
692  * soup_cookie_jar_add_cookie_with_first_party:
693  * @jar: a #SoupCookieJar
694  * @first_party: the URI for the main document
695  * @cookie: (transfer full): a #SoupCookie
696  *
697  * Adds @cookie to @jar, emitting the 'changed' signal if we are modifying
698  * an existing cookie or adding a valid new cookie ('valid' means
699  * that the cookie's expire date is not in the past).
700  *
701  * @first_party will be used to reject cookies coming from third party
702  * resources in case such a security policy is set in the @jar.
703  *
704  * @cookie will be 'stolen' by the jar, so don't free it afterwards.
705  *
706  * For secure cookies to work properly you may want to use
707  * soup_cookie_jar_add_cookie_full().
708  *
709  * Since: 2.40
710  **/
711 void
soup_cookie_jar_add_cookie_with_first_party(SoupCookieJar * jar,SoupURI * first_party,SoupCookie * cookie)712 soup_cookie_jar_add_cookie_with_first_party (SoupCookieJar *jar, SoupURI *first_party, SoupCookie *cookie)
713 {
714 	g_return_if_fail (first_party != NULL);
715 
716 	soup_cookie_jar_add_cookie_full (jar, cookie, NULL, first_party);
717 }
718 
719 /**
720  * soup_cookie_jar_set_cookie:
721  * @jar: a #SoupCookieJar
722  * @uri: the URI setting the cookie
723  * @cookie: the stringified cookie to set
724  *
725  * Adds @cookie to @jar, exactly as though it had appeared in a
726  * Set-Cookie header returned from a request to @uri.
727  *
728  * Keep in mind that if the #SoupCookieJarAcceptPolicy set is either
729  * %SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY or
730  * %SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY you'll need to use
731  * soup_cookie_jar_set_cookie_with_first_party(), otherwise the jar
732  * will have no way of knowing if the cookie is being set by a third
733  * party or not.
734  *
735  * Since: 2.24
736  **/
737 void
soup_cookie_jar_set_cookie(SoupCookieJar * jar,SoupURI * uri,const char * cookie)738 soup_cookie_jar_set_cookie (SoupCookieJar *jar, SoupURI *uri,
739 			    const char *cookie)
740 {
741 	SoupCookie *soup_cookie;
742 	SoupCookieJarPrivate *priv;
743 
744 	g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
745 	g_return_if_fail (uri != NULL);
746 	g_return_if_fail (cookie != NULL);
747 
748 	if (!uri->host)
749 		return;
750 
751 	priv = soup_cookie_jar_get_instance_private (jar);
752 	if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
753 		return;
754 
755 	g_return_if_fail (priv->accept_policy != SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY &&
756 			  priv->accept_policy != SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY);
757 
758 	soup_cookie = soup_cookie_parse (cookie, uri);
759 	if (soup_cookie) {
760 		/* will steal or free soup_cookie */
761 		soup_cookie_jar_add_cookie_full (jar, soup_cookie, uri, NULL);
762 	}
763 }
764 
765 /**
766  * soup_cookie_jar_set_cookie_with_first_party:
767  * @jar: a #SoupCookieJar
768  * @uri: the URI setting the cookie
769  * @first_party: the URI for the main document
770  * @cookie: the stringified cookie to set
771  *
772  * Adds @cookie to @jar, exactly as though it had appeared in a
773  * Set-Cookie header returned from a request to @uri. @first_party
774  * will be used to reject cookies coming from third party resources in
775  * case such a security policy is set in the @jar.
776  *
777  * Since: 2.30
778  **/
779 void
soup_cookie_jar_set_cookie_with_first_party(SoupCookieJar * jar,SoupURI * uri,SoupURI * first_party,const char * cookie)780 soup_cookie_jar_set_cookie_with_first_party (SoupCookieJar *jar,
781 					     SoupURI *uri,
782 					     SoupURI *first_party,
783 					     const char *cookie)
784 {
785 	SoupCookie *soup_cookie;
786 
787 	g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
788 	g_return_if_fail (uri != NULL);
789 	g_return_if_fail (first_party != NULL);
790 	g_return_if_fail (cookie != NULL);
791 
792 	if (!uri->host)
793 		return;
794 
795 	soup_cookie = soup_cookie_parse (cookie, uri);
796 	if (soup_cookie) {
797 		soup_cookie_jar_add_cookie_full (jar, soup_cookie, uri, first_party);
798 	}
799 }
800 
801 static void
process_set_cookie_header(SoupMessage * msg,gpointer user_data)802 process_set_cookie_header (SoupMessage *msg, gpointer user_data)
803 {
804 	SoupCookieJar *jar = user_data;
805 	SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
806 	GSList *new_cookies, *nc;
807 	SoupURI *first_party, *uri;
808 
809 	if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
810 		return;
811 
812 	new_cookies = soup_cookies_from_response (msg);
813 	first_party = soup_message_get_first_party (msg);
814 	uri = soup_message_get_uri (msg);
815 	for (nc = new_cookies; nc; nc = nc->next) {
816 		soup_cookie_jar_add_cookie_full (jar, g_steal_pointer (&nc->data), uri, first_party);
817 	}
818 	g_slist_free (new_cookies);
819 }
820 
821 static void
msg_starting_cb(SoupMessage * msg,gpointer feature)822 msg_starting_cb (SoupMessage *msg, gpointer feature)
823 {
824 	SoupCookieJar *jar = SOUP_COOKIE_JAR (feature);
825 	GSList *cookies;
826 
827 	cookies = soup_cookie_jar_get_cookie_list_with_same_site_info (jar, soup_message_get_uri (msg),
828 	                                                               soup_message_get_first_party (msg),
829 							               soup_message_get_site_for_cookies (msg),
830 								       TRUE,
831 							               SOUP_METHOD_IS_SAFE (msg->method),
832 							               soup_message_get_is_top_level_navigation (msg));
833 	if (cookies != NULL) {
834 		char *cookie_header = soup_cookies_to_cookie_header (cookies);
835 		soup_message_headers_replace (msg->request_headers, "Cookie", cookie_header);
836 		g_free (cookie_header);
837 		g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
838 	} else {
839 		soup_message_headers_remove (msg->request_headers, "Cookie");
840 	}
841 }
842 
843 static void
soup_cookie_jar_request_queued(SoupSessionFeature * feature,SoupSession * session,SoupMessage * msg)844 soup_cookie_jar_request_queued (SoupSessionFeature *feature,
845 				SoupSession *session,
846 				SoupMessage *msg)
847 {
848 	g_signal_connect (msg, "starting",
849 			  G_CALLBACK (msg_starting_cb),
850 			  feature);
851 
852 	soup_message_add_header_handler (msg, "got-headers",
853 					 "Set-Cookie",
854 					 G_CALLBACK (process_set_cookie_header),
855 					 feature);
856         soup_message_add_status_code_handler (msg, "got-informational",
857                                               SOUP_STATUS_SWITCHING_PROTOCOLS,
858                                               G_CALLBACK (process_set_cookie_header),
859                                               feature);
860 }
861 
862 static void
soup_cookie_jar_request_unqueued(SoupSessionFeature * feature,SoupSession * session,SoupMessage * msg)863 soup_cookie_jar_request_unqueued (SoupSessionFeature *feature,
864 				  SoupSession *session,
865 				  SoupMessage *msg)
866 {
867 	g_signal_handlers_disconnect_by_func (msg, process_set_cookie_header, feature);
868 }
869 
870 static void
soup_cookie_jar_session_feature_init(SoupSessionFeatureInterface * feature_interface,gpointer interface_data)871 soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface,
872 				      gpointer interface_data)
873 {
874 	feature_interface->request_queued = soup_cookie_jar_request_queued;
875 	feature_interface->request_unqueued = soup_cookie_jar_request_unqueued;
876 }
877 
878 /**
879  * soup_cookie_jar_all_cookies:
880  * @jar: a #SoupCookieJar
881  *
882  * Constructs a #GSList with every cookie inside the @jar.
883  * The cookies in the list are a copy of the original, so
884  * you have to free them when you are done with them.
885  *
886  * Return value: (transfer full) (element-type Soup.Cookie): a #GSList
887  * with all the cookies in the @jar.
888  *
889  * Since: 2.26
890  **/
891 GSList *
soup_cookie_jar_all_cookies(SoupCookieJar * jar)892 soup_cookie_jar_all_cookies (SoupCookieJar *jar)
893 {
894 	SoupCookieJarPrivate *priv;
895 	GHashTableIter iter;
896 	GSList *l = NULL;
897 	gpointer key, value;
898 
899 	g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
900 
901 	priv = soup_cookie_jar_get_instance_private (jar);
902 
903 	g_hash_table_iter_init (&iter, priv->domains);
904 
905 	while (g_hash_table_iter_next (&iter, &key, &value)) {
906 		GSList *p, *cookies = value;
907 		for (p = cookies; p; p = p->next)
908 			l = g_slist_prepend (l, soup_cookie_copy (p->data));
909 	}
910 
911 	return l;
912 }
913 
914 /**
915  * soup_cookie_jar_delete_cookie:
916  * @jar: a #SoupCookieJar
917  * @cookie: a #SoupCookie
918  *
919  * Deletes @cookie from @jar, emitting the 'changed' signal.
920  *
921  * Since: 2.26
922  **/
923 void
soup_cookie_jar_delete_cookie(SoupCookieJar * jar,SoupCookie * cookie)924 soup_cookie_jar_delete_cookie (SoupCookieJar *jar,
925 			       SoupCookie    *cookie)
926 {
927 	SoupCookieJarPrivate *priv;
928 	GSList *cookies, *p;
929 
930 	g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
931 	g_return_if_fail (cookie != NULL);
932 
933 	priv = soup_cookie_jar_get_instance_private (jar);
934 
935 	cookies = g_hash_table_lookup (priv->domains, cookie->domain);
936 	if (cookies == NULL)
937 		return;
938 
939 	for (p = cookies; p; p = p->next ) {
940 		SoupCookie *c = (SoupCookie*)p->data;
941 		if (soup_cookie_equal (cookie, c)) {
942 			cookies = g_slist_delete_link (cookies, p);
943 			g_hash_table_insert (priv->domains,
944 					     g_strdup (cookie->domain),
945 					     cookies);
946 			soup_cookie_jar_changed (jar, c, NULL);
947 			soup_cookie_free (c);
948 			return;
949 		}
950 	}
951 }
952 
953 /**
954  * SoupCookieJarAcceptPolicy:
955  * @SOUP_COOKIE_JAR_ACCEPT_ALWAYS: accept all cookies unconditionally.
956  * @SOUP_COOKIE_JAR_ACCEPT_NEVER: reject all cookies unconditionally.
957  * @SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY: accept all cookies set by
958  * the main document loaded in the application using libsoup. An
959  * example of the most common case, web browsers, would be: If
960  * http://www.example.com is the page loaded, accept all cookies set
961  * by example.com, but if a resource from http://www.third-party.com
962  * is loaded from that page reject any cookie that it could try to
963  * set. For libsoup to be able to tell apart first party cookies from
964  * the rest, the application must call soup_message_set_first_party()
965  * on each outgoing #SoupMessage, setting the #SoupURI of the main
966  * document. If no first party is set in a message when this policy is
967  * in effect, cookies will be assumed to be third party by default.
968  * @SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY: accept all cookies
969  * set by the main document loaded in the application using libsoup, and
970  * from domains that have previously set at least one cookie when loaded
971  * as the main document. An example of the most common case, web browsers,
972  * would be: if http://www.example.com is the page loaded, accept all
973  * cookies set by example.com, but if a resource from http://www.third-party.com
974  * is loaded from that page, reject any cookie that it could try to
975  * set unless it already has a cookie in the cookie jar. For libsoup to
976  * be able to tell apart first party cookies from the rest, the
977  * application must call soup_message_set_first_party() on each outgoing
978  * #SoupMessage, setting the #SoupURI of the main document. If no first
979  * party is set in a message when this policy is in effect, cookies will
980  * be assumed to be third party by default. Since 2.72.
981  *
982  * The policy for accepting or rejecting cookies returned in
983  * responses.
984  *
985  * Since: 2.30
986  */
987 
988 /**
989  * soup_cookie_jar_get_accept_policy:
990  * @jar: a #SoupCookieJar
991  *
992  * Gets @jar's #SoupCookieJarAcceptPolicy
993  *
994  * Returns: the #SoupCookieJarAcceptPolicy set in the @jar
995  *
996  * Since: 2.30
997  **/
998 SoupCookieJarAcceptPolicy
soup_cookie_jar_get_accept_policy(SoupCookieJar * jar)999 soup_cookie_jar_get_accept_policy (SoupCookieJar *jar)
1000 {
1001 	SoupCookieJarPrivate *priv;
1002 
1003 	g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), SOUP_COOKIE_JAR_ACCEPT_ALWAYS);
1004 
1005 	priv = soup_cookie_jar_get_instance_private (jar);
1006 	return priv->accept_policy;
1007 }
1008 
1009 /**
1010  * soup_cookie_jar_set_accept_policy:
1011  * @jar: a #SoupCookieJar
1012  * @policy: a #SoupCookieJarAcceptPolicy
1013  *
1014  * Sets @policy as the cookie acceptance policy for @jar.
1015  *
1016  * Since: 2.30
1017  **/
1018 void
soup_cookie_jar_set_accept_policy(SoupCookieJar * jar,SoupCookieJarAcceptPolicy policy)1019 soup_cookie_jar_set_accept_policy (SoupCookieJar *jar,
1020 				   SoupCookieJarAcceptPolicy policy)
1021 {
1022 	SoupCookieJarPrivate *priv;
1023 
1024 	g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
1025 
1026 	priv = soup_cookie_jar_get_instance_private (jar);
1027 
1028 	if (priv->accept_policy != policy) {
1029 		priv->accept_policy = policy;
1030 		g_object_notify (G_OBJECT (jar), SOUP_COOKIE_JAR_ACCEPT_POLICY);
1031 	}
1032 }
1033 
1034 /**
1035  * soup_cookie_jar_is_persistent:
1036  * @jar: a #SoupCookieJar
1037  *
1038  * Gets whether @jar stores cookies persistenly.
1039  *
1040  * Returns: %TRUE if @jar storage is persistent or %FALSE otherwise.
1041  *
1042  * Since: 2.40
1043  **/
1044 gboolean
soup_cookie_jar_is_persistent(SoupCookieJar * jar)1045 soup_cookie_jar_is_persistent (SoupCookieJar *jar)
1046 {
1047 	g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), FALSE);
1048 
1049 	return SOUP_COOKIE_JAR_GET_CLASS (jar)->is_persistent (jar);
1050 }
1051