Commit 550e835f authored by Carlos Garcia Campos's avatar Carlos Garcia Campos Committed by Patrick Griffis

soup-cookie-jar: Add SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY

This new policy matches the Safari behavior when ITP is disabled and
third-party cookies are blocked. The SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
policy does not allow subresources to set cookies unless they match the
domain of the main resource. The new policy makes an exception for domains
that have previously stored cookies (when being visited).

This patch was written by Michael Catanzaro, but it changed the behavior
of SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY. I just updated it to add a new
policy instead.
parent b82aff3e
Pipeline #194139 passed with stage
in 1 minute and 4 seconds
......@@ -522,12 +522,20 @@ normalize_cookie_domain (const char *domain)
}
static gboolean
incoming_cookie_is_third_party (SoupCookie *cookie, SoupURI *first_party)
incoming_cookie_is_third_party (SoupCookieJar *jar,
SoupCookie *cookie,
SoupURI *first_party,
SoupCookieJarAcceptPolicy policy)
{
SoupCookieJarPrivate *priv;
const char *normalized_cookie_domain;
const char *cookie_base_domain;
const char *first_party_base_domain;
if (policy != SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY &&
policy != SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY)
return FALSE;
if (first_party == NULL || first_party->host == NULL)
return TRUE;
......@@ -539,7 +547,21 @@ incoming_cookie_is_third_party (SoupCookie *cookie, SoupURI *first_party)
first_party_base_domain = soup_tld_get_base_domain (first_party->host, NULL);
if (first_party_base_domain == NULL)
first_party_base_domain = first_party->host;
return !soup_host_matches_host (cookie_base_domain, first_party_base_domain);
if (soup_host_matches_host (cookie_base_domain, first_party_base_domain))
return FALSE;
if (policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
return TRUE;
/* Now we know the cookie's base domain and the first party's base domain
* are different, but for SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY
* policy we want to grandfather in any domain that's already in the jar.
* That is, we never want to block cookies from domains the user has
* previously visited directly.
*/
priv = soup_cookie_jar_get_instance_private (jar);
return !g_hash_table_lookup (priv->domains, cookie->domain);
}
/**
......@@ -582,14 +604,13 @@ soup_cookie_jar_add_cookie_full (SoupCookieJar *jar, SoupCookie *cookie, SoupURI
priv = soup_cookie_jar_get_instance_private (jar);
if (first_party != NULL) {
if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER ||
(priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY &&
incoming_cookie_is_third_party (cookie, first_party))) {
soup_cookie_free (cookie);
return;
}
}
if (first_party != NULL) {
if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER ||
incoming_cookie_is_third_party (jar, cookie, first_party, priv->accept_policy)) {
soup_cookie_free (cookie);
return;
}
}
/* Cannot set a secure cookie over http */
if (uri != NULL && !soup_uri_is_https (uri, NULL) && soup_cookie_get_secure (cookie)) {
......@@ -704,8 +725,9 @@ soup_cookie_jar_add_cookie_with_first_party (SoupCookieJar *jar, SoupURI *first_
* Adds @cookie to @jar, exactly as though it had appeared in a
* Set-Cookie header returned from a request to @uri.
*
* Keep in mind that if the #SoupCookieJarAcceptPolicy
* %SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY is set you'll need to use
* Keep in mind that if the #SoupCookieJarAcceptPolicy set is either
* %SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY or
* %SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY you'll need to use
* soup_cookie_jar_set_cookie_with_first_party(), otherwise the jar
* will have no way of knowing if the cookie is being set by a third
* party or not.
......@@ -730,7 +752,8 @@ soup_cookie_jar_set_cookie (SoupCookieJar *jar, SoupURI *uri,
if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
return;
g_return_if_fail (priv->accept_policy != SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY);
g_return_if_fail (priv->accept_policy != SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY &&
priv->accept_policy != SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY);
soup_cookie = soup_cookie_parse (cookie, uri);
if (soup_cookie) {
......@@ -942,6 +965,19 @@ soup_cookie_jar_delete_cookie (SoupCookieJar *jar,
* on each outgoing #SoupMessage, setting the #SoupURI of the main
* document. If no first party is set in a message when this policy is
* in effect, cookies will be assumed to be third party by default.
* @SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY: accept all cookies
* set by the main document loaded in the application using libsoup, and
* from domains that have previously set at least one cookie when loaded
* as the main document. An example of the most common case, web browsers,
* would be: if http://www.example.com is the page loaded, accept all
* cookies set by example.com, but if a resource from http://www.third-party.com
* is loaded from that page, reject any cookie that it could try to
* set unless it already has a cookie in the cookie jar. For libsoup to
* be able to tell apart first party cookies from the rest, the
* application must call soup_message_set_first_party() on each outgoing
* #SoupMessage, setting the #SoupURI of the main document. If no first
* party is set in a message when this policy is in effect, cookies will
* be assumed to be third party by default. Since 2.72.
*
* The policy for accepting or rejecting cookies returned in
* responses.
......
......@@ -44,7 +44,8 @@ typedef struct {
typedef enum {
SOUP_COOKIE_JAR_ACCEPT_ALWAYS,
SOUP_COOKIE_JAR_ACCEPT_NEVER,
SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY,
SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY
} SoupCookieJarAcceptPolicy;
SOUP_AVAILABLE_IN_2_24
......
......@@ -36,13 +36,16 @@ server_callback (SoupServer *server, SoupMessage *msg,
typedef struct {
SoupCookieJarAcceptPolicy policy;
gboolean try_third_party_again;
int n_cookies;
} CookiesForPolicy;
static const CookiesForPolicy validResults[] = {
{ SOUP_COOKIE_JAR_ACCEPT_ALWAYS, 2 },
{ SOUP_COOKIE_JAR_ACCEPT_NEVER, 0 },
{ SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY, 1 }
{ SOUP_COOKIE_JAR_ACCEPT_ALWAYS, FALSE, 2 },
{ SOUP_COOKIE_JAR_ACCEPT_NEVER, FALSE, 0 },
{ SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY, FALSE, 1 },
{ SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY, FALSE, 1 },
{ SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY, TRUE, 2 }
};
static void
......@@ -62,13 +65,6 @@ do_cookies_accept_policy_test (void)
for (i = 0; i < G_N_ELEMENTS (validResults); i++) {
soup_cookie_jar_set_accept_policy (jar, validResults[i].policy);
uri = soup_uri_new_with_base (first_party_uri, "/index.html");
msg = soup_message_new_from_uri ("GET", uri);
soup_message_set_first_party (msg, first_party_uri);
soup_session_send_message (session, msg);
soup_uri_free (uri);
g_object_unref (msg);
/* We can't use two servers due to limitations in
* test_server, so let's swap first and third party here
* to simulate a cookie coming from a third party.
......@@ -80,6 +76,22 @@ do_cookies_accept_policy_test (void)
soup_uri_free (uri);
g_object_unref (msg);
uri = soup_uri_new_with_base (first_party_uri, "/index.html");
msg = soup_message_new_from_uri ("GET", uri);
soup_message_set_first_party (msg, first_party_uri);
soup_session_send_message (session, msg);
soup_uri_free (uri);
g_object_unref (msg);
if (validResults[i].try_third_party_again) {
uri = soup_uri_new_with_base (first_party_uri, "/foo.jpg");
msg = soup_message_new_from_uri ("GET", uri);
soup_message_set_first_party (msg, third_party_uri);
soup_session_send_message (session, msg);
soup_uri_free (uri);
g_object_unref (msg);
}
l = soup_cookie_jar_all_cookies (jar);
g_assert_cmpint (g_slist_length (l), ==, validResults[i].n_cookies);
......@@ -138,69 +150,86 @@ do_cookies_subdomain_policy_test (void)
g_assert_cmpint (g_slist_length (cookies), ==, 2);
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
/* Now, we allow the "third-party" to set one cookie as the
* first party. Three cookies in the jar.
*/
soup_cookie_jar_set_cookie_with_first_party (jar, third_party_uri, third_party_uri, "4=foo");
cookies = soup_cookie_jar_all_cookies (jar);
g_assert_cmpint (g_slist_length (cookies), ==, 3);
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
/* Third-party cookie should now be allowed by grandfathering, though
* it was blocked before on the same URL. Four cookies in the jar.
*/
soup_cookie_jar_set_accept_policy (jar, SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY);
soup_cookie_jar_set_cookie_with_first_party (jar, third_party_uri, uri1, "5=foo");
cookies = soup_cookie_jar_all_cookies (jar);
g_assert_cmpint (g_slist_length (cookies), ==, 4);
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
/* Now some Domain attribute tests.*/
soup_cookie_jar_set_accept_policy (jar, SOUP_COOKIE_JAR_ACCEPT_ALWAYS);
/* The cookie must be rejected if the Domain is not an appropriate
* match for the URI. Still two cookies in the jar.
* match for the URI. Still four cookies in the jar.
*/
soup_cookie_jar_set_cookie (jar, uri1, "4=foo; Domain=gitlab.gnome.org");
soup_cookie_jar_set_cookie (jar, uri1, "6=foo; Domain=gitlab.gnome.org");
cookies = soup_cookie_jar_all_cookies (jar);
g_assert_cmpint (g_slist_length (cookies), ==, 2);
g_assert_cmpint (g_slist_length (cookies), ==, 4);
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
/* Now the Domain is an appropriate match. Three cookies in the jar. */
soup_cookie_jar_set_cookie (jar, uri1, "5=foo; Domain=gnome.org");
/* Now the Domain is an appropriate match. Five cookies in the jar. */
soup_cookie_jar_set_cookie (jar, uri1, "7=foo; Domain=gnome.org");
cookies = soup_cookie_jar_all_cookies (jar);
g_assert_cmpint (g_slist_length (cookies), ==, 3);
g_assert_cmpint (g_slist_length (cookies), ==, 5);
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
/* A leading dot in the domain property should not affect things.
* This cookie should be accepted. Four cookies in the jar.
* This cookie should be accepted. Six cookies in the jar.
*/
soup_cookie_jar_set_cookie (jar, uri1, "6=foo; Domain=.www.gnome.org");
soup_cookie_jar_set_cookie (jar, uri1, "8=foo; Domain=.www.gnome.org");
cookies = soup_cookie_jar_all_cookies (jar);
g_assert_cmpint (g_slist_length (cookies), ==, 4);
g_assert_cmpint (g_slist_length (cookies), ==, 6);
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
/* The cookie must be rejected if the Domain ends in a trailing dot
* but the uri doesn't.
*/
soup_cookie_jar_set_cookie (jar, uri1, "7=foo; Domain=www.gnome.org.");
soup_cookie_jar_set_cookie (jar, uri1, "9=foo; Domain=www.gnome.org.");
cookies = soup_cookie_jar_all_cookies (jar);
g_assert_cmpint (g_slist_length (cookies), ==, 4);
g_assert_cmpint (g_slist_length (cookies), ==, 6);
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
/* The cookie should be accepted if both Domain and URI end with a trailing
* dot and they are a match. Five cookies in the jar.
* dot and they are a match. Seven cookies in the jar.
*/
soup_cookie_jar_set_cookie (jar, uri3, "8=foo; Domain=gnome.org.");
soup_cookie_jar_set_cookie (jar, uri3, "10=foo; Domain=gnome.org.");
cookies = soup_cookie_jar_all_cookies (jar);
g_assert_cmpint (g_slist_length (cookies), ==, 5);
g_assert_cmpint (g_slist_length (cookies), ==, 7);
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
/* The cookie should be rejected if URI has trailing dot but Domain doesn't.
* Five cookies in the jar.
* Seven cookies in the jar.
*/
soup_cookie_jar_set_cookie (jar, uri3, "9=foo; Domain=gnome.org");
soup_cookie_jar_set_cookie (jar, uri3, "11=foo; Domain=gnome.org");
cookies = soup_cookie_jar_all_cookies (jar);
g_assert_cmpint (g_slist_length (cookies), ==, 5);
g_assert_cmpint (g_slist_length (cookies), ==, 7);
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
/* It should not be possible to set a cookie for a TLD. Still five
/* It should not be possible to set a cookie for a TLD. Still seven
* cookies in the jar.
*/
soup_cookie_jar_set_cookie (jar, uri1, "10=foo; Domain=.org");
soup_cookie_jar_set_cookie (jar, uri1, "12=foo; Domain=.org");
cookies = soup_cookie_jar_all_cookies (jar);
g_assert_cmpint (g_slist_length (cookies), ==, 5);
g_assert_cmpint (g_slist_length (cookies), ==, 7);
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
/* It should still not be possible to set a cookie for a TLD, even if
* we are tricksy and have a trailing dot. Still only five cookies.
* we are tricksy and have a trailing dot. Still only seven cookies.
*/
soup_cookie_jar_set_cookie (jar, uri3, "11=foo; Domain=.org.");
soup_cookie_jar_set_cookie (jar, uri3, "13=foo; Domain=.org.");
cookies = soup_cookie_jar_all_cookies (jar);
g_assert_cmpint (g_slist_length (cookies), ==, 5);
g_assert_cmpint (g_slist_length (cookies), ==, 7);
g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
soup_uri_free (uri1);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment