[libsoup/carlosgc/grandfathered-third-party] soup-cookie-jar: Add SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY



commit ec42aabe36f5f2be4299f879a64d125b85ba1154
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Sat Jul 4 14:02:47 2020 +0200

    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 been previously 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.

 libsoup/soup-cookie-jar.c | 62 ++++++++++++++++++++++++-------
 libsoup/soup-cookie-jar.h |  3 +-
 tests/cookies-test.c      | 95 +++++++++++++++++++++++++++++++----------------
 3 files changed, 113 insertions(+), 47 deletions(-)
---
diff --git a/libsoup/soup-cookie-jar.c b/libsoup/soup-cookie-jar.c
index 7feaff34..3f420150 100644
--- a/libsoup/soup-cookie-jar.c
+++ b/libsoup/soup-cookie-jar.c
@@ -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 been previously 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.
diff --git a/libsoup/soup-cookie-jar.h b/libsoup/soup-cookie-jar.h
index 9161cd46..d7b2b4c2 100644
--- a/libsoup/soup-cookie-jar.h
+++ b/libsoup/soup-cookie-jar.h
@@ -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
diff --git a/tests/cookies-test.c b/tests/cookies-test.c
index d25da0e9..2e2a54fc 100644
--- a/tests/cookies-test.c
+++ b/tests/cookies-test.c
@@ -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);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]