mirror of
https://github.com/openharmony/third_party_libsoup.git
synced 2026-07-01 15:14:10 -04:00
Expose support for same-site cookies
This adds API for web browsers to set extra information to support same-site cookies. Note that usage of SoupSession alone does not provide enough information to reasonably use these at the moment and require manually setting the information with the extra context a browser may have.
This commit is contained in:
@@ -41,6 +41,11 @@ SoupMessagePriority
|
||||
soup_message_get_priority
|
||||
soup_message_set_priority
|
||||
<SUBSECTION>
|
||||
soup_message_get_site_for_cookies
|
||||
soup_message_set_site_for_cookies
|
||||
soup_message_get_is_top_level_navigation
|
||||
soup_message_set_is_top_level_navigation
|
||||
<SUBSECTION>
|
||||
SOUP_MESSAGE_METHOD
|
||||
SOUP_MESSAGE_URI
|
||||
SOUP_MESSAGE_HTTP_VERSION
|
||||
@@ -924,6 +929,10 @@ soup_cookie_get_secure
|
||||
soup_cookie_set_http_only
|
||||
soup_cookie_get_http_only
|
||||
<SUBSECTION>
|
||||
SoupSameSitePolicy
|
||||
soup_cookie_set_same_site_policy
|
||||
soup_cookie_get_same_site_policy
|
||||
<SUBSECTION>
|
||||
soup_cookie_applies_to_uri
|
||||
soup_cookie_domain_matches
|
||||
<SUBSECTION>
|
||||
@@ -950,6 +959,7 @@ SoupCookieJar
|
||||
soup_cookie_jar_new
|
||||
soup_cookie_jar_get_cookies
|
||||
soup_cookie_jar_get_cookie_list
|
||||
soup_cookie_jar_get_cookie_list_with_same_site_info
|
||||
soup_cookie_jar_set_cookie
|
||||
soup_cookie_jar_set_cookie_with_first_party
|
||||
<SUBSECTION>
|
||||
|
||||
@@ -128,9 +128,9 @@ soup_cookie_jar_db_new (const char *filename, gboolean read_only)
|
||||
NULL);
|
||||
}
|
||||
|
||||
#define QUERY_ALL "SELECT id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly FROM moz_cookies;"
|
||||
#define CREATE_TABLE "CREATE TABLE moz_cookies (id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT, path TEXT,expiry INTEGER, lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER)"
|
||||
#define QUERY_INSERT "INSERT INTO moz_cookies VALUES(NULL, %Q, %Q, %Q, %Q, %d, NULL, %d, %d);"
|
||||
#define QUERY_ALL "SELECT id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly, sameSite FROM moz_cookies;"
|
||||
#define CREATE_TABLE "CREATE TABLE moz_cookies (id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT, path TEXT, expiry INTEGER, lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER, sameSite INTEGER)"
|
||||
#define QUERY_INSERT "INSERT INTO moz_cookies VALUES(NULL, %Q, %Q, %Q, %Q, %d, NULL, %d, %d, %d);"
|
||||
#define QUERY_DELETE "DELETE FROM moz_cookies WHERE name=%Q AND host=%Q;"
|
||||
|
||||
enum {
|
||||
@@ -143,6 +143,7 @@ enum {
|
||||
COL_LAST_ACCESS,
|
||||
COL_SECURE,
|
||||
COL_HTTP_ONLY,
|
||||
COL_SAME_SITE_POLICY,
|
||||
N_COL,
|
||||
};
|
||||
|
||||
@@ -157,6 +158,7 @@ callback (void *data, int argc, char **argv, char **colname)
|
||||
time_t now;
|
||||
int max_age;
|
||||
gboolean http_only = FALSE, secure = FALSE;
|
||||
SoupSameSitePolicy same_site_policy;
|
||||
|
||||
now = time (NULL);
|
||||
|
||||
@@ -172,6 +174,7 @@ callback (void *data, int argc, char **argv, char **colname)
|
||||
|
||||
http_only = (g_strcmp0 (argv[COL_HTTP_ONLY], "1") == 0);
|
||||
secure = (g_strcmp0 (argv[COL_SECURE], "1") == 0);
|
||||
same_site_policy = g_ascii_strtoll (argv[COL_SAME_SITE_POLICY], NULL, 0);
|
||||
|
||||
cookie = soup_cookie_new (name, value, host, path, max_age);
|
||||
|
||||
@@ -179,6 +182,8 @@ callback (void *data, int argc, char **argv, char **colname)
|
||||
soup_cookie_set_secure (cookie, TRUE);
|
||||
if (http_only)
|
||||
soup_cookie_set_http_only (cookie, TRUE);
|
||||
if (same_site_policy)
|
||||
soup_cookie_set_same_site_policy (cookie, same_site_policy);
|
||||
|
||||
soup_cookie_jar_add_cookie (jar, cookie);
|
||||
|
||||
@@ -241,6 +246,10 @@ open_db (SoupCookieJar *jar)
|
||||
sqlite3_free (error);
|
||||
}
|
||||
|
||||
/* Migrate old DB to include same-site info. We simply always run this as it
|
||||
will safely handle a column with the same name existing */
|
||||
sqlite3_exec (priv->db, "ALTER TABLE moz_cookies ADD COLUMN sameSite INTEGER DEFAULT 0", NULL, NULL, NULL);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -291,7 +300,8 @@ soup_cookie_jar_db_changed (SoupCookieJar *jar,
|
||||
new_cookie->path,
|
||||
expires,
|
||||
new_cookie->secure,
|
||||
new_cookie->http_only);
|
||||
new_cookie->http_only,
|
||||
soup_cookie_get_same_site_policy (new_cookie));
|
||||
exec_query_with_try_create_table (priv->db, query, NULL, NULL);
|
||||
sqlite3_free (query);
|
||||
}
|
||||
|
||||
@@ -121,6 +121,34 @@ soup_cookie_jar_text_new (const char *filename, gboolean read_only)
|
||||
NULL);
|
||||
}
|
||||
|
||||
static SoupSameSitePolicy
|
||||
string_to_same_site_policy (const char *string)
|
||||
{
|
||||
if (strcmp (string, "Lax") == 0)
|
||||
return SOUP_SAME_SITE_POLICY_LAX;
|
||||
else if (strcmp (string, "Strict") == 0)
|
||||
return SOUP_SAME_SITE_POLICY_STRICT;
|
||||
else if (strcmp (string, "None") == 0)
|
||||
return SOUP_SAME_SITE_POLICY_NONE;
|
||||
else
|
||||
g_return_val_if_reached (SOUP_SAME_SITE_POLICY_NONE);
|
||||
}
|
||||
|
||||
static const char *
|
||||
same_site_policy_to_string (SoupSameSitePolicy policy)
|
||||
{
|
||||
switch (policy) {
|
||||
case SOUP_SAME_SITE_POLICY_STRICT:
|
||||
return "Strict";
|
||||
case SOUP_SAME_SITE_POLICY_LAX:
|
||||
return "Lax";
|
||||
case SOUP_SAME_SITE_POLICY_NONE:
|
||||
return "None";
|
||||
}
|
||||
|
||||
g_return_val_if_reached ("None");
|
||||
}
|
||||
|
||||
static SoupCookie*
|
||||
parse_cookie (char *line, time_t now)
|
||||
{
|
||||
@@ -129,7 +157,8 @@ parse_cookie (char *line, time_t now)
|
||||
gboolean http_only;
|
||||
gulong expire_time;
|
||||
int max_age;
|
||||
char *host, *path, *secure, *expires, *name, *value;
|
||||
char *host, *path, *secure, *expires, *name, *value, *samesite = NULL;
|
||||
gsize result_length;
|
||||
|
||||
if (g_str_has_prefix (line, "#HttpOnly_")) {
|
||||
http_only = TRUE;
|
||||
@@ -140,7 +169,8 @@ parse_cookie (char *line, time_t now)
|
||||
http_only = FALSE;
|
||||
|
||||
result = g_strsplit (line, "\t", -1);
|
||||
if (g_strv_length (result) != 7)
|
||||
result_length = g_strv_length (result);
|
||||
if (result_length < 7)
|
||||
goto out;
|
||||
|
||||
/* Check this first */
|
||||
@@ -164,8 +194,14 @@ parse_cookie (char *line, time_t now)
|
||||
name = result[5];
|
||||
value = result[6];
|
||||
|
||||
if (result_length == 8)
|
||||
samesite = result[7];
|
||||
|
||||
cookie = soup_cookie_new (name, value, host, path, max_age);
|
||||
|
||||
if (samesite != NULL)
|
||||
soup_cookie_set_same_site_policy (cookie, string_to_same_site_policy (samesite));
|
||||
|
||||
if (strcmp (secure, "FALSE") != 0)
|
||||
soup_cookie_set_secure (cookie, TRUE);
|
||||
if (http_only)
|
||||
@@ -219,7 +255,7 @@ write_cookie (FILE *out, SoupCookie *cookie)
|
||||
{
|
||||
fseek (out, 0, SEEK_END);
|
||||
|
||||
fprintf (out, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
|
||||
fprintf (out, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\t%s\n",
|
||||
cookie->http_only ? "#HttpOnly_" : "",
|
||||
cookie->domain,
|
||||
*cookie->domain == '.' ? "TRUE" : "FALSE",
|
||||
@@ -227,7 +263,8 @@ write_cookie (FILE *out, SoupCookie *cookie)
|
||||
cookie->secure ? "TRUE" : "FALSE",
|
||||
(gulong)soup_date_to_time_t (cookie->expires),
|
||||
cookie->name,
|
||||
cookie->value);
|
||||
cookie->value,
|
||||
same_site_policy_to_string (soup_cookie_get_same_site_policy (cookie)));
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
+93
-10
@@ -12,6 +12,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "soup-cookie-jar.h"
|
||||
#include "soup-message-private.h"
|
||||
#include "soup-misc-private.h"
|
||||
#include "soup.h"
|
||||
|
||||
@@ -299,8 +300,42 @@ compare_cookies (gconstpointer a, gconstpointer b, gpointer jar)
|
||||
return aserial - bserial;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
cookie_is_valid_for_same_site_policy (SoupCookie *cookie,
|
||||
const char *method,
|
||||
SoupURI *uri,
|
||||
SoupURI *top_level,
|
||||
SoupURI *cookie_uri,
|
||||
gboolean is_top_level_navigation,
|
||||
gboolean for_http)
|
||||
{
|
||||
SoupSameSitePolicy policy = soup_cookie_get_same_site_policy (cookie);
|
||||
|
||||
if (policy == SOUP_SAME_SITE_POLICY_NONE)
|
||||
return TRUE;
|
||||
|
||||
if (top_level == NULL)
|
||||
return TRUE;
|
||||
|
||||
if (policy == SOUP_SAME_SITE_POLICY_LAX && is_top_level_navigation &&
|
||||
(SOUP_METHOD_IS_SAFE (method) || for_http == FALSE))
|
||||
return TRUE;
|
||||
|
||||
if (is_top_level_navigation && cookie_uri == NULL)
|
||||
return FALSE;
|
||||
|
||||
return soup_host_matches_host (soup_uri_get_host (cookie_uri ? cookie_uri : top_level), soup_uri_get_host (uri));
|
||||
}
|
||||
|
||||
static GSList *
|
||||
get_cookies (SoupCookieJar *jar, SoupURI *uri, gboolean for_http, gboolean copy_cookies)
|
||||
get_cookies (SoupCookieJar *jar,
|
||||
SoupURI *uri,
|
||||
SoupURI *top_level,
|
||||
SoupURI *site_for_cookies,
|
||||
const char *method,
|
||||
gboolean for_http,
|
||||
gboolean is_top_level_navigation,
|
||||
gboolean copy_cookies)
|
||||
{
|
||||
SoupCookieJarPrivate *priv;
|
||||
GSList *cookies, *domain_cookies;
|
||||
@@ -334,6 +369,9 @@ get_cookies (SoupCookieJar *jar, SoupURI *uri, gboolean for_http, gboolean copy_
|
||||
g_strdup (cur),
|
||||
new_head);
|
||||
} else if (soup_cookie_applies_to_uri (cookie, uri) &&
|
||||
cookie_is_valid_for_same_site_policy (cookie, method, uri, top_level,
|
||||
site_for_cookies, is_top_level_navigation,
|
||||
for_http) &&
|
||||
(for_http || !cookie->http_only))
|
||||
cookies = g_slist_append (cookies, copy_cookies ? soup_cookie_copy (cookie) : cookie);
|
||||
|
||||
@@ -388,7 +426,7 @@ soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri,
|
||||
g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
|
||||
g_return_val_if_fail (uri != NULL, NULL);
|
||||
|
||||
cookies = get_cookies (jar, uri, for_http, FALSE);
|
||||
cookies = get_cookies (jar, uri, NULL, NULL, NULL, for_http, FALSE, FALSE);
|
||||
|
||||
if (cookies) {
|
||||
char *result = soup_cookies_to_cookie_header (cookies);
|
||||
@@ -432,7 +470,46 @@ soup_cookie_jar_get_cookie_list (SoupCookieJar *jar, SoupURI *uri, gboolean for_
|
||||
g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
|
||||
g_return_val_if_fail (uri != NULL, NULL);
|
||||
|
||||
return get_cookies (jar, uri, for_http, TRUE);
|
||||
return get_cookies (jar, uri, NULL, NULL, NULL, for_http, FALSE, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* soup_cookie_jar_get_cookie_list_with_same_site_info:
|
||||
* @jar: a #SoupCookieJar
|
||||
* @uri: a #SoupURI
|
||||
* @top_level: (nullable): a #SoupURI for the top level document
|
||||
* @site_for_cookies: (nullable): a #SoupURI indicating the origin to get cookies for
|
||||
* @method: (nullable): the HTTP method requesting the cookies, this
|
||||
* should only be %NULL when @for_http is %FALSE
|
||||
* @for_http: whether or not the return value is being passed directly
|
||||
* to an HTTP operation
|
||||
* @is_top_level_navigation: whether or not the HTTP request is part of
|
||||
* top level navigation
|
||||
*
|
||||
* This is an extended version of soup_cookie_jar_get_cookie_list() that
|
||||
* provides more information required to use SameSite cookies. See the
|
||||
* [SameSite cookies spec](https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00)
|
||||
* for more detailed information.
|
||||
*
|
||||
* Return value: (transfer full) (element-type Soup.Cookie): a #GSList
|
||||
* with the cookies in the @jar that would be sent with a request to @uri.
|
||||
*
|
||||
* Since: 2.70
|
||||
*/
|
||||
GSList *
|
||||
soup_cookie_jar_get_cookie_list_with_same_site_info (SoupCookieJar *jar,
|
||||
SoupURI *uri,
|
||||
SoupURI *top_level,
|
||||
SoupURI *site_for_cookies,
|
||||
const char *method,
|
||||
gboolean for_http,
|
||||
gboolean is_top_level_navigation)
|
||||
{
|
||||
g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
|
||||
g_return_val_if_fail (uri != NULL, NULL);
|
||||
g_return_val_if_fail (method != NULL || for_http == FALSE, NULL);
|
||||
|
||||
return get_cookies (jar, uri, top_level, site_for_cookies, g_intern_string (method), for_http, is_top_level_navigation, TRUE);
|
||||
}
|
||||
|
||||
static const char *
|
||||
@@ -724,15 +801,21 @@ static void
|
||||
msg_starting_cb (SoupMessage *msg, gpointer feature)
|
||||
{
|
||||
SoupCookieJar *jar = SOUP_COOKIE_JAR (feature);
|
||||
char *cookies;
|
||||
GSList *cookies;
|
||||
|
||||
cookies = soup_cookie_jar_get_cookies (jar, soup_message_get_uri (msg), TRUE);
|
||||
if (cookies) {
|
||||
soup_message_headers_replace (msg->request_headers,
|
||||
"Cookie", cookies);
|
||||
g_free (cookies);
|
||||
} else
|
||||
cookies = soup_cookie_jar_get_cookie_list_with_same_site_info (jar, soup_message_get_uri (msg),
|
||||
soup_message_get_first_party (msg),
|
||||
soup_message_get_site_for_cookies (msg),
|
||||
msg->method,
|
||||
TRUE, soup_message_get_is_top_level_navigation (msg));
|
||||
if (cookies != NULL) {
|
||||
char *cookie_header = soup_cookies_to_cookie_header (cookies);
|
||||
soup_message_headers_replace (msg->request_headers, "Cookie", cookie_header);
|
||||
g_free (cookie_header);
|
||||
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
|
||||
} else {
|
||||
soup_message_headers_remove (msg->request_headers, "Cookie");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -59,6 +59,15 @@ SOUP_AVAILABLE_IN_2_40
|
||||
GSList * soup_cookie_jar_get_cookie_list (SoupCookieJar *jar,
|
||||
SoupURI *uri,
|
||||
gboolean for_http);
|
||||
SOUP_AVAILABLE_IN_2_70
|
||||
GSList * soup_cookie_jar_get_cookie_list_with_same_site_info (
|
||||
SoupCookieJar *jar,
|
||||
SoupURI *uri,
|
||||
SoupURI *top_level,
|
||||
SoupURI *site_for_cookies,
|
||||
const char *method,
|
||||
gboolean for_http,
|
||||
gboolean is_top_level_navigation);
|
||||
SOUP_AVAILABLE_IN_2_24
|
||||
void soup_cookie_jar_set_cookie (SoupCookieJar *jar,
|
||||
SoupURI *uri,
|
||||
|
||||
@@ -88,6 +88,7 @@ soup_cookie_copy (SoupCookie *cookie)
|
||||
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;
|
||||
}
|
||||
@@ -238,6 +239,18 @@ parse_one_cookie (const char *header, SoupURI *origin)
|
||||
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.
|
||||
@@ -708,6 +721,8 @@ soup_cookie_set_http_only (SoupCookie *cookie, gboolean http_only)
|
||||
static void
|
||||
serialize_cookie (SoupCookie *cookie, GString *header, gboolean set_cookie)
|
||||
{
|
||||
SoupSameSitePolicy same_site_policy;
|
||||
|
||||
if (!*cookie->name && !*cookie->value)
|
||||
return;
|
||||
|
||||
@@ -743,12 +758,63 @@ serialize_cookie (SoupCookie *cookie, GString *header, gboolean set_cookie)
|
||||
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
|
||||
@@ -808,6 +874,7 @@ soup_cookie_free (SoupCookie *cookie)
|
||||
g_free (cookie->path);
|
||||
g_clear_pointer (&cookie->expires, soup_date_free);
|
||||
|
||||
g_dataset_destroy (cookie);
|
||||
g_slice_free (SoupCookie, cookie);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,20 @@
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* SoupSameSitePolicy:
|
||||
* @SOUP_SAME_SITE_POLICY_NONE: The cookie is exposed with both cross-site and same-site requests
|
||||
* @SOUP_SAME_SITE_POLICY_LAX: The cookie is withheld on cross-site requests but exposed on cross-site navigations
|
||||
* @SOUP_SAME_SITE_POLICY_STRICT: The cookie is only exposed for same-site requests
|
||||
*
|
||||
* Since: 2.70
|
||||
*/
|
||||
typedef enum {
|
||||
SOUP_SAME_SITE_POLICY_NONE,
|
||||
SOUP_SAME_SITE_POLICY_LAX,
|
||||
SOUP_SAME_SITE_POLICY_STRICT,
|
||||
} SoupSameSitePolicy;
|
||||
|
||||
struct _SoupCookie {
|
||||
char *name;
|
||||
char *value;
|
||||
@@ -80,6 +94,12 @@ SOUP_AVAILABLE_IN_2_24
|
||||
void soup_cookie_set_http_only (SoupCookie *cookie,
|
||||
gboolean http_only);
|
||||
|
||||
SOUP_AVAILABLE_IN_2_70
|
||||
void soup_cookie_set_same_site_policy (SoupCookie *cookie,
|
||||
SoupSameSitePolicy policy);
|
||||
SOUP_AVAILABLE_IN_2_70
|
||||
SoupSameSitePolicy soup_cookie_get_same_site_policy (SoupCookie *cookie);
|
||||
|
||||
SOUP_AVAILABLE_IN_2_24
|
||||
char *soup_cookie_to_set_cookie_header (SoupCookie *cookie);
|
||||
SOUP_AVAILABLE_IN_2_24
|
||||
|
||||
@@ -36,6 +36,7 @@ typedef struct {
|
||||
GSList *disabled_features;
|
||||
|
||||
SoupURI *first_party;
|
||||
SoupURI *site_for_cookies;
|
||||
|
||||
GTlsCertificate *tls_certificate;
|
||||
GTlsCertificateFlags tls_errors;
|
||||
@@ -43,6 +44,8 @@ typedef struct {
|
||||
SoupRequest *request;
|
||||
|
||||
SoupMessagePriority priority;
|
||||
|
||||
gboolean is_top_level_navigation;
|
||||
} SoupMessagePrivate;
|
||||
|
||||
void soup_message_cleanup_response (SoupMessage *msg);
|
||||
|
||||
@@ -144,6 +144,8 @@ enum {
|
||||
PROP_TLS_CERTIFICATE,
|
||||
PROP_TLS_ERRORS,
|
||||
PROP_PRIORITY,
|
||||
PROP_SITE_FOR_COOKIES,
|
||||
PROP_IS_TOP_LEVEL_NAVIGATION,
|
||||
|
||||
LAST_PROP
|
||||
};
|
||||
@@ -174,6 +176,7 @@ soup_message_finalize (GObject *object)
|
||||
|
||||
g_clear_pointer (&priv->uri, soup_uri_free);
|
||||
g_clear_pointer (&priv->first_party, soup_uri_free);
|
||||
g_clear_pointer (&priv->site_for_cookies, soup_uri_free);
|
||||
g_clear_object (&priv->addr);
|
||||
|
||||
g_clear_object (&priv->auth);
|
||||
@@ -207,6 +210,12 @@ soup_message_set_property (GObject *object, guint prop_id,
|
||||
case PROP_URI:
|
||||
soup_message_set_uri (msg, g_value_get_boxed (value));
|
||||
break;
|
||||
case PROP_SITE_FOR_COOKIES:
|
||||
soup_message_set_site_for_cookies (msg, g_value_get_boxed (value));
|
||||
break;
|
||||
case PROP_IS_TOP_LEVEL_NAVIGATION:
|
||||
soup_message_set_is_top_level_navigation (msg, g_value_get_boolean (value));
|
||||
break;
|
||||
case PROP_HTTP_VERSION:
|
||||
soup_message_set_http_version (msg, g_value_get_enum (value));
|
||||
break;
|
||||
@@ -270,6 +279,12 @@ soup_message_get_property (GObject *object, guint prop_id,
|
||||
case PROP_URI:
|
||||
g_value_set_boxed (value, priv->uri);
|
||||
break;
|
||||
case PROP_SITE_FOR_COOKIES:
|
||||
g_value_set_boxed (value, priv->site_for_cookies);
|
||||
break;
|
||||
case PROP_IS_TOP_LEVEL_NAVIGATION:
|
||||
g_value_set_boolean (value, priv->is_top_level_navigation);
|
||||
break;
|
||||
case PROP_HTTP_VERSION:
|
||||
g_value_set_enum (value, priv->http_version);
|
||||
break;
|
||||
@@ -811,6 +826,34 @@ soup_message_class_init (SoupMessageClass *message_class)
|
||||
SOUP_TYPE_URI,
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* SoupMessage:site-for-cookkies:
|
||||
*
|
||||
* Site used to compare cookies against. Used for SameSite cookie support.
|
||||
*
|
||||
* Since: 2.70
|
||||
*/
|
||||
g_object_class_install_property (
|
||||
object_class, PROP_SITE_FOR_COOKIES,
|
||||
g_param_spec_boxed (SOUP_MESSAGE_SITE_FOR_COOKIES,
|
||||
"Site for cookies",
|
||||
"The URI for the site to compare cookies against",
|
||||
SOUP_TYPE_URI,
|
||||
G_PARAM_READWRITE));
|
||||
/**
|
||||
* SoupMessage:is-top-level-navigation:
|
||||
*
|
||||
* Set when the message is navigating between top level domains.
|
||||
*
|
||||
* Since: 2.70
|
||||
*/
|
||||
g_object_class_install_property (
|
||||
object_class, PROP_IS_TOP_LEVEL_NAVIGATION,
|
||||
g_param_spec_boolean (SOUP_MESSAGE_IS_TOP_LEVEL_NAVIGATION,
|
||||
"Is top-level navigation",
|
||||
"If the current messsage is navigating between top-levels",
|
||||
FALSE,
|
||||
G_PARAM_READWRITE));
|
||||
/**
|
||||
* SOUP_MESSAGE_REQUEST_BODY:
|
||||
*
|
||||
@@ -1956,6 +1999,111 @@ soup_message_set_first_party (SoupMessage *msg,
|
||||
g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_FIRST_PARTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* soup_message_get_site_for_cookies:
|
||||
* @msg: a #SoupMessage
|
||||
*
|
||||
* Gets @msg's site for cookies #SoupURI
|
||||
*
|
||||
* Returns: (transfer none): the @msg's site for cookies #SoupURI
|
||||
*
|
||||
* Since: 2.70
|
||||
**/
|
||||
SoupURI *
|
||||
soup_message_get_site_for_cookies (SoupMessage *msg)
|
||||
{
|
||||
SoupMessagePrivate *priv;
|
||||
|
||||
g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
|
||||
|
||||
priv = soup_message_get_instance_private (msg);
|
||||
return priv->site_for_cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* soup_message_set_site_for_cookies:
|
||||
* @msg: a #SoupMessage
|
||||
* @site_for_cookies: (nullable): the #SoupURI for the @msg's site for cookies
|
||||
*
|
||||
* Sets @site_for_cookies as the policy URL for same-site cookies for @msg.
|
||||
*
|
||||
* It is either the URL of the top-level document or %NULL depending on whether the registrable
|
||||
* domain of this document's URL matches the registrable domain of its parent's/opener's
|
||||
* URL. For the top-level document it is set to the document's URL.
|
||||
*
|
||||
* See the [same-site spec](https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00)
|
||||
* for more information.
|
||||
*
|
||||
* Since: 2.70
|
||||
**/
|
||||
void
|
||||
soup_message_set_site_for_cookies (SoupMessage *msg,
|
||||
SoupURI *site_for_cookies)
|
||||
{
|
||||
SoupMessagePrivate *priv;
|
||||
|
||||
g_return_if_fail (SOUP_IS_MESSAGE (msg));
|
||||
|
||||
priv = soup_message_get_instance_private (msg);
|
||||
|
||||
if (priv->site_for_cookies == site_for_cookies)
|
||||
return;
|
||||
|
||||
if (priv->site_for_cookies) {
|
||||
if (site_for_cookies && soup_uri_equal (priv->site_for_cookies, site_for_cookies))
|
||||
return;
|
||||
|
||||
soup_uri_free (priv->site_for_cookies);
|
||||
}
|
||||
|
||||
priv->site_for_cookies = site_for_cookies ? soup_uri_copy (site_for_cookies) : NULL;
|
||||
g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_SITE_FOR_COOKIES);
|
||||
}
|
||||
|
||||
/**
|
||||
* soup_message_set_is_top_level_navigation:
|
||||
* @msg: a #SoupMessage
|
||||
* @is_top_level_navigation: if %TRUE indicate the current request is a top-level navigation
|
||||
*
|
||||
* See the [same-site spec](https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00)
|
||||
* for more information.
|
||||
*
|
||||
* Since: 2.70
|
||||
**/
|
||||
void
|
||||
soup_message_set_is_top_level_navigation (SoupMessage *msg,
|
||||
gboolean is_top_level_navigation)
|
||||
{
|
||||
SoupMessagePrivate *priv;
|
||||
|
||||
g_return_if_fail (SOUP_IS_MESSAGE (msg));
|
||||
|
||||
priv = soup_message_get_instance_private (msg);
|
||||
|
||||
if (priv->is_top_level_navigation == is_top_level_navigation)
|
||||
return;
|
||||
|
||||
priv->is_top_level_navigation = is_top_level_navigation;
|
||||
g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_IS_TOP_LEVEL_NAVIGATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* soup_message_get_is_top_level_navigation:
|
||||
* @msg: a #SoupMessage
|
||||
*
|
||||
* Since: 2.70
|
||||
**/
|
||||
gboolean
|
||||
soup_message_get_is_top_level_navigation (SoupMessage *msg)
|
||||
{
|
||||
SoupMessagePrivate *priv;
|
||||
|
||||
g_return_val_if_fail (SOUP_IS_MESSAGE (msg), FALSE);
|
||||
|
||||
priv = soup_message_get_instance_private (msg);
|
||||
return priv->is_top_level_navigation;
|
||||
}
|
||||
|
||||
void
|
||||
soup_message_set_https_status (SoupMessage *msg, SoupConnection *conn)
|
||||
{
|
||||
|
||||
@@ -69,6 +69,7 @@ GType soup_message_get_type (void);
|
||||
#define SOUP_MESSAGE_STATUS_CODE "status-code"
|
||||
#define SOUP_MESSAGE_REASON_PHRASE "reason-phrase"
|
||||
#define SOUP_MESSAGE_FIRST_PARTY "first-party"
|
||||
#define SOUP_MESSAGE_SITE_FOR_COOKIES "site-for-cookies"
|
||||
#define SOUP_MESSAGE_REQUEST_BODY "request-body"
|
||||
#define SOUP_MESSAGE_REQUEST_BODY_DATA "request-body-data"
|
||||
#define SOUP_MESSAGE_REQUEST_HEADERS "request-headers"
|
||||
@@ -78,6 +79,7 @@ GType soup_message_get_type (void);
|
||||
#define SOUP_MESSAGE_TLS_CERTIFICATE "tls-certificate"
|
||||
#define SOUP_MESSAGE_TLS_ERRORS "tls-errors"
|
||||
#define SOUP_MESSAGE_PRIORITY "priority"
|
||||
#define SOUP_MESSAGE_IS_TOP_LEVEL_NAVIGATION "is-top-level-navigation"
|
||||
|
||||
SOUP_AVAILABLE_IN_2_4
|
||||
SoupMessage *soup_message_new (const char *method,
|
||||
@@ -126,6 +128,16 @@ SoupURI *soup_message_get_first_party (SoupMessage *msg);
|
||||
SOUP_AVAILABLE_IN_2_30
|
||||
void soup_message_set_first_party (SoupMessage *msg,
|
||||
SoupURI *first_party);
|
||||
SOUP_AVAILABLE_IN_2_70
|
||||
SoupURI *soup_message_get_site_for_cookies (SoupMessage *msg);
|
||||
SOUP_AVAILABLE_IN_2_70
|
||||
void soup_message_set_site_for_cookies (SoupMessage *msg,
|
||||
SoupURI *site_for_cookies);
|
||||
SOUP_AVAILABLE_IN_2_70
|
||||
void soup_message_set_is_top_level_navigation (SoupMessage *msg,
|
||||
gboolean is_top_level_navigation);
|
||||
SOUP_AVAILABLE_IN_2_70
|
||||
gboolean soup_message_get_is_top_level_navigation (SoupMessage *msg);
|
||||
|
||||
typedef enum {
|
||||
SOUP_MESSAGE_NO_REDIRECT = (1 << 1),
|
||||
|
||||
@@ -278,13 +278,13 @@ do_cookies_parsing_test (void)
|
||||
|
||||
msg = soup_message_new_from_uri ("GET", first_party_uri);
|
||||
soup_message_headers_append (msg->request_headers, "Echo-Set-Cookie",
|
||||
"two=2; HttpOnly; max-age=100");
|
||||
"two=2; HttpOnly; max-age=100; SameSite=Invalid");
|
||||
soup_session_send_message (session, msg);
|
||||
g_object_unref (msg);
|
||||
|
||||
msg = soup_message_new_from_uri ("GET", first_party_uri);
|
||||
soup_message_headers_append (msg->request_headers, "Echo-Set-Cookie",
|
||||
"three=3; httpONLY=Wednesday; max-age=100");
|
||||
"three=3; httpONLY=Wednesday; max-age=100; SameSite=Lax");
|
||||
soup_session_send_message (session, msg);
|
||||
g_object_unref (msg);
|
||||
|
||||
@@ -302,10 +302,12 @@ do_cookies_parsing_test (void)
|
||||
got2 = TRUE;
|
||||
g_assert_true (soup_cookie_get_http_only (cookie));
|
||||
g_assert_true (soup_cookie_get_expires (cookie) != NULL);
|
||||
g_assert_cmpint (soup_cookie_get_same_site_policy (cookie), ==, SOUP_SAME_SITE_POLICY_NONE);
|
||||
} else if (!strcmp (soup_cookie_get_name (cookie), "three")) {
|
||||
got3 = TRUE;
|
||||
g_assert_true (soup_cookie_get_http_only (cookie));
|
||||
g_assert_true (soup_cookie_get_expires (cookie) != NULL);
|
||||
g_assert_cmpint (soup_cookie_get_same_site_policy (cookie), ==, SOUP_SAME_SITE_POLICY_LAX);
|
||||
} else {
|
||||
soup_test_assert (FALSE, "got unexpected cookie '%s'",
|
||||
soup_cookie_get_name (cookie));
|
||||
|
||||
@@ -44,6 +44,7 @@ tests = [
|
||||
['redirect', true, []],
|
||||
['requester', true, []],
|
||||
['resource', true, []],
|
||||
['samesite', true, []],
|
||||
['session', true, []],
|
||||
['server-auth', true, []],
|
||||
['server', true, []],
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
||||
|
||||
#include "test-utils.h"
|
||||
|
||||
typedef struct {
|
||||
SoupURI *origin_uri;
|
||||
SoupURI *cross_uri;
|
||||
SoupCookieJar *jar;
|
||||
GSList *cookies;
|
||||
} SameSiteFixture;
|
||||
|
||||
static void
|
||||
same_site_setup (SameSiteFixture *fixture,
|
||||
gconstpointer data)
|
||||
{
|
||||
SoupCookie *cookie_none, *cookie_lax, *cookie_strict;
|
||||
|
||||
fixture->origin_uri = soup_uri_new ("http://127.0.0.1");
|
||||
fixture->cross_uri = soup_uri_new ("http://localhost");
|
||||
fixture->jar = soup_cookie_jar_new ();
|
||||
|
||||
cookie_none = soup_cookie_new ("none", "1", "127.0.0.1", "/", 1000);
|
||||
cookie_lax = soup_cookie_new ("lax", "1", "127.0.0.1", "/", 1000);
|
||||
soup_cookie_set_same_site_policy (cookie_lax, SOUP_SAME_SITE_POLICY_LAX);
|
||||
cookie_strict = soup_cookie_new ("strict", "1", "127.0.0.1", "/", 1000);
|
||||
soup_cookie_set_same_site_policy (cookie_strict, SOUP_SAME_SITE_POLICY_STRICT);
|
||||
|
||||
soup_cookie_jar_add_cookie_with_first_party (fixture->jar, fixture->origin_uri, cookie_none);
|
||||
soup_cookie_jar_add_cookie_with_first_party (fixture->jar, fixture->origin_uri, cookie_lax);
|
||||
soup_cookie_jar_add_cookie_with_first_party (fixture->jar, fixture->origin_uri, cookie_strict);
|
||||
}
|
||||
|
||||
static void
|
||||
same_site_teardown (SameSiteFixture *fixture,
|
||||
gconstpointer data)
|
||||
{
|
||||
g_object_unref (fixture->jar);
|
||||
soup_uri_free (fixture->origin_uri);
|
||||
soup_uri_free (fixture->cross_uri);
|
||||
g_slist_free_full (fixture->cookies, (GDestroyNotify) soup_cookie_free);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_highest_policy_visible (GSList *cookies, SoupSameSitePolicy policy)
|
||||
{
|
||||
GSList *l;
|
||||
size_t size = 0, expected_count;
|
||||
for (l = cookies; l; l = l->next) {
|
||||
g_assert_cmpint (soup_cookie_get_same_site_policy (l->data), <=, policy);
|
||||
++size;
|
||||
}
|
||||
|
||||
switch (policy) {
|
||||
case SOUP_SAME_SITE_POLICY_STRICT:
|
||||
expected_count = 3;
|
||||
break;
|
||||
case SOUP_SAME_SITE_POLICY_LAX:
|
||||
expected_count = 2;
|
||||
break;
|
||||
case SOUP_SAME_SITE_POLICY_NONE:
|
||||
expected_count = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
g_assert_cmpuint (size, ==, expected_count);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
gboolean cross_origin;
|
||||
gboolean cookie_uri_is_origin;
|
||||
gboolean top_level_nav;
|
||||
gboolean javascript;
|
||||
gboolean unsafe_method;
|
||||
SoupSameSitePolicy visible_policy;
|
||||
} SameSiteTest;
|
||||
|
||||
static void
|
||||
same_site_test (SameSiteFixture *fixture, gconstpointer user_data)
|
||||
{
|
||||
const SameSiteTest *test = user_data;
|
||||
fixture->cookies = soup_cookie_jar_get_cookie_list_with_same_site_info (fixture->jar, fixture->origin_uri,
|
||||
test->cross_origin ? fixture->cross_uri : fixture->origin_uri,
|
||||
test->cookie_uri_is_origin ? fixture->origin_uri : NULL,
|
||||
test->unsafe_method ? "POST" : "GET",
|
||||
test->javascript ? FALSE : TRUE,
|
||||
test->top_level_nav);
|
||||
assert_highest_policy_visible (fixture->cookies, test->visible_policy);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
int ret, i;
|
||||
SameSiteTest same_site_tests[] = {
|
||||
/* This does not necessarily cover all combinations since some make no sense in real use */
|
||||
|
||||
/* Situations where Strict are passed: */
|
||||
{ .name="/same-site/basic", .visible_policy=SOUP_SAME_SITE_POLICY_STRICT },
|
||||
{ .name="/same-site/basic-js", .javascript=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_STRICT },
|
||||
{ .name="/same-site/top-level-to-same-site", .top_level_nav=TRUE, .cookie_uri_is_origin=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_STRICT },
|
||||
{ .name="/same-site/top-level-to-same-site-js", .top_level_nav=TRUE, .cookie_uri_is_origin=TRUE, .javascript=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_STRICT },
|
||||
{ .name="/same-site/unsafe-method", .unsafe_method=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_STRICT },
|
||||
{ .name="/same-site/unsafe-method-js", .unsafe_method=TRUE, .javascript=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_STRICT },
|
||||
{ .name="/same-site/cross-top-level-to-same-site", .cross_origin=TRUE, .top_level_nav=TRUE, .cookie_uri_is_origin=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_STRICT },
|
||||
{ .name="/same-site/cross-top-level-to-same-site-js", .cross_origin=TRUE, .javascript=TRUE, .top_level_nav=TRUE, .cookie_uri_is_origin=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_STRICT },
|
||||
|
||||
/* Situations where Lax are passed: */
|
||||
{ .name="/same-site/top-level", .top_level_nav=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_LAX },
|
||||
{ .name="/same-site/top-level-js", .top_level_nav=TRUE, .javascript=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_LAX },
|
||||
{ .name="/same-site/cross-top-level", .cross_origin=TRUE, .top_level_nav=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_LAX },
|
||||
{ .name="/same-site/cross-top-level-js", .cross_origin=TRUE, .javascript=TRUE, .top_level_nav=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_LAX },
|
||||
{ .name="/same-site/cross-unsafe-method-top-level-js", .cross_origin=TRUE, .javascript=TRUE, .unsafe_method=TRUE, .top_level_nav=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_LAX },
|
||||
|
||||
/* All same-site blocked: */
|
||||
{ .name="/same-site/cross-basic", .cross_origin=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_NONE },
|
||||
{ .name="/same-site/cross-basic-js", .cross_origin=TRUE, .javascript=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_NONE },
|
||||
{ .name="/same-site/cross-unsafe-method", .cross_origin=TRUE, .unsafe_method=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_NONE },
|
||||
{ .name="/same-site/cross-unsafe-method-js", .cross_origin=TRUE, .javascript=TRUE, .unsafe_method=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_NONE },
|
||||
{ .name="/same-site/cross-unsafe-method-top-level", .cross_origin=TRUE, .unsafe_method=TRUE, .top_level_nav=TRUE, .visible_policy=SOUP_SAME_SITE_POLICY_NONE },
|
||||
};
|
||||
|
||||
test_init (argc, argv, NULL);
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (same_site_tests); ++i)
|
||||
g_test_add (same_site_tests[i].name, SameSiteFixture, &same_site_tests[i],
|
||||
same_site_setup, same_site_test, same_site_teardown);
|
||||
|
||||
ret = g_test_run ();
|
||||
test_cleanup ();
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user