summaryrefslogtreecommitdiff
path: root/libsoup/cookies/soup-cookie.c
diff options
context:
space:
mode:
Diffstat (limited to 'libsoup/cookies/soup-cookie.c')
-rw-r--r--libsoup/cookies/soup-cookie.c1129
1 files changed, 1129 insertions, 0 deletions
diff --git a/libsoup/cookies/soup-cookie.c b/libsoup/cookies/soup-cookie.c
new file mode 100644
index 00000000..cf538831
--- /dev/null
+++ b/libsoup/cookies/soup-cookie.c
@@ -0,0 +1,1129 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-cookie.c
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "soup-cookie.h"
+#include "soup-misc-private.h"
+#include "soup.h"
+
+/**
+ * SECTION:soup-cookie
+ * @short_description: HTTP Cookies
+ * @see_also: #SoupMessage, #SoupCookieJar
+ *
+ * #SoupCookie implements HTTP cookies, as described by <ulink
+ * url="http://tools.ietf.org/html/rfc6265.txt">RFC 6265</ulink>.
+ *
+ * To have a #SoupSession handle cookies for your appliction
+ * automatically, use a #SoupCookieJar.
+ **/
+
+/**
+ * SoupCookie:
+ * @name: the cookie name
+ * @value: the cookie value
+ * @domain: the "domain" attribute, or else the hostname that the
+ * cookie came from.
+ * @path: the "path" attribute, or %NULL
+ * @expires: the cookie expiration time, or %NULL for a session cookie
+ * @secure: %TRUE if the cookie should only be tranferred over SSL
+ * @http_only: %TRUE if the cookie should not be exposed to scripts
+ *
+ * An HTTP cookie.
+ *
+ * @name and @value will be set for all cookies. If the cookie is
+ * generated from a string that appears to have no name, then @name
+ * will be the empty string.
+ *
+ * @domain and @path give the host or domain, and path within that
+ * host/domain, to restrict this cookie to. If @domain starts with
+ * ".", that indicates a domain (which matches the string after the
+ * ".", or any hostname that has @domain as a suffix). Otherwise, it
+ * is a hostname and must match exactly.
+ *
+ * @expires will be non-%NULL if the cookie uses either the original
+ * "expires" attribute, or the newer "max-age" attribute. If @expires
+ * is %NULL, it indicates that neither "expires" nor "max-age" was
+ * specified, and the cookie expires at the end of the session.
+ *
+ * If @http_only is set, the cookie should not be exposed to untrusted
+ * code (eg, javascript), so as to minimize the danger posed by
+ * cross-site scripting attacks.
+ *
+ * Since: 2.24
+ **/
+
+G_DEFINE_BOXED_TYPE (SoupCookie, soup_cookie, soup_cookie_copy, soup_cookie_free)
+
+/**
+ * soup_cookie_copy:
+ * @cookie: a #SoupCookie
+ *
+ * Copies @cookie.
+ *
+ * Return value: a copy of @cookie
+ *
+ * Since: 2.24
+ **/
+SoupCookie *
+soup_cookie_copy (SoupCookie *cookie)
+{
+ SoupCookie *copy = g_slice_new0 (SoupCookie);
+
+ copy->name = g_strdup (cookie->name);
+ copy->value = g_strdup (cookie->value);
+ copy->domain = g_strdup (cookie->domain);
+ copy->path = g_strdup (cookie->path);
+ if (cookie->expires)
+ copy->expires = soup_date_copy(cookie->expires);
+ copy->secure = cookie->secure;
+ copy->http_only = cookie->http_only;
+ soup_cookie_set_same_site_policy (copy, soup_cookie_get_same_site_policy (cookie));
+
+ return copy;
+}
+
+/**
+ * soup_cookie_domain_matches:
+ * @cookie: a #SoupCookie
+ * @host: a URI
+ *
+ * Checks if the @cookie's domain and @host match in the sense that
+ * @cookie should be sent when making a request to @host, or that
+ * @cookie should be accepted when receiving a response from @host.
+ *
+ * Return value: %TRUE if the domains match, %FALSE otherwise
+ *
+ * Since: 2.30
+ **/
+gboolean
+soup_cookie_domain_matches (SoupCookie *cookie, const char *host)
+{
+ g_return_val_if_fail (cookie != NULL, FALSE);
+ g_return_val_if_fail (host != NULL, FALSE);
+
+ return soup_host_matches_host (cookie->domain, host);
+}
+
+static inline const char *
+skip_lws (const char *s)
+{
+ while (g_ascii_isspace (*s))
+ s++;
+ return s;
+}
+
+static inline const char *
+unskip_lws (const char *s, const char *start)
+{
+ while (s > start && g_ascii_isspace (*(s - 1)))
+ s--;
+ return s;
+}
+
+#define is_attr_ender(ch) ((ch) < ' ' || (ch) == ';' || (ch) == ',' || (ch) == '=')
+#define is_value_ender(ch) ((ch) < ' ' || (ch) == ';')
+
+static char *
+parse_value (const char **val_p, gboolean copy)
+{
+ const char *start, *end, *p;
+ char *value;
+
+ p = *val_p;
+ if (*p == '=')
+ p++;
+ start = skip_lws (p);
+ for (p = start; !is_value_ender (*p); p++)
+ ;
+ end = unskip_lws (p, start);
+
+ if (copy)
+ value = g_strndup (start, end - start);
+ else
+ value = NULL;
+
+ *val_p = p;
+ return value;
+}
+
+static SoupDate *
+parse_date (const char **val_p)
+{
+ char *value;
+ SoupDate *date;
+
+ value = parse_value (val_p, TRUE);
+ date = soup_date_new_from_string (value);
+ g_free (value);
+ return date;
+}
+
+static SoupCookie *
+parse_one_cookie (const char *header, SoupURI *origin)
+{
+ const char *start, *end, *p;
+ gboolean has_value;
+ SoupCookie *cookie;
+
+ g_return_val_if_fail (origin == NULL || origin->host, NULL);
+
+ cookie = g_slice_new0 (SoupCookie);
+
+ /* Parse the NAME */
+ start = skip_lws (header);
+ for (p = start; !is_attr_ender (*p); p++)
+ ;
+ if (*p == '=') {
+ end = unskip_lws (p, start);
+ cookie->name = g_strndup (start, end - start);
+ } else {
+ /* No NAME; Set cookie->name to "" and then rewind to
+ * re-parse the string as a VALUE.
+ */
+ cookie->name = g_strdup ("");
+ p = start;
+ }
+
+ /* Parse the VALUE */
+ cookie->value = parse_value (&p, TRUE);
+
+ /* Parse attributes */
+ while (*p == ';') {
+ start = skip_lws (p + 1);
+ for (p = start; !is_attr_ender (*p); p++)
+ ;
+ end = unskip_lws (p, start);
+
+ has_value = (*p == '=');
+#define MATCH_NAME(name) ((end - start == strlen (name)) && !g_ascii_strncasecmp (start, name, end - start))
+
+ if (MATCH_NAME ("domain") && has_value) {
+ cookie->domain = parse_value (&p, TRUE);
+ if (!*cookie->domain) {
+ g_free (cookie->domain);
+ cookie->domain = NULL;
+ }
+ } else if (MATCH_NAME ("expires") && has_value) {
+ cookie->expires = parse_date (&p);
+ } else if (MATCH_NAME ("httponly")) {
+ cookie->http_only = TRUE;
+ if (has_value)
+ parse_value (&p, FALSE);
+ } else if (MATCH_NAME ("max-age") && has_value) {
+ char *max_age_str = parse_value (&p, TRUE), *mae;
+ long max_age = strtol (max_age_str, &mae, 10);
+ if (!*mae) {
+ if (max_age < 0)
+ max_age = 0;
+ soup_cookie_set_max_age (cookie, max_age);
+ }
+ g_free (max_age_str);
+ } else if (MATCH_NAME ("path") && has_value) {
+ cookie->path = parse_value (&p, TRUE);
+ if (*cookie->path != '/') {
+ g_free (cookie->path);
+ cookie->path = NULL;
+ }
+ } else if (MATCH_NAME ("secure")) {
+ cookie->secure = TRUE;
+ if (has_value)
+ parse_value (&p, FALSE);
+ } else if (MATCH_NAME ("samesite")) {
+ if (has_value) {
+ char *policy = parse_value (&p, TRUE);
+ if (g_ascii_strcasecmp (policy, "Lax") == 0)
+ soup_cookie_set_same_site_policy (cookie, SOUP_SAME_SITE_POLICY_LAX);
+ else if (g_ascii_strcasecmp (policy, "Strict") == 0)
+ soup_cookie_set_same_site_policy (cookie, SOUP_SAME_SITE_POLICY_STRICT);
+ /* There is an explicit "None" value which is the default. */
+ g_free (policy);
+ }
+ /* Note that earlier versions of the same-site RFC treated invalid values as strict but
+ the latest revision simply ignores them. */
+ } else {
+ /* Ignore unknown attributes, but we still have
+ * to skip over the value.
+ */
+ if (has_value)
+ parse_value (&p, FALSE);
+ }
+ }
+
+ if (cookie->domain) {
+ /* Domain must have at least one '.' (not counting an
+ * initial one. (We check this now, rather than
+ * bailing out sooner, because we don't want to force
+ * any cookies after this one in the Set-Cookie header
+ * to be discarded.)
+ */
+ if (!strchr (cookie->domain + 1, '.')) {
+ soup_cookie_free (cookie);
+ return NULL;
+ }
+
+ /* If the domain string isn't an IP addr, and doesn't
+ * start with a '.', prepend one.
+ */
+ if (!g_hostname_is_ip_address (cookie->domain) &&
+ cookie->domain[0] != '.') {
+ char *tmp = g_strdup_printf (".%s", cookie->domain);
+ g_free (cookie->domain);
+ cookie->domain = tmp;
+ }
+ }
+
+ if (origin) {
+ /* Sanity-check domain */
+ if (cookie->domain) {
+ if (!soup_cookie_domain_matches (cookie, origin->host)) {
+ soup_cookie_free (cookie);
+ return NULL;
+ }
+ } else
+ cookie->domain = g_strdup (origin->host);
+
+ /* The original cookie spec didn't say that pages
+ * could only set cookies for paths they were under.
+ * RFC 2109 adds that requirement, but some sites
+ * depend on the old behavior
+ * (https://bugzilla.mozilla.org/show_bug.cgi?id=156725#c20).
+ * So we don't check the path.
+ */
+
+ if (!cookie->path) {
+ char *slash;
+
+ slash = strrchr (origin->path, '/');
+ if (!slash || slash == origin->path)
+ cookie->path = g_strdup ("/");
+ else {
+ cookie->path = g_strndup (origin->path,
+ slash - origin->path);
+ }
+ }
+ } else if (!cookie->path) {
+ cookie->path = g_strdup ("/");
+ }
+
+ return cookie;
+}
+
+static SoupCookie *
+cookie_new_internal (const char *name, const char *value,
+ const char *domain, const char *path,
+ int max_age)
+{
+ SoupCookie *cookie;
+
+ cookie = g_slice_new0 (SoupCookie);
+ cookie->name = g_strdup (name);
+ cookie->value = g_strdup (value);
+ cookie->domain = g_strdup (domain);
+ cookie->path = g_strdup (path);
+ soup_cookie_set_max_age (cookie, max_age);
+
+ return cookie;
+}
+
+/**
+ * soup_cookie_new:
+ * @name: cookie name
+ * @value: cookie value
+ * @domain: cookie domain or hostname
+ * @path: cookie path, or %NULL
+ * @max_age: max age of the cookie, or -1 for a session cookie
+ *
+ * Creates a new #SoupCookie with the given attributes. (Use
+ * soup_cookie_set_secure() and soup_cookie_set_http_only() if you
+ * need to set those attributes on the returned cookie.)
+ *
+ * If @domain starts with ".", that indicates a domain (which matches
+ * the string after the ".", or any hostname that has @domain as a
+ * suffix). Otherwise, it is a hostname and must match exactly.
+ *
+ * @max_age is used to set the "expires" attribute on the cookie; pass
+ * -1 to not include the attribute (indicating that the cookie expires
+ * with the current session), 0 for an already-expired cookie, or a
+ * lifetime in seconds. You can use the constants
+ * %SOUP_COOKIE_MAX_AGE_ONE_HOUR, %SOUP_COOKIE_MAX_AGE_ONE_DAY,
+ * %SOUP_COOKIE_MAX_AGE_ONE_WEEK and %SOUP_COOKIE_MAX_AGE_ONE_YEAR (or
+ * multiples thereof) to calculate this value. (If you really care
+ * about setting the exact time that the cookie will expire, use
+ * soup_cookie_set_expires().)
+ *
+ * Return value: a new #SoupCookie.
+ *
+ * Since: 2.24
+ **/
+SoupCookie *
+soup_cookie_new (const char *name, const char *value,
+ const char *domain, const char *path,
+ int max_age)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (value != NULL, NULL);
+
+ /* We ought to return if domain is NULL too, but this used to
+ * do be incorrectly documented as legal, and it wouldn't
+ * break anything as long as you called
+ * soup_cookie_set_domain() immediately after. So we warn but
+ * don't return, to discourage that behavior but not actually
+ * break anyone doing it.
+ */
+ g_warn_if_fail (domain != NULL);
+
+ return cookie_new_internal (name, value, domain, path, max_age);
+}
+
+/**
+ * soup_cookie_parse:
+ * @header: a cookie string (eg, the value of a Set-Cookie header)
+ * @origin: origin of the cookie, or %NULL
+ *
+ * Parses @header and returns a #SoupCookie. (If @header contains
+ * multiple cookies, only the first one will be parsed.)
+ *
+ * If @header does not have "path" or "domain" attributes, they will
+ * be defaulted from @origin. If @origin is %NULL, path will default
+ * to "/", but domain will be left as %NULL. Note that this is not a
+ * valid state for a #SoupCookie, and you will need to fill in some
+ * appropriate string for the domain if you want to actually make use
+ * of the cookie.
+ *
+ * Return value: (nullable): a new #SoupCookie, or %NULL if it could
+ * not be parsed, or contained an illegal "domain" attribute for a
+ * cookie originating from @origin.
+ *
+ * Since: 2.24
+ **/
+SoupCookie *
+soup_cookie_parse (const char *cookie, SoupURI *origin)
+{
+ return parse_one_cookie (cookie, origin);
+}
+
+/**
+ * soup_cookie_get_name:
+ * @cookie: a #SoupCookie
+ *
+ * Gets @cookie's name
+ *
+ * Return value: @cookie's name
+ *
+ * Since: 2.32
+ **/
+const char *
+soup_cookie_get_name (SoupCookie *cookie)
+{
+ return cookie->name;
+}
+
+/**
+ * soup_cookie_set_name:
+ * @cookie: a #SoupCookie
+ * @name: the new name
+ *
+ * Sets @cookie's name to @name
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookie_set_name (SoupCookie *cookie, const char *name)
+{
+ g_free (cookie->name);
+ cookie->name = g_strdup (name);
+}
+
+/**
+ * soup_cookie_get_value:
+ * @cookie: a #SoupCookie
+ *
+ * Gets @cookie's value
+ *
+ * Return value: @cookie's value
+ *
+ * Since: 2.32
+ **/
+const char *
+soup_cookie_get_value (SoupCookie *cookie)
+{
+ return cookie->value;
+}
+
+/**
+ * soup_cookie_set_value:
+ * @cookie: a #SoupCookie
+ * @value: the new value
+ *
+ * Sets @cookie's value to @value
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookie_set_value (SoupCookie *cookie, const char *value)
+{
+ g_free (cookie->value);
+ cookie->value = g_strdup (value);
+}
+
+/**
+ * soup_cookie_get_domain:
+ * @cookie: a #SoupCookie
+ *
+ * Gets @cookie's domain
+ *
+ * Return value: @cookie's domain
+ *
+ * Since: 2.32
+ **/
+const char *
+soup_cookie_get_domain (SoupCookie *cookie)
+{
+ return cookie->domain;
+}
+
+/**
+ * soup_cookie_set_domain:
+ * @cookie: a #SoupCookie
+ * @domain: the new domain
+ *
+ * Sets @cookie's domain to @domain
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookie_set_domain (SoupCookie *cookie, const char *domain)
+{
+ g_free (cookie->domain);
+ cookie->domain = g_strdup (domain);
+}
+
+/**
+ * soup_cookie_get_path:
+ * @cookie: a #SoupCookie
+ *
+ * Gets @cookie's path
+ *
+ * Return value: @cookie's path
+ *
+ * Since: 2.32
+ **/
+const char *
+soup_cookie_get_path (SoupCookie *cookie)
+{
+ return cookie->path;
+}
+
+/**
+ * soup_cookie_set_path:
+ * @cookie: a #SoupCookie
+ * @path: the new path
+ *
+ * Sets @cookie's path to @path
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookie_set_path (SoupCookie *cookie, const char *path)
+{
+ g_free (cookie->path);
+ cookie->path = g_strdup (path);
+}
+
+/**
+ * soup_cookie_set_max_age:
+ * @cookie: a #SoupCookie
+ * @max_age: the new max age
+ *
+ * Sets @cookie's max age to @max_age. If @max_age is -1, the cookie
+ * is a session cookie, and will expire at the end of the client's
+ * session. Otherwise, it is the number of seconds until the cookie
+ * expires. You can use the constants %SOUP_COOKIE_MAX_AGE_ONE_HOUR,
+ * %SOUP_COOKIE_MAX_AGE_ONE_DAY, %SOUP_COOKIE_MAX_AGE_ONE_WEEK and
+ * %SOUP_COOKIE_MAX_AGE_ONE_YEAR (or multiples thereof) to calculate
+ * this value. (A value of 0 indicates that the cookie should be
+ * considered already-expired.)
+ *
+ * (This sets the same property as soup_cookie_set_expires().)
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookie_set_max_age (SoupCookie *cookie, int max_age)
+{
+ if (cookie->expires)
+ soup_date_free (cookie->expires);
+
+ if (max_age == -1)
+ cookie->expires = NULL;
+ else if (max_age == 0) {
+ /* Use a date way in the past, to protect against
+ * clock skew.
+ */
+ cookie->expires = soup_date_new (1970, 1, 1, 0, 0, 0);
+ } else
+ cookie->expires = soup_date_new_from_now (max_age);
+}
+
+/**
+ * SOUP_COOKIE_MAX_AGE_ONE_HOUR:
+ *
+ * A constant corresponding to 1 hour, for use with soup_cookie_new()
+ * and soup_cookie_set_max_age().
+ *
+ * Since: 2.24
+ **/
+/**
+ * SOUP_COOKIE_MAX_AGE_ONE_DAY:
+ *
+ * A constant corresponding to 1 day, for use with soup_cookie_new()
+ * and soup_cookie_set_max_age().
+ *
+ * Since: 2.24
+ **/
+/**
+ * SOUP_COOKIE_MAX_AGE_ONE_WEEK:
+ *
+ * A constant corresponding to 1 week, for use with soup_cookie_new()
+ * and soup_cookie_set_max_age().
+ *
+ * Since: 2.24
+ **/
+/**
+ * SOUP_COOKIE_MAX_AGE_ONE_YEAR:
+ *
+ * A constant corresponding to 1 year, for use with soup_cookie_new()
+ * and soup_cookie_set_max_age().
+ *
+ * Since: 2.24
+ **/
+
+/**
+ * soup_cookie_get_expires:
+ * @cookie: a #SoupCookie
+ *
+ * Gets @cookie's expiration time.
+ *
+ * Return value: (nullable) (transfer none): @cookie's expiration
+ * time, which is owned by @cookie and should not be modified or
+ * freed.
+ *
+ * Since: 2.32
+ **/
+SoupDate *
+soup_cookie_get_expires (SoupCookie *cookie)
+{
+ return cookie->expires;
+}
+
+/**
+ * soup_cookie_set_expires:
+ * @cookie: a #SoupCookie
+ * @expires: the new expiration time, or %NULL
+ *
+ * Sets @cookie's expiration time to @expires. If @expires is %NULL,
+ * @cookie will be a session cookie and will expire at the end of the
+ * client's session.
+ *
+ * (This sets the same property as soup_cookie_set_max_age().)
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookie_set_expires (SoupCookie *cookie, SoupDate *expires)
+{
+ if (cookie->expires)
+ soup_date_free (cookie->expires);
+
+ if (expires)
+ cookie->expires = soup_date_copy (expires);
+ else
+ cookie->expires = NULL;
+}
+
+/**
+ * soup_cookie_get_secure:
+ * @cookie: a #SoupCookie
+ *
+ * Gets @cookie's secure attribute
+ *
+ * Return value: @cookie's secure attribute
+ *
+ * Since: 2.32
+ **/
+gboolean
+soup_cookie_get_secure (SoupCookie *cookie)
+{
+ return cookie->secure;
+}
+
+/**
+ * soup_cookie_set_secure:
+ * @cookie: a #SoupCookie
+ * @secure: the new value for the secure attribute
+ *
+ * Sets @cookie's secure attribute to @secure. If %TRUE, @cookie will
+ * only be transmitted from the client to the server over secure
+ * (https) connections.
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookie_set_secure (SoupCookie *cookie, gboolean secure)
+{
+ cookie->secure = secure;
+}
+
+/**
+ * soup_cookie_get_http_only:
+ * @cookie: a #SoupCookie
+ *
+ * Gets @cookie's HttpOnly attribute
+ *
+ * Return value: @cookie's HttpOnly attribute
+ *
+ * Since: 2.32
+ **/
+gboolean
+soup_cookie_get_http_only (SoupCookie *cookie)
+{
+ return cookie->http_only;
+}
+
+/**
+ * soup_cookie_set_http_only:
+ * @cookie: a #SoupCookie
+ * @http_only: the new value for the HttpOnly attribute
+ *
+ * Sets @cookie's HttpOnly attribute to @http_only. If %TRUE, @cookie
+ * will be marked as "http only", meaning it should not be exposed to
+ * web page scripts or other untrusted code.
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookie_set_http_only (SoupCookie *cookie, gboolean http_only)
+{
+ cookie->http_only = http_only;
+}
+
+static void
+serialize_cookie (SoupCookie *cookie, GString *header, gboolean set_cookie)
+{
+ SoupSameSitePolicy same_site_policy;
+
+ if (!*cookie->name && !*cookie->value)
+ return;
+
+ if (header->len) {
+ if (set_cookie)
+ g_string_append (header, ", ");
+ else
+ g_string_append (header, "; ");
+ }
+
+ if (set_cookie || *cookie->name) {
+ g_string_append (header, cookie->name);
+ g_string_append (header, "=");
+ }
+ g_string_append (header, cookie->value);
+ if (!set_cookie)
+ return;
+
+ if (cookie->expires) {
+ char *timestamp;
+
+ g_string_append (header, "; expires=");
+ timestamp = soup_date_to_string (cookie->expires,
+ SOUP_DATE_COOKIE);
+ g_string_append (header, timestamp);
+ g_free (timestamp);
+ }
+ if (cookie->path) {
+ g_string_append (header, "; path=");
+ g_string_append (header, cookie->path);
+ }
+ if (cookie->domain) {
+ g_string_append (header, "; domain=");
+ g_string_append (header, cookie->domain);
+ }
+
+ same_site_policy = soup_cookie_get_same_site_policy (cookie);
+ if (same_site_policy != SOUP_SAME_SITE_POLICY_NONE) {
+ g_string_append (header, "; SameSite=");
+ if (same_site_policy == SOUP_SAME_SITE_POLICY_LAX)
+ g_string_append (header, "Lax");
+ else
+ g_string_append (header, "Strict");
+ }
+ if (cookie->secure)
+ g_string_append (header, "; secure");
+ if (cookie->http_only)
+ g_string_append (header, "; HttpOnly");
+}
+
+static const char *same_site_policy_string = "soup-same-site-policy";
+#define SAME_SITE_POLICY_QUARK (g_quark_from_static_string (same_site_policy_string))
+
+/**
+ * soup_cookie_set_same_site_policy:
+ * @cookie: a #SoupCookie
+ * @policy: a #SoupSameSitePolicy
+ *
+ * When used in conjunction with soup_cookie_jar_get_cookie_list_with_same_site_info() this
+ * sets the policy of when this cookie should be exposed.
+ *
+ * Since: 2.70
+ **/
+void
+soup_cookie_set_same_site_policy (SoupCookie *cookie,
+ SoupSameSitePolicy policy)
+{
+ switch (policy) {
+ case SOUP_SAME_SITE_POLICY_NONE:
+ case SOUP_SAME_SITE_POLICY_STRICT:
+ case SOUP_SAME_SITE_POLICY_LAX:
+ g_dataset_id_set_data (cookie, SAME_SITE_POLICY_QUARK, GUINT_TO_POINTER (policy));
+ break;
+ default:
+ g_return_if_reached ();
+ }
+}
+
+/**
+ * soup_cookie_get_same_site_policy:
+ * @cookie: a #SoupCookie
+ *
+ * Returns: a #SoupSameSitePolicy
+ *
+ * Since: 2.70
+ **/
+SoupSameSitePolicy
+soup_cookie_get_same_site_policy (SoupCookie *cookie)
+{
+ return GPOINTER_TO_UINT (g_dataset_id_get_data (cookie, SAME_SITE_POLICY_QUARK));
+}
+
+/**
+ * soup_cookie_to_set_cookie_header:
+ * @cookie: a #SoupCookie
+ *
+ * Serializes @cookie in the format used by the Set-Cookie header
+ * (ie, for sending a cookie from a #SoupServer to a client).
+ *
+ * Return value: the header
+ *
+ * Since: 2.24
+ **/
+char *
+soup_cookie_to_set_cookie_header (SoupCookie *cookie)
+{
+ GString *header = g_string_new (NULL);
+
+ serialize_cookie (cookie, header, TRUE);
+ return g_string_free (header, FALSE);
+}
+
+/**
+ * soup_cookie_to_cookie_header:
+ * @cookie: a #SoupCookie
+ *
+ * Serializes @cookie in the format used by the Cookie header (ie, for
+ * returning a cookie from a #SoupSession to a server).
+ *
+ * Return value: the header
+ *
+ * Since: 2.24
+ **/
+char *
+soup_cookie_to_cookie_header (SoupCookie *cookie)
+{
+ GString *header = g_string_new (NULL);
+
+ serialize_cookie (cookie, header, FALSE);
+ return g_string_free (header, FALSE);
+}
+
+/**
+ * soup_cookie_free:
+ * @cookie: a #SoupCookie
+ *
+ * Frees @cookie
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookie_free (SoupCookie *cookie)
+{
+ g_return_if_fail (cookie != NULL);
+
+ g_free (cookie->name);
+ g_free (cookie->value);
+ g_free (cookie->domain);
+ g_free (cookie->path);
+ g_clear_pointer (&cookie->expires, soup_date_free);
+
+ g_dataset_destroy (cookie);
+ g_slice_free (SoupCookie, cookie);
+}
+
+/**
+ * soup_cookies_from_response:
+ * @msg: a #SoupMessage containing a "Set-Cookie" response header
+ *
+ * Parses @msg's Set-Cookie response headers and returns a #GSList of
+ * #SoupCookie<!-- -->s. Cookies that do not specify "path" or
+ * "domain" attributes will have their values defaulted from @msg.
+ *
+ * Return value: (element-type SoupCookie) (transfer full): a #GSList
+ * of #SoupCookie<!-- -->s, which can be freed with
+ * soup_cookies_free().
+ *
+ * Since: 2.24
+ **/
+GSList *
+soup_cookies_from_response (SoupMessage *msg)
+{
+ SoupURI *origin;
+ const char *name, *value;
+ SoupCookie *cookie;
+ GSList *cookies = NULL;
+ SoupMessageHeadersIter iter;
+
+ origin = soup_message_get_uri (msg);
+
+ /* We have to use soup_message_headers_iter rather than
+ * soup_message_headers_get_list() since Set-Cookie isn't
+ * properly mergeable/unmergeable.
+ */
+ soup_message_headers_iter_init (&iter, msg->response_headers);
+ while (soup_message_headers_iter_next (&iter, &name, &value)) {
+ if (g_ascii_strcasecmp (name, "Set-Cookie") != 0)
+ continue;
+
+ cookie = parse_one_cookie (value, origin);
+ if (cookie)
+ cookies = g_slist_prepend (cookies, cookie);
+ }
+ return g_slist_reverse (cookies);
+}
+
+/**
+ * soup_cookies_from_request:
+ * @msg: a #SoupMessage containing a "Cookie" request header
+ *
+ * Parses @msg's Cookie request header and returns a #GSList of
+ * #SoupCookie<!-- -->s. As the "Cookie" header, unlike "Set-Cookie",
+ * only contains cookie names and values, none of the other
+ * #SoupCookie fields will be filled in. (Thus, you can't generally
+ * pass a cookie returned from this method directly to
+ * soup_cookies_to_response().)
+ *
+ * Return value: (element-type SoupCookie) (transfer full): a #GSList
+ * of #SoupCookie<!-- -->s, which can be freed with
+ * soup_cookies_free().
+ *
+ * Since: 2.24
+ **/
+GSList *
+soup_cookies_from_request (SoupMessage *msg)
+{
+ SoupCookie *cookie;
+ GSList *cookies = NULL;
+ GHashTable *params;
+ GHashTableIter iter;
+ gpointer name, value;
+ const char *header;
+
+ header = soup_message_headers_get_one (msg->request_headers, "Cookie");
+ if (!header)
+ return NULL;
+
+ params = soup_header_parse_semi_param_list (header);
+ g_hash_table_iter_init (&iter, params);
+ while (g_hash_table_iter_next (&iter, &name, &value)) {
+ if (name && value) {
+ cookie = cookie_new_internal (name, value,
+ NULL, NULL, 0);
+ cookies = g_slist_prepend (cookies, cookie);
+ }
+ }
+ soup_header_free_param_list (params);
+
+ return g_slist_reverse (cookies);
+}
+
+/**
+ * soup_cookies_to_response:
+ * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
+ * @msg: a #SoupMessage
+ *
+ * Appends a "Set-Cookie" response header to @msg for each cookie in
+ * @cookies. (This is in addition to any other "Set-Cookie" headers
+ * @msg may already have.)
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookies_to_response (GSList *cookies, SoupMessage *msg)
+{
+ GString *header;
+
+ header = g_string_new (NULL);
+ while (cookies) {
+ serialize_cookie (cookies->data, header, TRUE);
+ soup_message_headers_append (msg->response_headers,
+ "Set-Cookie", header->str);
+ g_string_truncate (header, 0);
+ cookies = cookies->next;
+ }
+ g_string_free (header, TRUE);
+}
+
+/**
+ * soup_cookies_to_request:
+ * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
+ * @msg: a #SoupMessage
+ *
+ * Adds the name and value of each cookie in @cookies to @msg's
+ * "Cookie" request. (If @msg already has a "Cookie" request header,
+ * these cookies will be appended to the cookies already present. Be
+ * careful that you do not append the same cookies twice, eg, when
+ * requeuing a message.)
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookies_to_request (GSList *cookies, SoupMessage *msg)
+{
+ GString *header;
+
+ header = g_string_new (soup_message_headers_get_one (msg->request_headers,
+ "Cookie"));
+ while (cookies) {
+ serialize_cookie (cookies->data, header, FALSE);
+ cookies = cookies->next;
+ }
+ soup_message_headers_replace (msg->request_headers,
+ "Cookie", header->str);
+ g_string_free (header, TRUE);
+}
+
+/**
+ * soup_cookies_free: (skip)
+ * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
+ *
+ * Frees @cookies.
+ *
+ * Since: 2.24
+ **/
+void
+soup_cookies_free (GSList *cookies)
+{
+ g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
+}
+
+/**
+ * soup_cookies_to_cookie_header:
+ * @cookies: (element-type SoupCookie): a #GSList of #SoupCookie
+ *
+ * Serializes a #GSList of #SoupCookie into a string suitable for
+ * setting as the value of the "Cookie" header.
+ *
+ * Return value: the serialization of @cookies
+ *
+ * Since: 2.24
+ **/
+char *
+soup_cookies_to_cookie_header (GSList *cookies)
+{
+ GString *str;
+
+ g_return_val_if_fail (cookies != NULL, NULL);
+
+ str = g_string_new (NULL);
+ while (cookies) {
+ serialize_cookie (cookies->data, str, FALSE);
+ cookies = cookies->next;
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+/**
+ * soup_cookie_applies_to_uri:
+ * @cookie: a #SoupCookie
+ * @uri: a #SoupURI
+ *
+ * Tests if @cookie should be sent to @uri.
+ *
+ * (At the moment, this does not check that @cookie's domain matches
+ * @uri, because it assumes that the caller has already done that.
+ * But don't rely on that; it may change in the future.)
+ *
+ * Return value: %TRUE if @cookie should be sent to @uri, %FALSE if
+ * not
+ *
+ * Since: 2.24
+ **/
+gboolean
+soup_cookie_applies_to_uri (SoupCookie *cookie, SoupURI *uri)
+{
+ int plen;
+
+ if (cookie->secure && !soup_uri_is_https (uri, NULL))
+ return FALSE;
+
+ if (cookie->expires && soup_date_is_past (cookie->expires))
+ return FALSE;
+
+ /* uri->path is required to be non-NULL */
+ g_return_val_if_fail (uri->path != NULL, FALSE);
+
+ plen = strlen (cookie->path);
+ if (plen == 0)
+ return TRUE;
+ if (strncmp (cookie->path, uri->path, plen) != 0)
+ return FALSE;
+ if (cookie->path[plen - 1] != '/' &&
+ uri->path[plen] && uri->path[plen] != '/')
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * soup_cookie_equal:
+ * @cookie1: a #SoupCookie
+ * @cookie2: a #SoupCookie
+ *
+ * Tests if @cookie1 and @cookie2 are equal.
+ *
+ * Note that currently, this does not check that the cookie domains
+ * match. This may change in the future.
+ *
+ * Return value: whether the cookies are equal.
+ *
+ * Since: 2.24
+ */
+gboolean
+soup_cookie_equal (SoupCookie *cookie1, SoupCookie *cookie2)
+{
+ g_return_val_if_fail (cookie1, FALSE);
+ g_return_val_if_fail (cookie2, FALSE);
+
+ return (!strcmp (cookie1->name, cookie2->name) &&
+ !strcmp (cookie1->value, cookie2->value) &&
+ !strcmp (cookie1->path, cookie2->path));
+}