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