• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-cookie.c
4  *
5  * Copyright (C) 2007 Red Hat, Inc.
6  */
7 
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11 
12 #include <stdlib.h>
13 #include <string.h>
14 
15 #include "soup-cookie.h"
16 #include "soup-misc-private.h"
17 #include "soup.h"
18 
19 /**
20  * SECTION:soup-cookie
21  * @short_description: HTTP Cookies
22  * @see_also: #SoupMessage, #SoupCookieJar
23  *
24  * #SoupCookie implements HTTP cookies, as described by <ulink
25  * url="http://tools.ietf.org/html/rfc6265.txt">RFC 6265</ulink>.
26  *
27  * To have a #SoupSession handle cookies for your appliction
28  * automatically, use a #SoupCookieJar.
29  **/
30 
31 /**
32  * SoupCookie:
33  * @name: the cookie name
34  * @value: the cookie value
35  * @domain: the "domain" attribute, or else the hostname that the
36  * cookie came from.
37  * @path: the "path" attribute, or %NULL
38  * @expires: the cookie expiration time, or %NULL for a session cookie
39  * @secure: %TRUE if the cookie should only be tranferred over SSL
40  * @http_only: %TRUE if the cookie should not be exposed to scripts
41  *
42  * An HTTP cookie.
43  *
44  * @name and @value will be set for all cookies. If the cookie is
45  * generated from a string that appears to have no name, then @name
46  * will be the empty string.
47  *
48  * @domain and @path give the host or domain, and path within that
49  * host/domain, to restrict this cookie to. If @domain starts with
50  * ".", that indicates a domain (which matches the string after the
51  * ".", or any hostname that has @domain as a suffix). Otherwise, it
52  * is a hostname and must match exactly.
53  *
54  * @expires will be non-%NULL if the cookie uses either the original
55  * "expires" attribute, or the newer "max-age" attribute. If @expires
56  * is %NULL, it indicates that neither "expires" nor "max-age" was
57  * specified, and the cookie expires at the end of the session.
58  *
59  * If @http_only is set, the cookie should not be exposed to untrusted
60  * code (eg, javascript), so as to minimize the danger posed by
61  * cross-site scripting attacks.
62  *
63  * Since: 2.24
64  **/
65 
G_DEFINE_BOXED_TYPE(SoupCookie,soup_cookie,soup_cookie_copy,soup_cookie_free)66 G_DEFINE_BOXED_TYPE (SoupCookie, soup_cookie, soup_cookie_copy, soup_cookie_free)
67 
68 /**
69  * soup_cookie_copy:
70  * @cookie: a #SoupCookie
71  *
72  * Copies @cookie.
73  *
74  * Return value: a copy of @cookie
75  *
76  * Since: 2.24
77  **/
78 SoupCookie *
79 soup_cookie_copy (SoupCookie *cookie)
80 {
81 	SoupCookie *copy = g_slice_new0 (SoupCookie);
82 
83 	copy->name = g_strdup (cookie->name);
84 	copy->value = g_strdup (cookie->value);
85 	copy->domain = g_strdup (cookie->domain);
86 	copy->path = g_strdup (cookie->path);
87 	if (cookie->expires)
88 		copy->expires = soup_date_copy(cookie->expires);
89 	copy->secure = cookie->secure;
90 	copy->http_only = cookie->http_only;
91 	soup_cookie_set_same_site_policy (copy, soup_cookie_get_same_site_policy (cookie));
92 
93 	return copy;
94 }
95 
96 /**
97  * soup_cookie_domain_matches:
98  * @cookie: a #SoupCookie
99  * @host: a URI
100  *
101  * Checks if the @cookie's domain and @host match in the sense that
102  * @cookie should be sent when making a request to @host, or that
103  * @cookie should be accepted when receiving a response from @host.
104  *
105  * Return value: %TRUE if the domains match, %FALSE otherwise
106  *
107  * Since: 2.30
108  **/
109 gboolean
soup_cookie_domain_matches(SoupCookie * cookie,const char * host)110 soup_cookie_domain_matches (SoupCookie *cookie, const char *host)
111 {
112 	g_return_val_if_fail (cookie != NULL, FALSE);
113 	g_return_val_if_fail (host != NULL, FALSE);
114 
115 	return soup_host_matches_host (cookie->domain, host);
116 }
117 
118 static inline const char *
skip_lws(const char * s)119 skip_lws (const char *s)
120 {
121 	while (g_ascii_isspace (*s))
122 		s++;
123 	return s;
124 }
125 
126 static inline const char *
unskip_lws(const char * s,const char * start)127 unskip_lws (const char *s, const char *start)
128 {
129 	while (s > start && g_ascii_isspace (*(s - 1)))
130 		s--;
131 	return s;
132 }
133 
134 #define is_attr_ender(ch) ((ch) < ' ' || (ch) == ';' || (ch) == ',' || (ch) == '=')
135 #define is_value_ender(ch) ((ch) < ' ' || (ch) == ';')
136 
137 static char *
parse_value(const char ** val_p,gboolean copy)138 parse_value (const char **val_p, gboolean copy)
139 {
140 	const char *start, *end, *p;
141 	char *value;
142 
143 	p = *val_p;
144 	if (*p == '=')
145 		p++;
146 	start = skip_lws (p);
147 	for (p = start; !is_value_ender (*p); p++)
148 		;
149 	end = unskip_lws (p, start);
150 
151 	if (copy)
152 		value = g_strndup (start, end - start);
153 	else
154 		value = NULL;
155 
156 	*val_p = p;
157 	return value;
158 }
159 
160 static SoupDate *
parse_date(const char ** val_p)161 parse_date (const char **val_p)
162 {
163 	char *value;
164 	SoupDate *date;
165 
166 	value = parse_value (val_p, TRUE);
167 	date = soup_date_new_from_string (value);
168 	g_free (value);
169 	return date;
170 }
171 
172 static SoupCookie *
parse_one_cookie(const char * header,SoupURI * origin)173 parse_one_cookie (const char *header, SoupURI *origin)
174 {
175 	const char *start, *end, *p;
176 	gboolean has_value;
177 	SoupCookie *cookie;
178 
179 	g_return_val_if_fail (origin == NULL || origin->host, NULL);
180 
181 	cookie = g_slice_new0 (SoupCookie);
182 
183 	/* Parse the NAME */
184 	start = skip_lws (header);
185 	for (p = start; !is_attr_ender (*p); p++)
186 		;
187 	if (*p == '=') {
188 		end = unskip_lws (p, start);
189 		cookie->name = g_strndup (start, end - start);
190 	} else {
191 		/* No NAME; Set cookie->name to "" and then rewind to
192 		 * re-parse the string as a VALUE.
193 		 */
194 		cookie->name = g_strdup ("");
195 		p = start;
196 	}
197 
198 	/* Parse the VALUE */
199 	cookie->value = parse_value (&p, TRUE);
200 
201 	/* Parse attributes */
202 	while (*p == ';') {
203 		start = skip_lws (p + 1);
204 		for (p = start; !is_attr_ender (*p); p++)
205 			;
206 		end = unskip_lws (p, start);
207 
208 		has_value = (*p == '=');
209 #define MATCH_NAME(name) ((end - start == strlen (name)) && !g_ascii_strncasecmp (start, name, end - start))
210 
211 		if (MATCH_NAME ("domain") && has_value) {
212 			cookie->domain = parse_value (&p, TRUE);
213 			if (!*cookie->domain) {
214 				g_free (cookie->domain);
215 				cookie->domain = NULL;
216 			}
217 		} else if (MATCH_NAME ("expires") && has_value) {
218 			cookie->expires = parse_date (&p);
219 		} else if (MATCH_NAME ("httponly")) {
220 			cookie->http_only = TRUE;
221 			if (has_value)
222 				parse_value (&p, FALSE);
223 		} else if (MATCH_NAME ("max-age") && has_value) {
224 			char *max_age_str = parse_value (&p, TRUE), *mae;
225 			long max_age = strtol (max_age_str, &mae, 10);
226 			if (!*mae) {
227 				if (max_age < 0)
228 					max_age = 0;
229 				soup_cookie_set_max_age (cookie, max_age);
230 			}
231 			g_free (max_age_str);
232 		} else if (MATCH_NAME ("path") && has_value) {
233 			cookie->path = parse_value (&p, TRUE);
234 			if (*cookie->path != '/') {
235 				g_free (cookie->path);
236 				cookie->path = NULL;
237 			}
238 		} else if (MATCH_NAME ("secure")) {
239 			cookie->secure = TRUE;
240 			if (has_value)
241 				parse_value (&p, FALSE);
242 		} else if (MATCH_NAME ("samesite")) {
243 			if (has_value) {
244 				char *policy = parse_value (&p, TRUE);
245 				if (g_ascii_strcasecmp (policy, "Lax") == 0)
246 					soup_cookie_set_same_site_policy (cookie, SOUP_SAME_SITE_POLICY_LAX);
247 				else if (g_ascii_strcasecmp (policy, "Strict") == 0)
248 					soup_cookie_set_same_site_policy (cookie, SOUP_SAME_SITE_POLICY_STRICT);
249 				/* There is an explicit "None" value which is the default. */
250 				g_free (policy);
251 			}
252 			/* Note that earlier versions of the same-site RFC treated invalid values as strict but
253 			   the latest revision simply ignores them. */
254 		} else {
255 			/* Ignore unknown attributes, but we still have
256 			 * to skip over the value.
257 			 */
258 			if (has_value)
259 				parse_value (&p, FALSE);
260 		}
261 	}
262 
263 	if (cookie->domain) {
264 		/* Domain must have at least one '.' (not counting an
265 		 * initial one. (We check this now, rather than
266 		 * bailing out sooner, because we don't want to force
267 		 * any cookies after this one in the Set-Cookie header
268 		 * to be discarded.)
269 		 */
270 		if (!strchr (cookie->domain + 1, '.')) {
271 			soup_cookie_free (cookie);
272 			return NULL;
273 		}
274 
275 		/* If the domain string isn't an IP addr, and doesn't
276 		 * start with a '.', prepend one.
277 		 */
278 		if (!g_hostname_is_ip_address (cookie->domain) &&
279 		    cookie->domain[0] != '.') {
280 			char *tmp = g_strdup_printf (".%s", cookie->domain);
281 			g_free (cookie->domain);
282 			cookie->domain = tmp;
283 		}
284 	}
285 
286 	if (origin) {
287 		/* Sanity-check domain */
288 		if (cookie->domain) {
289 			if (!soup_cookie_domain_matches (cookie, origin->host)) {
290 				soup_cookie_free (cookie);
291 				return NULL;
292 			}
293 		} else
294 			cookie->domain = g_strdup (origin->host);
295 
296 		/* The original cookie spec didn't say that pages
297 		 * could only set cookies for paths they were under.
298 		 * RFC 2109 adds that requirement, but some sites
299 		 * depend on the old behavior
300 		 * (https://bugzilla.mozilla.org/show_bug.cgi?id=156725#c20).
301 		 * So we don't check the path.
302 		 */
303 
304 		if (!cookie->path) {
305 			char *slash;
306 
307 			slash = strrchr (origin->path, '/');
308 			if (!slash || slash == origin->path)
309 				cookie->path = g_strdup ("/");
310 			else {
311 				cookie->path = g_strndup (origin->path,
312 							  slash - origin->path);
313 			}
314 		}
315 	} else if (!cookie->path) {
316 		cookie->path = g_strdup ("/");
317 	}
318 
319 	return cookie;
320 }
321 
322 static SoupCookie *
cookie_new_internal(const char * name,const char * value,const char * domain,const char * path,int max_age)323 cookie_new_internal (const char *name, const char *value,
324 		     const char *domain, const char *path,
325 		     int max_age)
326 {
327 	SoupCookie *cookie;
328 
329 	cookie = g_slice_new0 (SoupCookie);
330 	cookie->name = g_strdup (name);
331 	cookie->value = g_strdup (value);
332 	cookie->domain = g_strdup (domain);
333 	cookie->path = g_strdup (path);
334 	soup_cookie_set_max_age (cookie, max_age);
335 
336 	return cookie;
337 }
338 
339 /**
340  * soup_cookie_new:
341  * @name: cookie name
342  * @value: cookie value
343  * @domain: cookie domain or hostname
344  * @path: cookie path, or %NULL
345  * @max_age: max age of the cookie, or -1 for a session cookie
346  *
347  * Creates a new #SoupCookie with the given attributes. (Use
348  * soup_cookie_set_secure() and soup_cookie_set_http_only() if you
349  * need to set those attributes on the returned cookie.)
350  *
351  * If @domain starts with ".", that indicates a domain (which matches
352  * the string after the ".", or any hostname that has @domain as a
353  * suffix). Otherwise, it is a hostname and must match exactly.
354  *
355  * @max_age is used to set the "expires" attribute on the cookie; pass
356  * -1 to not include the attribute (indicating that the cookie expires
357  * with the current session), 0 for an already-expired cookie, or a
358  * lifetime in seconds. You can use the constants
359  * %SOUP_COOKIE_MAX_AGE_ONE_HOUR, %SOUP_COOKIE_MAX_AGE_ONE_DAY,
360  * %SOUP_COOKIE_MAX_AGE_ONE_WEEK and %SOUP_COOKIE_MAX_AGE_ONE_YEAR (or
361  * multiples thereof) to calculate this value. (If you really care
362  * about setting the exact time that the cookie will expire, use
363  * soup_cookie_set_expires().)
364  *
365  * Return value: a new #SoupCookie.
366  *
367  * Since: 2.24
368  **/
369 SoupCookie *
soup_cookie_new(const char * name,const char * value,const char * domain,const char * path,int max_age)370 soup_cookie_new (const char *name, const char *value,
371 		 const char *domain, const char *path,
372 		 int max_age)
373 {
374 	g_return_val_if_fail (name != NULL, NULL);
375 	g_return_val_if_fail (value != NULL, NULL);
376 
377 	/* We ought to return if domain is NULL too, but this used to
378 	 * do be incorrectly documented as legal, and it wouldn't
379 	 * break anything as long as you called
380 	 * soup_cookie_set_domain() immediately after. So we warn but
381 	 * don't return, to discourage that behavior but not actually
382 	 * break anyone doing it.
383 	 */
384 	g_warn_if_fail (domain != NULL);
385 
386 	return cookie_new_internal (name, value, domain, path, max_age);
387 }
388 
389 /**
390  * soup_cookie_parse:
391  * @header: a cookie string (eg, the value of a Set-Cookie header)
392  * @origin: origin of the cookie, or %NULL
393  *
394  * Parses @header and returns a #SoupCookie. (If @header contains
395  * multiple cookies, only the first one will be parsed.)
396  *
397  * If @header does not have "path" or "domain" attributes, they will
398  * be defaulted from @origin. If @origin is %NULL, path will default
399  * to "/", but domain will be left as %NULL. Note that this is not a
400  * valid state for a #SoupCookie, and you will need to fill in some
401  * appropriate string for the domain if you want to actually make use
402  * of the cookie.
403  *
404  * Return value: (nullable): a new #SoupCookie, or %NULL if it could
405  * not be parsed, or contained an illegal "domain" attribute for a
406  * cookie originating from @origin.
407  *
408  * Since: 2.24
409  **/
410 SoupCookie *
soup_cookie_parse(const char * cookie,SoupURI * origin)411 soup_cookie_parse (const char *cookie, SoupURI *origin)
412 {
413 	return parse_one_cookie (cookie, origin);
414 }
415 
416 /**
417  * soup_cookie_get_name:
418  * @cookie: a #SoupCookie
419  *
420  * Gets @cookie's name
421  *
422  * Return value: @cookie's name
423  *
424  * Since: 2.32
425  **/
426 const char *
soup_cookie_get_name(SoupCookie * cookie)427 soup_cookie_get_name (SoupCookie *cookie)
428 {
429 	return cookie->name;
430 }
431 
432 /**
433  * soup_cookie_set_name:
434  * @cookie: a #SoupCookie
435  * @name: the new name
436  *
437  * Sets @cookie's name to @name
438  *
439  * Since: 2.24
440  **/
441 void
soup_cookie_set_name(SoupCookie * cookie,const char * name)442 soup_cookie_set_name (SoupCookie *cookie, const char *name)
443 {
444 	g_free (cookie->name);
445 	cookie->name = g_strdup (name);
446 }
447 
448 /**
449  * soup_cookie_get_value:
450  * @cookie: a #SoupCookie
451  *
452  * Gets @cookie's value
453  *
454  * Return value: @cookie's value
455  *
456  * Since: 2.32
457  **/
458 const char *
soup_cookie_get_value(SoupCookie * cookie)459 soup_cookie_get_value (SoupCookie *cookie)
460 {
461 	return cookie->value;
462 }
463 
464 /**
465  * soup_cookie_set_value:
466  * @cookie: a #SoupCookie
467  * @value: the new value
468  *
469  * Sets @cookie's value to @value
470  *
471  * Since: 2.24
472  **/
473 void
soup_cookie_set_value(SoupCookie * cookie,const char * value)474 soup_cookie_set_value (SoupCookie *cookie, const char *value)
475 {
476 	g_free (cookie->value);
477 	cookie->value = g_strdup (value);
478 }
479 
480 /**
481  * soup_cookie_get_domain:
482  * @cookie: a #SoupCookie
483  *
484  * Gets @cookie's domain
485  *
486  * Return value: @cookie's domain
487  *
488  * Since: 2.32
489  **/
490 const char *
soup_cookie_get_domain(SoupCookie * cookie)491 soup_cookie_get_domain (SoupCookie *cookie)
492 {
493 	return cookie->domain;
494 }
495 
496 /**
497  * soup_cookie_set_domain:
498  * @cookie: a #SoupCookie
499  * @domain: the new domain
500  *
501  * Sets @cookie's domain to @domain
502  *
503  * Since: 2.24
504  **/
505 void
soup_cookie_set_domain(SoupCookie * cookie,const char * domain)506 soup_cookie_set_domain (SoupCookie *cookie, const char *domain)
507 {
508 	g_free (cookie->domain);
509 	cookie->domain = g_strdup (domain);
510 }
511 
512 /**
513  * soup_cookie_get_path:
514  * @cookie: a #SoupCookie
515  *
516  * Gets @cookie's path
517  *
518  * Return value: @cookie's path
519  *
520  * Since: 2.32
521  **/
522 const char *
soup_cookie_get_path(SoupCookie * cookie)523 soup_cookie_get_path (SoupCookie *cookie)
524 {
525 	return cookie->path;
526 }
527 
528 /**
529  * soup_cookie_set_path:
530  * @cookie: a #SoupCookie
531  * @path: the new path
532  *
533  * Sets @cookie's path to @path
534  *
535  * Since: 2.24
536  **/
537 void
soup_cookie_set_path(SoupCookie * cookie,const char * path)538 soup_cookie_set_path (SoupCookie *cookie, const char *path)
539 {
540 	g_free (cookie->path);
541 	cookie->path = g_strdup (path);
542 }
543 
544 /**
545  * soup_cookie_set_max_age:
546  * @cookie: a #SoupCookie
547  * @max_age: the new max age
548  *
549  * Sets @cookie's max age to @max_age. If @max_age is -1, the cookie
550  * is a session cookie, and will expire at the end of the client's
551  * session. Otherwise, it is the number of seconds until the cookie
552  * expires. You can use the constants %SOUP_COOKIE_MAX_AGE_ONE_HOUR,
553  * %SOUP_COOKIE_MAX_AGE_ONE_DAY, %SOUP_COOKIE_MAX_AGE_ONE_WEEK and
554  * %SOUP_COOKIE_MAX_AGE_ONE_YEAR (or multiples thereof) to calculate
555  * this value. (A value of 0 indicates that the cookie should be
556  * considered already-expired.)
557  *
558  * (This sets the same property as soup_cookie_set_expires().)
559  *
560  * Since: 2.24
561  **/
562 void
soup_cookie_set_max_age(SoupCookie * cookie,int max_age)563 soup_cookie_set_max_age (SoupCookie *cookie, int max_age)
564 {
565 	if (cookie->expires)
566 		soup_date_free (cookie->expires);
567 
568 	if (max_age == -1)
569 		cookie->expires = NULL;
570 	else if (max_age == 0) {
571 		/* Use a date way in the past, to protect against
572 		 * clock skew.
573 		 */
574 		cookie->expires = soup_date_new (1970, 1, 1, 0, 0, 0);
575 	} else
576 		cookie->expires = soup_date_new_from_now (max_age);
577 }
578 
579 /**
580  * SOUP_COOKIE_MAX_AGE_ONE_HOUR:
581  *
582  * A constant corresponding to 1 hour, for use with soup_cookie_new()
583  * and soup_cookie_set_max_age().
584  *
585  * Since: 2.24
586  **/
587 /**
588  * SOUP_COOKIE_MAX_AGE_ONE_DAY:
589  *
590  * A constant corresponding to 1 day, for use with soup_cookie_new()
591  * and soup_cookie_set_max_age().
592  *
593  * Since: 2.24
594  **/
595 /**
596  * SOUP_COOKIE_MAX_AGE_ONE_WEEK:
597  *
598  * A constant corresponding to 1 week, for use with soup_cookie_new()
599  * and soup_cookie_set_max_age().
600  *
601  * Since: 2.24
602  **/
603 /**
604  * SOUP_COOKIE_MAX_AGE_ONE_YEAR:
605  *
606  * A constant corresponding to 1 year, for use with soup_cookie_new()
607  * and soup_cookie_set_max_age().
608  *
609  * Since: 2.24
610  **/
611 
612 /**
613  * soup_cookie_get_expires:
614  * @cookie: a #SoupCookie
615  *
616  * Gets @cookie's expiration time.
617  *
618  * Return value: (nullable) (transfer none): @cookie's expiration
619  * time, which is owned by @cookie and should not be modified or
620  * freed.
621  *
622  * Since: 2.32
623  **/
624 SoupDate *
soup_cookie_get_expires(SoupCookie * cookie)625 soup_cookie_get_expires (SoupCookie *cookie)
626 {
627 	return cookie->expires;
628 }
629 
630 /**
631  * soup_cookie_set_expires:
632  * @cookie: a #SoupCookie
633  * @expires: the new expiration time, or %NULL
634  *
635  * Sets @cookie's expiration time to @expires. If @expires is %NULL,
636  * @cookie will be a session cookie and will expire at the end of the
637  * client's session.
638  *
639  * (This sets the same property as soup_cookie_set_max_age().)
640  *
641  * Since: 2.24
642  **/
643 void
soup_cookie_set_expires(SoupCookie * cookie,SoupDate * expires)644 soup_cookie_set_expires (SoupCookie *cookie, SoupDate *expires)
645 {
646 	if (cookie->expires)
647 		soup_date_free (cookie->expires);
648 
649 	if (expires)
650 		cookie->expires = soup_date_copy (expires);
651 	else
652 		cookie->expires = NULL;
653 }
654 
655 /**
656  * soup_cookie_get_secure:
657  * @cookie: a #SoupCookie
658  *
659  * Gets @cookie's secure attribute
660  *
661  * Return value: @cookie's secure attribute
662  *
663  * Since: 2.32
664  **/
665 gboolean
soup_cookie_get_secure(SoupCookie * cookie)666 soup_cookie_get_secure (SoupCookie *cookie)
667 {
668 	return cookie->secure;
669 }
670 
671 /**
672  * soup_cookie_set_secure:
673  * @cookie: a #SoupCookie
674  * @secure: the new value for the secure attribute
675  *
676  * Sets @cookie's secure attribute to @secure. If %TRUE, @cookie will
677  * only be transmitted from the client to the server over secure
678  * (https) connections.
679  *
680  * Since: 2.24
681  **/
682 void
soup_cookie_set_secure(SoupCookie * cookie,gboolean secure)683 soup_cookie_set_secure (SoupCookie *cookie, gboolean secure)
684 {
685 	cookie->secure = secure;
686 }
687 
688 /**
689  * soup_cookie_get_http_only:
690  * @cookie: a #SoupCookie
691  *
692  * Gets @cookie's HttpOnly attribute
693  *
694  * Return value: @cookie's HttpOnly attribute
695  *
696  * Since: 2.32
697  **/
698 gboolean
soup_cookie_get_http_only(SoupCookie * cookie)699 soup_cookie_get_http_only (SoupCookie *cookie)
700 {
701 	return cookie->http_only;
702 }
703 
704 /**
705  * soup_cookie_set_http_only:
706  * @cookie: a #SoupCookie
707  * @http_only: the new value for the HttpOnly attribute
708  *
709  * Sets @cookie's HttpOnly attribute to @http_only. If %TRUE, @cookie
710  * will be marked as "http only", meaning it should not be exposed to
711  * web page scripts or other untrusted code.
712  *
713  * Since: 2.24
714  **/
715 void
soup_cookie_set_http_only(SoupCookie * cookie,gboolean http_only)716 soup_cookie_set_http_only (SoupCookie *cookie, gboolean http_only)
717 {
718 	cookie->http_only = http_only;
719 }
720 
721 static void
serialize_cookie(SoupCookie * cookie,GString * header,gboolean set_cookie)722 serialize_cookie (SoupCookie *cookie, GString *header, gboolean set_cookie)
723 {
724 	SoupSameSitePolicy same_site_policy;
725 
726 	if (!*cookie->name && !*cookie->value)
727 		return;
728 
729 	if (header->len) {
730 		if (set_cookie)
731 			g_string_append (header, ", ");
732 		else
733 			g_string_append (header, "; ");
734 	}
735 
736 	if (set_cookie || *cookie->name) {
737 		g_string_append (header, cookie->name);
738 		g_string_append (header, "=");
739 	}
740 	g_string_append (header, cookie->value);
741 	if (!set_cookie)
742 		return;
743 
744 	if (cookie->expires) {
745 		char *timestamp;
746 
747 		g_string_append (header, "; expires=");
748 		timestamp = soup_date_to_string (cookie->expires,
749 						 SOUP_DATE_COOKIE);
750 		g_string_append (header, timestamp);
751 		g_free (timestamp);
752 	}
753 	if (cookie->path) {
754 		g_string_append (header, "; path=");
755 		g_string_append (header, cookie->path);
756 	}
757 	if (cookie->domain) {
758 		g_string_append (header, "; domain=");
759 		g_string_append (header, cookie->domain);
760 	}
761 
762 	same_site_policy = soup_cookie_get_same_site_policy (cookie);
763 	if (same_site_policy != SOUP_SAME_SITE_POLICY_NONE) {
764 		g_string_append (header, "; SameSite=");
765 		if (same_site_policy == SOUP_SAME_SITE_POLICY_LAX)
766 			g_string_append (header, "Lax");
767 		else
768 			g_string_append (header, "Strict");
769 	}
770 	if (cookie->secure)
771 		g_string_append (header, "; secure");
772 	if (cookie->http_only)
773 		g_string_append (header, "; HttpOnly");
774 }
775 
776 static const char *same_site_policy_string = "soup-same-site-policy";
777 #define SAME_SITE_POLICY_QUARK (g_quark_from_static_string (same_site_policy_string))
778 
779 /**
780  * soup_cookie_set_same_site_policy:
781  * @cookie: a #SoupCookie
782  * @policy: a #SoupSameSitePolicy
783  *
784  * When used in conjunction with soup_cookie_jar_get_cookie_list_with_same_site_info() this
785  * sets the policy of when this cookie should be exposed.
786  *
787  * Since: 2.70
788  **/
789 void
soup_cookie_set_same_site_policy(SoupCookie * cookie,SoupSameSitePolicy policy)790 soup_cookie_set_same_site_policy (SoupCookie         *cookie,
791                                   SoupSameSitePolicy  policy)
792 {
793 	switch (policy) {
794 	case SOUP_SAME_SITE_POLICY_NONE:
795 	case SOUP_SAME_SITE_POLICY_STRICT:
796 	case SOUP_SAME_SITE_POLICY_LAX:
797 		g_dataset_id_set_data (cookie, SAME_SITE_POLICY_QUARK, GUINT_TO_POINTER (policy));
798 		break;
799 	default:
800 		g_return_if_reached ();
801 	}
802 }
803 
804 /**
805  * soup_cookie_get_same_site_policy:
806  * @cookie: a #SoupCookie
807  *
808  * Returns: a #SoupSameSitePolicy
809  *
810  * Since: 2.70
811  **/
812 SoupSameSitePolicy
soup_cookie_get_same_site_policy(SoupCookie * cookie)813 soup_cookie_get_same_site_policy (SoupCookie *cookie)
814 {
815 	return GPOINTER_TO_UINT (g_dataset_id_get_data (cookie, SAME_SITE_POLICY_QUARK));
816 }
817 
818 /**
819  * soup_cookie_to_set_cookie_header:
820  * @cookie: a #SoupCookie
821  *
822  * Serializes @cookie in the format used by the Set-Cookie header
823  * (ie, for sending a cookie from a #SoupServer to a client).
824  *
825  * Return value: the header
826  *
827  * Since: 2.24
828  **/
829 char *
soup_cookie_to_set_cookie_header(SoupCookie * cookie)830 soup_cookie_to_set_cookie_header (SoupCookie *cookie)
831 {
832 	GString *header = g_string_new (NULL);
833 
834 	serialize_cookie (cookie, header, TRUE);
835 	return g_string_free (header, FALSE);
836 }
837 
838 /**
839  * soup_cookie_to_cookie_header:
840  * @cookie: a #SoupCookie
841  *
842  * Serializes @cookie in the format used by the Cookie header (ie, for
843  * returning a cookie from a #SoupSession to a server).
844  *
845  * Return value: the header
846  *
847  * Since: 2.24
848  **/
849 char *
soup_cookie_to_cookie_header(SoupCookie * cookie)850 soup_cookie_to_cookie_header (SoupCookie *cookie)
851 {
852 	GString *header = g_string_new (NULL);
853 
854 	serialize_cookie (cookie, header, FALSE);
855 	return g_string_free (header, FALSE);
856 }
857 
858 /**
859  * soup_cookie_free:
860  * @cookie: a #SoupCookie
861  *
862  * Frees @cookie
863  *
864  * Since: 2.24
865  **/
866 void
soup_cookie_free(SoupCookie * cookie)867 soup_cookie_free (SoupCookie *cookie)
868 {
869 	g_return_if_fail (cookie != NULL);
870 
871 	g_free (cookie->name);
872 	g_free (cookie->value);
873 	g_free (cookie->domain);
874 	g_free (cookie->path);
875 	g_clear_pointer (&cookie->expires, soup_date_free);
876 
877 	g_dataset_destroy (cookie);
878 	g_slice_free (SoupCookie, cookie);
879 }
880 
881 /**
882  * soup_cookies_from_response:
883  * @msg: a #SoupMessage containing a "Set-Cookie" response header
884  *
885  * Parses @msg's Set-Cookie response headers and returns a #GSList of
886  * #SoupCookie<!-- -->s. Cookies that do not specify "path" or
887  * "domain" attributes will have their values defaulted from @msg.
888  *
889  * Return value: (element-type SoupCookie) (transfer full): a #GSList
890  * of #SoupCookie<!-- -->s, which can be freed with
891  * soup_cookies_free().
892  *
893  * Since: 2.24
894  **/
895 GSList *
soup_cookies_from_response(SoupMessage * msg)896 soup_cookies_from_response (SoupMessage *msg)
897 {
898 	SoupURI *origin;
899 	const char *name, *value;
900 	SoupCookie *cookie;
901 	GSList *cookies = NULL;
902 	SoupMessageHeadersIter iter;
903 
904 	origin = soup_message_get_uri (msg);
905 
906 	/* We have to use soup_message_headers_iter rather than
907 	 * soup_message_headers_get_list() since Set-Cookie isn't
908 	 * properly mergeable/unmergeable.
909 	 */
910 	soup_message_headers_iter_init (&iter, msg->response_headers);
911 	while (soup_message_headers_iter_next (&iter, &name, &value)) {
912 		if (g_ascii_strcasecmp (name, "Set-Cookie") != 0)
913 			continue;
914 
915 		cookie = parse_one_cookie (value, origin);
916 		if (cookie)
917 			cookies = g_slist_prepend (cookies, cookie);
918 	}
919 	return g_slist_reverse (cookies);
920 }
921 
922 /**
923  * soup_cookies_from_request:
924  * @msg: a #SoupMessage containing a "Cookie" request header
925  *
926  * Parses @msg's Cookie request header and returns a #GSList of
927  * #SoupCookie<!-- -->s. As the "Cookie" header, unlike "Set-Cookie",
928  * only contains cookie names and values, none of the other
929  * #SoupCookie fields will be filled in. (Thus, you can't generally
930  * pass a cookie returned from this method directly to
931  * soup_cookies_to_response().)
932  *
933  * Return value: (element-type SoupCookie) (transfer full): a #GSList
934  * of #SoupCookie<!-- -->s, which can be freed with
935  * soup_cookies_free().
936  *
937  * Since: 2.24
938  **/
939 GSList *
soup_cookies_from_request(SoupMessage * msg)940 soup_cookies_from_request (SoupMessage *msg)
941 {
942 	SoupCookie *cookie;
943 	GSList *cookies = NULL;
944 	GHashTable *params;
945 	GHashTableIter iter;
946 	gpointer name, value;
947 	const char *header;
948 
949 	header = soup_message_headers_get_one (msg->request_headers, "Cookie");
950 	if (!header)
951 		return NULL;
952 
953 	params = soup_header_parse_semi_param_list (header);
954 	g_hash_table_iter_init (&iter, params);
955 	while (g_hash_table_iter_next (&iter, &name, &value)) {
956 		if (name && value) {
957 			cookie = cookie_new_internal (name, value,
958 						      NULL, NULL, 0);
959 			cookies = g_slist_prepend (cookies, cookie);
960 		}
961 	}
962 	soup_header_free_param_list (params);
963 
964 	return g_slist_reverse (cookies);
965 }
966 
967 /**
968  * soup_cookies_to_response:
969  * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
970  * @msg: a #SoupMessage
971  *
972  * Appends a "Set-Cookie" response header to @msg for each cookie in
973  * @cookies. (This is in addition to any other "Set-Cookie" headers
974  * @msg may already have.)
975  *
976  * Since: 2.24
977  **/
978 void
soup_cookies_to_response(GSList * cookies,SoupMessage * msg)979 soup_cookies_to_response (GSList *cookies, SoupMessage *msg)
980 {
981 	GString *header;
982 
983 	header = g_string_new (NULL);
984 	while (cookies) {
985 		serialize_cookie (cookies->data, header, TRUE);
986 		soup_message_headers_append (msg->response_headers,
987 					     "Set-Cookie", header->str);
988 		g_string_truncate (header, 0);
989 		cookies = cookies->next;
990 	}
991 	g_string_free (header, TRUE);
992 }
993 
994 /**
995  * soup_cookies_to_request:
996  * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
997  * @msg: a #SoupMessage
998  *
999  * Adds the name and value of each cookie in @cookies to @msg's
1000  * "Cookie" request. (If @msg already has a "Cookie" request header,
1001  * these cookies will be appended to the cookies already present. Be
1002  * careful that you do not append the same cookies twice, eg, when
1003  * requeuing a message.)
1004  *
1005  * Since: 2.24
1006  **/
1007 void
soup_cookies_to_request(GSList * cookies,SoupMessage * msg)1008 soup_cookies_to_request (GSList *cookies, SoupMessage *msg)
1009 {
1010 	GString *header;
1011 
1012 	header = g_string_new (soup_message_headers_get_one (msg->request_headers,
1013 							     "Cookie"));
1014 	while (cookies) {
1015 		serialize_cookie (cookies->data, header, FALSE);
1016 		cookies = cookies->next;
1017 	}
1018 	soup_message_headers_replace (msg->request_headers,
1019 				      "Cookie", header->str);
1020 	g_string_free (header, TRUE);
1021 }
1022 
1023 /**
1024  * soup_cookies_free: (skip)
1025  * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
1026  *
1027  * Frees @cookies.
1028  *
1029  * Since: 2.24
1030  **/
1031 void
soup_cookies_free(GSList * cookies)1032 soup_cookies_free (GSList *cookies)
1033 {
1034 	g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
1035 }
1036 
1037 /**
1038  * soup_cookies_to_cookie_header:
1039  * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
1040  *
1041  * Serializes a #GSList of #SoupCookie into a string suitable for
1042  * setting as the value of the "Cookie" header.
1043  *
1044  * Return value: the serialization of @cookies
1045  *
1046  * Since: 2.24
1047  **/
1048 char *
soup_cookies_to_cookie_header(GSList * cookies)1049 soup_cookies_to_cookie_header (GSList *cookies)
1050 {
1051 	GString *str;
1052 
1053 	g_return_val_if_fail (cookies != NULL, NULL);
1054 
1055 	str = g_string_new (NULL);
1056 	while (cookies) {
1057 		serialize_cookie (cookies->data, str, FALSE);
1058 		cookies = cookies->next;
1059 	}
1060 
1061 	return g_string_free (str, FALSE);
1062 }
1063 
1064 /**
1065  * soup_cookie_applies_to_uri:
1066  * @cookie: a #SoupCookie
1067  * @uri: a #SoupURI
1068  *
1069  * Tests if @cookie should be sent to @uri.
1070  *
1071  * (At the moment, this does not check that @cookie's domain matches
1072  * @uri, because it assumes that the caller has already done that.
1073  * But don't rely on that; it may change in the future.)
1074  *
1075  * Return value: %TRUE if @cookie should be sent to @uri, %FALSE if
1076  * not
1077  *
1078  * Since: 2.24
1079  **/
1080 gboolean
soup_cookie_applies_to_uri(SoupCookie * cookie,SoupURI * uri)1081 soup_cookie_applies_to_uri (SoupCookie *cookie, SoupURI *uri)
1082 {
1083 	int plen;
1084 
1085 	if (cookie->secure && !soup_uri_is_https (uri, NULL))
1086 		return FALSE;
1087 
1088 	if (cookie->expires && soup_date_is_past (cookie->expires))
1089 		return FALSE;
1090 
1091 	/* uri->path is required to be non-NULL */
1092 	g_return_val_if_fail (uri->path != NULL, FALSE);
1093 
1094 	plen = strlen (cookie->path);
1095 	if (plen == 0)
1096 		return TRUE;
1097 	if (strncmp (cookie->path, uri->path, plen) != 0)
1098 		return FALSE;
1099 	if (cookie->path[plen - 1] != '/' &&
1100 	    uri->path[plen] && uri->path[plen] != '/')
1101 		return FALSE;
1102 
1103 	return TRUE;
1104 }
1105 
1106 /**
1107  * soup_cookie_equal:
1108  * @cookie1: a #SoupCookie
1109  * @cookie2: a #SoupCookie
1110  *
1111  * Tests if @cookie1 and @cookie2 are equal.
1112  *
1113  * Note that currently, this does not check that the cookie domains
1114  * match. This may change in the future.
1115  *
1116  * Return value: whether the cookies are equal.
1117  *
1118  * Since: 2.24
1119  */
1120 gboolean
soup_cookie_equal(SoupCookie * cookie1,SoupCookie * cookie2)1121 soup_cookie_equal (SoupCookie *cookie1, SoupCookie *cookie2)
1122 {
1123 	g_return_val_if_fail (cookie1, FALSE);
1124 	g_return_val_if_fail (cookie2, FALSE);
1125 
1126 	return (!strcmp (cookie1->name, cookie2->name) &&
1127 		!strcmp (cookie1->value, cookie2->value) &&
1128 		!strcmp (cookie1->path, cookie2->path));
1129 }
1130