1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
5 * Copyright (C) 2007 Red Hat, Inc.
15 #include "soup-cookie.h"
20 * @short_description: HTTP Cookies
21 * @see_also: #SoupMessage
23 * #SoupCookie implements HTTP cookies, primarily as described by
25 * url="http://wp.netscape.com/newsref/std/cookie_spec.html">the
26 * original Netscape cookie specification</ulink>, but with slight
27 * modifications based on <ulink
28 * url="http://www.ietf.org/rfc/rfc2109.txt">RFC 2109</ulink>, <ulink
29 * url="http://msdn2.microsoft.com/en-us/library/ms533046.aspx">Microsoft's
30 * HttpOnly extension attribute</ulink>, and observed real-world usage
31 * (and, in particular, based on what Firefox does).
33 * To have a #SoupSession handle cookies for your appliction
34 * automatically, use a #SoupCookieJar.
39 * @name: the cookie name
40 * @value: the cookie value
41 * @domain: the "domain" attribute, or else the hostname that the
43 * @path: the "path" attribute, or %NULL
44 * @expires: the cookie expiration time, or %NULL for a session cookie
45 * @secure: %TRUE if the cookie should only be tranferred over SSL
46 * @http_only: %TRUE if the cookie should not be exposed to scripts
50 * @name and @value will be set for all cookies. If the cookie is
51 * generated from a string that appears to have no name, then @name
52 * will be the empty string.
54 * @domain and @path give the host or domain, and path within that
55 * host/domain, to restrict this cookie to. If @domain starts with
56 * ".", that indicates a domain (which matches the string after the
57 * ".", or any hostname that has @domain as a suffix). Otherwise, it
58 * is a hostname and must match exactly.
60 * @expires will be non-%NULL if the cookie uses either the original
61 * "expires" attribute, or the "max-age" attribute specified in RFC
62 * 2109. If @expires is %NULL, it indicates that neither "expires" nor
63 * "max-age" was specified, and the cookie expires at the end of the
66 * If @http_only is set, the cookie should not be exposed to untrusted
67 * code (eg, javascript), so as to minimize the danger posed by
68 * cross-site scripting attacks.
73 G_DEFINE_BOXED_TYPE (SoupCookie, soup_cookie, soup_cookie_copy, soup_cookie_free)
77 * @cookie: a #SoupCookie
81 * Return value: a copy of @cookie
86 soup_cookie_copy (SoupCookie *cookie)
88 SoupCookie *copy = g_slice_new0 (SoupCookie);
90 copy->name = g_strdup (cookie->name);
91 copy->value = g_strdup (cookie->value);
92 copy->domain = g_strdup (cookie->domain);
93 copy->path = g_strdup (cookie->path);
95 copy->expires = soup_date_copy(cookie->expires);
96 copy->secure = cookie->secure;
97 copy->http_only = cookie->http_only;
103 * soup_cookie_domain_matches:
104 * @cookie: a #SoupCookie
107 * Checks if the @cookie's domain and @host match in the sense that
108 * @cookie should be sent when making a request to @host, or that
109 * @cookie should be accepted when receiving a response from @host.
111 * Return value: %TRUE if the domains match, %FALSE otherwise
116 soup_cookie_domain_matches (SoupCookie *cookie, const char *host)
122 g_return_val_if_fail (cookie != NULL, FALSE);
123 g_return_val_if_fail (host != NULL, FALSE);
125 domain = cookie->domain;
127 if (!g_ascii_strcasecmp (domain, host))
131 if (!g_ascii_strcasecmp (domain + 1, host))
133 dlen = strlen (domain);
134 while ((match = strstr (host, domain))) {
142 static inline const char *
143 skip_lws (const char *s)
145 while (g_ascii_isspace (*s))
150 static inline const char *
151 unskip_lws (const char *s, const char *start)
153 while (s > start && g_ascii_isspace (*(s - 1)))
158 #define is_attr_ender(ch) ((ch) < ' ' || (ch) == ';' || (ch) == ',' || (ch) == '=')
159 #define is_value_ender(ch) ((ch) < ' ' || (ch) == ';')
162 parse_value (const char **val_p, gboolean copy)
164 const char *start, *end, *p;
170 start = skip_lws (p);
171 for (p = start; !is_value_ender (*p); p++)
173 end = unskip_lws (p, start);
176 value = g_strndup (start, end - start);
185 parse_date (const char **val_p)
190 value = parse_value (val_p, TRUE);
191 date = soup_date_new_from_string (value);
197 parse_one_cookie (const char *header, SoupURI *origin)
199 const char *start, *end, *p;
203 g_return_val_if_fail (origin == NULL || origin->host, NULL);
205 cookie = g_slice_new0 (SoupCookie);
208 start = skip_lws (header);
209 for (p = start; !is_attr_ender (*p); p++)
212 end = unskip_lws (p, start);
213 cookie->name = g_strndup (start, end - start);
215 /* No NAME; Set cookie->name to "" and then rewind to
216 * re-parse the string as a VALUE.
218 cookie->name = g_strdup ("");
222 /* Parse the VALUE */
223 cookie->value = parse_value (&p, TRUE);
225 /* Parse attributes */
227 start = skip_lws (p + 1);
228 for (p = start; !is_attr_ender (*p); p++)
230 end = unskip_lws (p, start);
232 has_value = (*p == '=');
233 #define MATCH_NAME(name) ((end - start == strlen (name)) && !g_ascii_strncasecmp (start, name, end - start))
235 if (MATCH_NAME ("domain") && has_value) {
236 cookie->domain = parse_value (&p, TRUE);
237 if (!*cookie->domain) {
238 g_free (cookie->domain);
239 cookie->domain = NULL;
241 } else if (MATCH_NAME ("expires") && has_value) {
242 cookie->expires = parse_date (&p);
243 } else if (MATCH_NAME ("httponly")) {
244 cookie->http_only = TRUE;
246 parse_value (&p, FALSE);
247 } else if (MATCH_NAME ("max-age") && has_value) {
248 char *max_age_str = parse_value (&p, TRUE), *mae;
249 long max_age = strtol (max_age_str, &mae, 10);
253 soup_cookie_set_max_age (cookie, max_age);
255 g_free (max_age_str);
256 } else if (MATCH_NAME ("path") && has_value) {
257 cookie->path = parse_value (&p, TRUE);
258 if (*cookie->path != '/') {
259 g_free (cookie->path);
262 } else if (MATCH_NAME ("secure")) {
263 cookie->secure = TRUE;
265 parse_value (&p, FALSE);
267 /* Ignore unknown attributes, but we still have
268 * to skip over the value.
271 parse_value (&p, FALSE);
275 if (cookie->domain) {
276 /* Domain must have at least one '.' (not counting an
277 * initial one. (We check this now, rather than
278 * bailing out sooner, because we don't want to force
279 * any cookies after this one in the Set-Cookie header
282 if (!strchr (cookie->domain + 1, '.')) {
283 soup_cookie_free (cookie);
287 /* If the domain string isn't an IP addr, and doesn't
288 * start with a '.', prepend one.
290 if (!g_hostname_is_ip_address (cookie->domain) &&
291 cookie->domain[0] != '.') {
292 char *tmp = g_strdup_printf (".%s", cookie->domain);
293 g_free (cookie->domain);
294 cookie->domain = tmp;
299 /* Sanity-check domain */
300 if (cookie->domain) {
301 if (!soup_cookie_domain_matches (cookie, origin->host)) {
302 soup_cookie_free (cookie);
306 cookie->domain = g_strdup (origin->host);
308 /* The original cookie spec didn't say that pages
309 * could only set cookies for paths they were under.
310 * RFC 2109 adds that requirement, but some sites
311 * depend on the old behavior
312 * (https://bugzilla.mozilla.org/show_bug.cgi?id=156725#c20).
313 * So we don't check the path.
319 slash = strrchr (origin->path, '/');
320 if (!slash || slash == origin->path)
321 cookie->path = g_strdup ("/");
323 cookie->path = g_strndup (origin->path,
324 slash - origin->path);
333 cookie_new_internal (const char *name, const char *value,
334 const char *domain, const char *path,
339 cookie = g_slice_new0 (SoupCookie);
340 cookie->name = g_strdup (name);
341 cookie->value = g_strdup (value);
342 cookie->domain = g_strdup (domain);
343 cookie->path = g_strdup (path);
344 soup_cookie_set_max_age (cookie, max_age);
352 * @value: cookie value
353 * @domain: cookie domain or hostname
354 * @path: cookie path, or %NULL
355 * @max_age: max age of the cookie, or -1 for a session cookie
357 * Creates a new #SoupCookie with the given attributes. (Use
358 * soup_cookie_set_secure() and soup_cookie_set_http_only() if you
359 * need to set those attributes on the returned cookie.)
361 * @max_age is used to set the "expires" attribute on the cookie; pass
362 * -1 to not include the attribute (indicating that the cookie expires
363 * with the current session), 0 for an already-expired cookie, or a
364 * lifetime in seconds. You can use the constants
365 * %SOUP_COOKIE_MAX_AGE_ONE_HOUR, %SOUP_COOKIE_MAX_AGE_ONE_DAY,
366 * %SOUP_COOKIE_MAX_AGE_ONE_WEEK and %SOUP_COOKIE_MAX_AGE_ONE_YEAR (or
367 * multiples thereof) to calculate this value. (If you really care
368 * about setting the exact time that the cookie will expire, use
369 * soup_cookie_set_expires().)
371 * Return value: a new #SoupCookie.
376 soup_cookie_new (const char *name, const char *value,
377 const char *domain, const char *path,
380 g_return_val_if_fail (name != NULL, NULL);
381 g_return_val_if_fail (value != NULL, NULL);
383 /* We ought to return if domain is NULL too, but this used to
384 * do be incorrectly documented as legal, and it wouldn't
385 * break anything as long as you called
386 * soup_cookie_set_domain() immediately after. So we warn but
387 * don't return, to discourage that behavior but not actually
388 * break anyone doing it.
390 g_warn_if_fail (domain != NULL);
392 return cookie_new_internal (name, value, domain, path, max_age);
397 * @header: a cookie string (eg, the value of a Set-Cookie header)
398 * @origin: origin of the cookie, or %NULL
400 * Parses @header and returns a #SoupCookie. (If @header contains
401 * multiple cookies, only the first one will be parsed.)
403 * If @header does not have "path" or "domain" attributes, they will
404 * be defaulted from @origin. If @origin is %NULL, path will default
405 * to "/", but domain will be left as %NULL. Note that this is not a
406 * valid state for a #SoupCookie, and you will need to fill in some
407 * appropriate string for the domain if you want to actually make use
410 * Return value: a new #SoupCookie, or %NULL if it could not be
411 * parsed, or contained an illegal "domain" attribute for a cookie
412 * originating from @origin.
417 soup_cookie_parse (const char *cookie, SoupURI *origin)
419 return parse_one_cookie (cookie, origin);
423 * soup_cookie_get_name:
424 * @cookie: a #SoupCookie
426 * Gets @cookie's name
428 * Return value: @cookie's name
433 soup_cookie_get_name (SoupCookie *cookie)
439 * soup_cookie_set_name:
440 * @cookie: a #SoupCookie
441 * @name: the new name
443 * Sets @cookie's name to @name
448 soup_cookie_set_name (SoupCookie *cookie, const char *name)
450 g_free (cookie->name);
451 cookie->name = g_strdup (name);
455 * soup_cookie_get_value:
456 * @cookie: a #SoupCookie
458 * Gets @cookie's value
460 * Return value: @cookie's value
465 soup_cookie_get_value (SoupCookie *cookie)
467 return cookie->value;
471 * soup_cookie_set_value:
472 * @cookie: a #SoupCookie
473 * @value: the new value
475 * Sets @cookie's value to @value
480 soup_cookie_set_value (SoupCookie *cookie, const char *value)
482 g_free (cookie->value);
483 cookie->value = g_strdup (value);
487 * soup_cookie_get_domain:
488 * @cookie: a #SoupCookie
490 * Gets @cookie's domain
492 * Return value: @cookie's domain
497 soup_cookie_get_domain (SoupCookie *cookie)
499 return cookie->domain;
503 * soup_cookie_set_domain:
504 * @cookie: a #SoupCookie
505 * @domain: the new domain
507 * Sets @cookie's domain to @domain
512 soup_cookie_set_domain (SoupCookie *cookie, const char *domain)
514 g_free (cookie->domain);
515 cookie->domain = g_strdup (domain);
519 * soup_cookie_get_path:
520 * @cookie: a #SoupCookie
522 * Gets @cookie's path
524 * Return value: @cookie's path
529 soup_cookie_get_path (SoupCookie *cookie)
535 * soup_cookie_set_path:
536 * @cookie: a #SoupCookie
537 * @path: the new path
539 * Sets @cookie's path to @path
544 soup_cookie_set_path (SoupCookie *cookie, const char *path)
546 g_free (cookie->path);
547 cookie->path = g_strdup (path);
551 * soup_cookie_set_max_age:
552 * @cookie: a #SoupCookie
553 * @max_age: the new max age
555 * Sets @cookie's max age to @max_age. If @max_age is -1, the cookie
556 * is a session cookie, and will expire at the end of the client's
557 * session. Otherwise, it is the number of seconds until the cookie
558 * expires. You can use the constants %SOUP_COOKIE_MAX_AGE_ONE_HOUR,
559 * %SOUP_COOKIE_MAX_AGE_ONE_DAY, %SOUP_COOKIE_MAX_AGE_ONE_WEEK and
560 * %SOUP_COOKIE_MAX_AGE_ONE_YEAR (or multiples thereof) to calculate
561 * this value. (A value of 0 indicates that the cookie should be
562 * considered already-expired.)
564 * (This sets the same property as soup_cookie_set_expires().)
569 soup_cookie_set_max_age (SoupCookie *cookie, int max_age)
572 soup_date_free (cookie->expires);
575 cookie->expires = NULL;
576 else if (max_age == 0) {
577 /* Use a date way in the past, to protect against
580 cookie->expires = soup_date_new (1970, 1, 1, 0, 0, 0);
582 cookie->expires = soup_date_new_from_now (max_age);
586 * SOUP_COOKIE_MAX_AGE_ONE_HOUR:
588 * A constant corresponding to 1 hour, for use with soup_cookie_new()
589 * and soup_cookie_set_max_age().
594 * SOUP_COOKIE_MAX_AGE_ONE_DAY:
596 * A constant corresponding to 1 day, for use with soup_cookie_new()
597 * and soup_cookie_set_max_age().
602 * SOUP_COOKIE_MAX_AGE_ONE_WEEK:
604 * A constant corresponding to 1 week, for use with soup_cookie_new()
605 * and soup_cookie_set_max_age().
610 * SOUP_COOKIE_MAX_AGE_ONE_YEAR:
612 * A constant corresponding to 1 year, for use with soup_cookie_new()
613 * and soup_cookie_set_max_age().
619 * soup_cookie_get_expires:
620 * @cookie: a #SoupCookie
622 * Gets @cookie's expiration time
624 * Return value: (transfer none): @cookie's expiration time, which is
625 * owned by @cookie and should not be modified or freed.
630 soup_cookie_get_expires (SoupCookie *cookie)
632 return cookie->expires;
636 * soup_cookie_set_expires:
637 * @cookie: a #SoupCookie
638 * @expires: the new expiration time, or %NULL
640 * Sets @cookie's expiration time to @expires. If @expires is %NULL,
641 * @cookie will be a session cookie and will expire at the end of the
644 * (This sets the same property as soup_cookie_set_max_age().)
649 soup_cookie_set_expires (SoupCookie *cookie, SoupDate *expires)
652 soup_date_free (cookie->expires);
655 cookie->expires = soup_date_copy (expires);
657 cookie->expires = NULL;
661 * soup_cookie_get_secure:
662 * @cookie: a #SoupCookie
664 * Gets @cookie's secure attribute
666 * Return value: @cookie's secure attribute
671 soup_cookie_get_secure (SoupCookie *cookie)
673 return cookie->secure;
677 * soup_cookie_set_secure:
678 * @cookie: a #SoupCookie
679 * @secure: the new value for the secure attribute
681 * Sets @cookie's secure attribute to @secure. If %TRUE, @cookie will
682 * only be transmitted from the client to the server over secure
683 * (https) connections.
688 soup_cookie_set_secure (SoupCookie *cookie, gboolean secure)
690 cookie->secure = secure;
694 * soup_cookie_get_http_only:
695 * @cookie: a #SoupCookie
697 * Gets @cookie's HttpOnly attribute
699 * Return value: @cookie's HttpOnly attribute
704 soup_cookie_get_http_only (SoupCookie *cookie)
706 return cookie->http_only;
710 * soup_cookie_set_http_only:
711 * @cookie: a #SoupCookie
712 * @http_only: the new value for the HttpOnly attribute
714 * Sets @cookie's HttpOnly attribute to @http_only. If %TRUE, @cookie
715 * will be marked as "http only", meaning it should not be exposed to
716 * web page scripts or other untrusted code.
721 soup_cookie_set_http_only (SoupCookie *cookie, gboolean http_only)
723 cookie->http_only = http_only;
727 serialize_cookie (SoupCookie *cookie, GString *header, gboolean set_cookie)
729 if (!*cookie->name && !*cookie->value)
734 g_string_append (header, ", ");
736 g_string_append (header, "; ");
739 if (set_cookie || *cookie->name) {
740 g_string_append (header, cookie->name);
741 g_string_append (header, "=");
743 g_string_append (header, cookie->value);
747 if (cookie->expires) {
750 g_string_append (header, "; expires=");
751 timestamp = soup_date_to_string (cookie->expires,
753 g_string_append (header, timestamp);
757 g_string_append (header, "; path=");
758 g_string_append (header, cookie->path);
760 if (cookie->domain) {
761 g_string_append (header, "; domain=");
762 g_string_append (header, cookie->domain);
765 g_string_append (header, "; secure");
766 if (cookie->http_only)
767 g_string_append (header, "; HttpOnly");
771 * soup_cookie_to_set_cookie_header:
772 * @cookie: a #SoupCookie
774 * Serializes @cookie in the format used by the Set-Cookie header
775 * (ie, for sending a cookie from a #SoupServer to a client).
777 * Return value: the header
782 soup_cookie_to_set_cookie_header (SoupCookie *cookie)
784 GString *header = g_string_new (NULL);
786 serialize_cookie (cookie, header, TRUE);
787 return g_string_free (header, FALSE);
791 * soup_cookie_to_cookie_header:
792 * @cookie: a #SoupCookie
794 * Serializes @cookie in the format used by the Cookie header (ie, for
795 * returning a cookie from a #SoupSession to a server).
797 * Return value: the header
802 soup_cookie_to_cookie_header (SoupCookie *cookie)
804 GString *header = g_string_new (NULL);
806 serialize_cookie (cookie, header, FALSE);
807 return g_string_free (header, FALSE);
812 * @cookie: a #SoupCookie
819 soup_cookie_free (SoupCookie *cookie)
821 g_return_if_fail (cookie != NULL);
823 g_free (cookie->name);
824 g_free (cookie->value);
825 g_free (cookie->domain);
826 g_free (cookie->path);
827 g_clear_pointer (&cookie->expires, soup_date_free);
829 g_slice_free (SoupCookie, cookie);
833 * soup_cookies_from_response:
834 * @msg: a #SoupMessage containing a "Set-Cookie" response header
836 * Parses @msg's Set-Cookie response headers and returns a #GSList of
837 * #SoupCookie<!-- -->s. Cookies that do not specify "path" or
838 * "domain" attributes will have their values defaulted from @msg.
840 * Return value: (element-type SoupCookie) (transfer full): a #GSList
841 * of #SoupCookie<!-- -->s, which can be freed with
842 * soup_cookies_free().
847 soup_cookies_from_response (SoupMessage *msg)
850 const char *name, *value;
852 GSList *cookies = NULL;
853 SoupMessageHeadersIter iter;
855 origin = soup_message_get_uri (msg);
857 /* We have to use soup_message_headers_iter rather than
858 * soup_message_headers_get_list() since Set-Cookie isn't
859 * properly mergeable/unmergeable.
861 soup_message_headers_iter_init (&iter, msg->response_headers);
862 while (soup_message_headers_iter_next (&iter, &name, &value)) {
863 if (g_ascii_strcasecmp (name, "Set-Cookie") != 0)
866 cookie = parse_one_cookie (value, origin);
868 cookies = g_slist_prepend (cookies, cookie);
870 return g_slist_reverse (cookies);
874 * soup_cookies_from_request:
875 * @msg: a #SoupMessage containing a "Cookie" request header
877 * Parses @msg's Cookie request header and returns a #GSList of
878 * #SoupCookie<!-- -->s. As the "Cookie" header, unlike "Set-Cookie",
879 * only contains cookie names and values, none of the other
880 * #SoupCookie fields will be filled in. (Thus, you can't generally
881 * pass a cookie returned from this method directly to
882 * soup_cookies_to_response().)
884 * Return value: (element-type SoupCookie) (transfer full): a #GSList
885 * of #SoupCookie<!-- -->s, which can be freed with
886 * soup_cookies_free().
891 soup_cookies_from_request (SoupMessage *msg)
894 GSList *cookies = NULL;
897 gpointer name, value;
900 header = soup_message_headers_get_one (msg->request_headers, "Cookie");
904 params = soup_header_parse_semi_param_list (header);
905 g_hash_table_iter_init (&iter, params);
906 while (g_hash_table_iter_next (&iter, &name, &value)) {
908 cookie = cookie_new_internal (name, value,
910 cookies = g_slist_prepend (cookies, cookie);
913 soup_header_free_param_list (params);
915 return g_slist_reverse (cookies);
919 * soup_cookies_to_response:
920 * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
921 * @msg: a #SoupMessage
923 * Appends a "Set-Cookie" response header to @msg for each cookie in
924 * @cookies. (This is in addition to any other "Set-Cookie" headers
925 * @msg may already have.)
930 soup_cookies_to_response (GSList *cookies, SoupMessage *msg)
934 header = g_string_new (NULL);
936 serialize_cookie (cookies->data, header, TRUE);
937 soup_message_headers_append (msg->response_headers,
938 "Set-Cookie", header->str);
939 g_string_truncate (header, 0);
940 cookies = cookies->next;
942 g_string_free (header, TRUE);
946 * soup_cookies_to_request:
947 * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
948 * @msg: a #SoupMessage
950 * Adds the name and value of each cookie in @cookies to @msg's
951 * "Cookie" request. (If @msg already has a "Cookie" request header,
952 * these cookies will be appended to the cookies already present. Be
953 * careful that you do not append the same cookies twice, eg, when
954 * requeuing a message.)
959 soup_cookies_to_request (GSList *cookies, SoupMessage *msg)
963 header = g_string_new (soup_message_headers_get_one (msg->request_headers,
966 serialize_cookie (cookies->data, header, FALSE);
967 cookies = cookies->next;
969 soup_message_headers_replace (msg->request_headers,
970 "Cookie", header->str);
971 g_string_free (header, TRUE);
975 * soup_cookies_free: (skip)
976 * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
983 soup_cookies_free (GSList *cookies)
985 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
989 * soup_cookies_to_cookie_header:
990 * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
992 * Serializes a #GSList of #SoupCookie into a string suitable for
993 * setting as the value of the "Cookie" header.
995 * Return value: the serialization of @cookies
1000 soup_cookies_to_cookie_header (GSList *cookies)
1004 g_return_val_if_fail (cookies != NULL, NULL);
1006 str = g_string_new (NULL);
1008 serialize_cookie (cookies->data, str, FALSE);
1009 cookies = cookies->next;
1012 return g_string_free (str, FALSE);
1016 * soup_cookie_applies_to_uri:
1017 * @cookie: a #SoupCookie
1020 * Tests if @cookie should be sent to @uri.
1022 * (At the moment, this does not check that @cookie's domain matches
1023 * @uri, because it assumes that the caller has already done that.
1024 * But don't rely on that; it may change in the future.)
1026 * Return value: %TRUE if @cookie should be sent to @uri, %FALSE if
1032 soup_cookie_applies_to_uri (SoupCookie *cookie, SoupURI *uri)
1036 if (cookie->secure && uri->scheme != SOUP_URI_SCHEME_HTTPS)
1039 if (cookie->expires && soup_date_is_past (cookie->expires))
1042 /* uri->path is required to be non-NULL */
1043 g_return_val_if_fail (uri->path != NULL, FALSE);
1045 plen = strlen (cookie->path);
1048 if (strncmp (cookie->path, uri->path, plen) != 0)
1050 if (cookie->path[plen - 1] != '/' &&
1051 uri->path[plen] && uri->path[plen] != '/')
1058 * soup_cookie_equal:
1059 * @cookie1: a #SoupCookie
1060 * @cookie2: a #SoupCookie
1062 * Tests if @cookie1 and @cookie2 are equal.
1064 * Note that currently, this does not check that the cookie domains
1065 * match. This may change in the future.
1067 * Return value: whether the cookies are equal.
1072 soup_cookie_equal (SoupCookie *cookie1, SoupCookie *cookie2)
1074 g_return_val_if_fail (cookie1, FALSE);
1075 g_return_val_if_fail (cookie2, FALSE);
1077 return (!strcmp (cookie1->name, cookie2->name) &&
1078 !strcmp (cookie1->value, cookie2->value) &&
1079 !strcmp (cookie1->path, cookie2->path));