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