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