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