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