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