Implement acceptance policies in SoupCookieJar
authorXan Lopez <xan@gnome.org>
Thu, 28 Jan 2010 16:31:08 +0000 (18:31 +0200)
committerXan Lopez <xan@gnome.org>
Wed, 3 Feb 2010 19:13:17 +0000 (21:13 +0200)
Through the "accept-policy" property in the jar we can now set one
among three predetermined policies: accept all cookies
(SOUP_COOKIE_JAR_ACCEPT_ALWAYS), accept none
(SOUP_COOKIE_JAR_ACCEPT_NEVER) and only accept cookies set by the main
document we are loading (SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY).

A new property, "first-party", is added to SoupMessage so that the
application using libsoup can inform us of what is the main document
URI for each message that is requested.

libsoup/soup-cookie-jar.c
libsoup/soup-cookie-jar.h
libsoup/soup-cookie.c
libsoup/soup-cookie.h
libsoup/soup-message-private.h
libsoup/soup-message.c
libsoup/soup-message.h
tests/Makefile.am
tests/cookies-test.c [new file with mode: 0644]

index e5a80a6..8f0e62a 100644 (file)
@@ -15,6 +15,7 @@
 #include "soup-cookie.h"
 #include "soup-cookie-jar.h"
 #include "soup-date.h"
+#include "soup-enum-types.h"
 #include "soup-marshal.h"
 #include "soup-message.h"
 #include "soup-session-feature.h"
@@ -57,6 +58,7 @@ enum {
        PROP_0,
 
        PROP_READ_ONLY,
+       PROP_ACCEPT_POLICY,
 
        LAST_PROP
 };
@@ -65,6 +67,7 @@ typedef struct {
        gboolean constructed, read_only;
        GHashTable *domains, *serials;
        guint serial;
+       SoupCookieJarAcceptPolicy accept_policy;
 } SoupCookieJarPrivate;
 #define SOUP_COOKIE_JAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_COOKIE_JAR, SoupCookieJarPrivate))
 
@@ -82,6 +85,7 @@ soup_cookie_jar_init (SoupCookieJar *jar)
                                               soup_str_case_equal,
                                               g_free, NULL);
        priv->serials = g_hash_table_new (NULL, NULL);
+       priv->accept_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
 }
 
 static void
@@ -158,6 +162,20 @@ soup_cookie_jar_class_init (SoupCookieJarClass *jar_class)
                                      "Whether or not the cookie jar is read-only",
                                      FALSE,
                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+       /**
+        * SOUP_COOKIE_JAR_ACCEPT_POLICY:
+        *
+        * Alias for the #SoupCookieJar:accept-policy property.
+        **/
+       g_object_class_install_property (
+               object_class, PROP_ACCEPT_POLICY,
+               g_param_spec_enum (SOUP_COOKIE_JAR_ACCEPT_POLICY,
+                                  "Accept-policy",
+                                  "The policy the jar should follow to accept or reject cookies",
+                                  SOUP_TYPE_COOKIE_JAR_ACCEPT_POLICY,
+                                  SOUP_COOKIE_JAR_ACCEPT_ALWAYS,
+                                  G_PARAM_READWRITE));
 }
 
 static void
@@ -180,6 +198,9 @@ set_property (GObject *object, guint prop_id,
        case PROP_READ_ONLY:
                priv->read_only = g_value_get_boolean (value);
                break;
+       case PROP_ACCEPT_POLICY:
+               priv->accept_policy = g_value_get_enum (value);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -197,6 +218,9 @@ get_property (GObject *object, guint prop_id,
        case PROP_READ_ONLY:
                g_value_set_boolean (value, priv->read_only);
                break;
+       case PROP_ACCEPT_POLICY:
+               g_value_set_enum (value, priv->accept_policy);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -443,6 +467,12 @@ soup_cookie_jar_add_cookie (SoupCookieJar *jar, SoupCookie *cookie)
  * 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
+ * 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.
+ *
  * Since: 2.24
  **/
 void
@@ -450,6 +480,7 @@ soup_cookie_jar_set_cookie (SoupCookieJar *jar, SoupURI *uri,
                            const char *cookie)
 {
        SoupCookie *soup_cookie;
+       SoupCookieJarPrivate *priv;
 
        g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
        g_return_if_fail (uri != NULL);
@@ -458,6 +489,12 @@ soup_cookie_jar_set_cookie (SoupCookieJar *jar, SoupURI *uri,
        if (!SOUP_URI_VALID_FOR_HTTP (uri))
                return;
 
+       priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
+               return;
+
+       g_return_if_fail (priv->accept_policy != SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY);
+
        soup_cookie = soup_cookie_parse (cookie, uri);
        if (soup_cookie) {
                /* will steal or free soup_cookie */
@@ -465,15 +502,78 @@ soup_cookie_jar_set_cookie (SoupCookieJar *jar, SoupURI *uri,
        }
 }
 
+/**
+ * soup_cookie_jar_set_cookie_with_first_party:
+ * @jar: a #SoupCookieJar
+ * @uri: the URI setting the cookie
+ * @first_party: the URI for the main document
+ * @cookie: the stringified cookie to set
+ *
+ * Adds @cookie to @jar, exactly as though it had appeared in a
+ * Set-Cookie header returned from a request to @uri. @first_party
+ * will be used to reject cookies coming from third party resources in
+ * case such a security policy is set in the @jar.
+ *
+ * Since: 2.30
+ **/
+void
+soup_cookie_jar_set_cookie_with_first_party (SoupCookieJar *jar,
+                                            SoupURI *uri,
+                                            SoupURI *first_party,
+                                            const char *cookie)
+{
+       SoupCookie *soup_cookie;
+       SoupCookieJarPrivate *priv;
+
+       g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
+       g_return_if_fail (uri != NULL);
+       g_return_if_fail (first_party != NULL);
+       g_return_if_fail (cookie != NULL);
+
+       if (!SOUP_URI_VALID_FOR_HTTP (uri))
+               return;
+
+       priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
+               return;
+
+       soup_cookie = soup_cookie_parse (cookie, uri);
+       if (soup_cookie) {
+               if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS ||
+                   soup_cookie_domain_matches (soup_cookie, first_party->host)) {
+                       /* will steal or free soup_cookie */
+                       soup_cookie_jar_add_cookie (jar, soup_cookie);
+               } else {
+                       soup_cookie_free (soup_cookie);
+               }
+       }
+}
+
 static void
 process_set_cookie_header (SoupMessage *msg, gpointer user_data)
 {
        SoupCookieJar *jar = user_data;
+       SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
        GSList *new_cookies, *nc;
 
+       if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
+               return;
+
        new_cookies = soup_cookies_from_response (msg);
-       for (nc = new_cookies; nc; nc = nc->next)
-               soup_cookie_jar_add_cookie (jar, nc->data);
+       for (nc = new_cookies; nc; nc = nc->next) {
+               SoupURI *first_party = soup_message_get_first_party (msg);
+               
+               if (first_party == NULL &&
+                   priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
+                       continue; /* Can't check anything */
+
+               if ((priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY &&
+                    soup_cookie_domain_matches (nc->data, first_party->host)) ||
+                   priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
+                       soup_cookie_jar_add_cookie (jar, nc->data);
+               else
+                       soup_cookie_free (nc->data);
+       }
        g_slist_free (new_cookies);
 }
 
@@ -586,3 +686,65 @@ soup_cookie_jar_delete_cookie (SoupCookieJar *jar,
                }
        }
 }
+
+/**
+ * SoupCookieJarAcceptPolicy:
+ * @SOUP_COOKIE_JAR_ACCEPT_ALWAYS: accept all cookies unconditionally.
+ * @SOUP_COOKIE_JAR_ACCEPT_NEVER: reject all cookies unconditionally.
+ * @SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY: accept all cookies set by
+ * the main document loaded in the application using libsoup. 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. 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.
+ *
+**/
+
+/**
+ * soup_cookie_jar_get_accept_policy:
+ * @jar: a #SoupCookieJar
+ * 
+ * Returns: the #SoupCookieJarAcceptPolicy set in the @jar
+ *
+ * Since: 2.30
+ **/
+SoupCookieJarAcceptPolicy
+soup_cookie_jar_get_accept_policy (SoupCookieJar *jar)
+{
+       SoupCookieJarPrivate *priv;
+
+       g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), SOUP_COOKIE_JAR_ACCEPT_ALWAYS);
+
+       priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       return priv->accept_policy;
+}
+
+/**
+ * soup_cookie_jar_set_accept_policy:
+ * @jar: a #SoupCookieJar
+ * @policy: a #SoupCookieJarAcceptPolicy
+ * 
+ * Sets @policy as the cookie acceptance policy for @jar.
+ *
+ * Since: 2.30
+ **/
+void
+soup_cookie_jar_set_accept_policy (SoupCookieJar *jar,
+                                  SoupCookieJarAcceptPolicy policy)
+{
+       SoupCookieJarPrivate *priv;
+
+       g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
+
+       priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+
+       if (priv->accept_policy != policy) {
+               priv->accept_policy = policy;
+               g_object_notify (G_OBJECT (jar), SOUP_COOKIE_JAR_ACCEPT_POLICY);
+       }
+}
index 3b0c3c6..eab64bf 100644 (file)
@@ -39,29 +39,37 @@ typedef struct {
 } SoupCookieJarClass;
 
 #define SOUP_COOKIE_JAR_READ_ONLY "read-only"
+#define SOUP_COOKIE_JAR_ACCEPT_POLICY "accept-policy"
 
-GType          soup_cookie_jar_get_type      (void);
-
-SoupCookieJar *soup_cookie_jar_new           (void);
+typedef enum {
+       SOUP_COOKIE_JAR_ACCEPT_ALWAYS,
+       SOUP_COOKIE_JAR_ACCEPT_NEVER,
+       SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
+} SoupCookieJarAcceptPolicy;
 
+GType                     soup_cookie_jar_get_type                    (void);
+SoupCookieJar *           soup_cookie_jar_new                         (void);
 #ifndef LIBSOUP_DISABLE_DEPRECATED
-void           soup_cookie_jar_save          (SoupCookieJar *jar);
+void                      soup_cookie_jar_save                        (SoupCookieJar             *jar);
 #endif
-
-char          *soup_cookie_jar_get_cookies   (SoupCookieJar *jar,
-                                             SoupURI       *uri,
-                                             gboolean       for_http);
-void           soup_cookie_jar_set_cookie    (SoupCookieJar *jar,
-                                             SoupURI       *uri,
-                                             const char    *cookie);
-
-void           soup_cookie_jar_add_cookie    (SoupCookieJar *jar,
-                                             SoupCookie    *cookie);
-void           soup_cookie_jar_delete_cookie (SoupCookieJar *jar,
-                                             SoupCookie    *cookie);
-
-GSList        *soup_cookie_jar_all_cookies   (SoupCookieJar *jar);
-
+char          *           soup_cookie_jar_get_cookies                 (SoupCookieJar             *jar,
+                                                                      SoupURI                   *uri,
+                                                                      gboolean                   for_http);
+void                      soup_cookie_jar_set_cookie                  (SoupCookieJar             *jar,
+                                                                      SoupURI                   *uri,
+                                                                      const char                *cookie);
+void                      soup_cookie_jar_set_cookie_with_first_party (SoupCookieJar             *jar,
+                                                                      SoupURI                   *uri,
+                                                                      SoupURI                   *first_party,
+                                                                      const char                *cookie);
+void                      soup_cookie_jar_add_cookie                  (SoupCookieJar             *jar,
+                                                                      SoupCookie                *cookie);
+void                      soup_cookie_jar_delete_cookie               (SoupCookieJar             *jar,
+                                                                      SoupCookie                *cookie);
+GSList        *           soup_cookie_jar_all_cookies                 (SoupCookieJar             *jar);
+void                      soup_cookie_jar_set_accept_policy           (SoupCookieJar             *jar,
+                                                                      SoupCookieJarAcceptPolicy  policy);
+SoupCookieJarAcceptPolicy soup_cookie_jar_get_accept_policy           (SoupCookieJar             *jar);
 
 G_END_DECLS
 
index 8ac39dc..7f51496 100644 (file)
@@ -116,11 +116,30 @@ soup_cookie_copy (SoupCookie *cookie)
        return copy;
 }
 
-static gboolean
-domain_matches (const char *domain, const char *host)
+/**
+ * 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)
 {
        char *match;
        int dlen;
+       const char *domain;
+
+       g_return_val_if_fail (cookie != NULL, FALSE);
+       g_return_val_if_fail (host != NULL, FALSE);
+
+       domain = cookie->domain;
 
        if (!g_ascii_strcasecmp (domain, host))
                return TRUE;
@@ -288,7 +307,7 @@ parse_one_cookie (const char *header, SoupURI *origin)
        if (origin) {
                /* Sanity-check domain */
                if (cookie->domain) {
-                       if (!domain_matches (cookie->domain, origin->host)) {
+                       if (!soup_cookie_domain_matches (cookie, origin->host)) {
                                soup_cookie_free (cookie);
                                return NULL;
                        }
index 34a2aa4..8bccd90 100644 (file)
@@ -76,6 +76,9 @@ void        soup_cookies_free                   (GSList      *cookies);
 
 char       *soup_cookies_to_cookie_header       (GSList      *cookies);
 
+gboolean    soup_cookie_domain_matches          (SoupCookie  *cookie,
+                                                const char  *host);
+
 G_END_DECLS
 
 #endif /* SOUP_COOKIE_H */
index ee6221d..f279303 100644 (file)
@@ -41,6 +41,8 @@ typedef struct {
 
        GSList            *disabled_features;
        GSList            *decoders;
+
+       SoupURI           *first_party;
 } SoupMessagePrivate;
 #define SOUP_MESSAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_MESSAGE, SoupMessagePrivate))
 
index 4545e5c..5a0035f 100644 (file)
@@ -119,6 +119,7 @@ enum {
        PROP_SERVER_SIDE,
        PROP_STATUS_CODE,
        PROP_REASON_PHRASE,
+       PROP_FIRST_PARTY,
 
        LAST_PROP
 };
@@ -158,6 +159,8 @@ finalize (GObject *object)
 
        if (priv->uri)
                soup_uri_free (priv->uri);
+       if (priv->first_party)
+               soup_uri_free (priv->first_party);
        if (priv->addr)
                g_object_unref (priv->addr);
 
@@ -576,6 +579,21 @@ soup_message_class_init (SoupMessageClass *message_class)
                                     "The HTTP response reason phrase",
                                     NULL,
                                     G_PARAM_READWRITE));
+
+       /**
+        * SOUP_MESSAGE_FIRST_PARTY:
+        *
+        * Alias for the #SoupMessage:first-party property. (The
+        * #SoupURI loaded in the application when the message was
+        * queued.)
+        **/
+       g_object_class_install_property (
+               object_class, PROP_FIRST_PARTY,
+               g_param_spec_boxed (SOUP_MESSAGE_FIRST_PARTY,
+                                   "First party",
+                                   "The URI loaded in the application when the message was requested.",
+                                   SOUP_TYPE_URI,
+                                   G_PARAM_READWRITE));
 }
 
 static void
@@ -612,6 +630,9 @@ set_property (GObject *object, guint prop_id,
                soup_message_set_status_full (msg, msg->status_code,
                                              g_value_get_string (value));
                break;
+       case PROP_FIRST_PARTY:
+               soup_message_set_first_party (msg, g_value_get_boxed (value));
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -647,6 +668,9 @@ get_property (GObject *object, guint prop_id,
        case PROP_REASON_PHRASE:
                g_value_set_string (value, msg->reason_phrase);
                break;
+       case PROP_FIRST_PARTY:
+               g_value_set_boxed (value, priv->first_party);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -1661,3 +1685,55 @@ soup_message_disables_feature (SoupMessage *msg, gpointer feature)
        }
        return FALSE;
 }
+
+/**
+ * soup_message_get_first_party:
+ * @msg: a #SoupMessage
+ * 
+ * Returns: the @msg's first party #SoupURI
+ * 
+ * Since: 2.30
+ **/
+SoupURI*
+soup_message_get_first_party (SoupMessage *msg)
+{
+       SoupMessagePrivate *priv;
+
+       g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
+
+       priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+       return priv->first_party;
+}
+
+/**
+ * soup_message_set_first_party:
+ * @msg: a #SoupMessage
+ * @first_party: the #SoupURI for the @msg's first party
+ * 
+ * Sets @first_party as the main document #SoupURI for @msg. For
+ * details of when and how this is used refer to the documentation for
+ * #SoupCookieJarAcceptPolicy.
+ *
+ * Since: 2.30
+ **/
+void
+soup_message_set_first_party (SoupMessage *msg,
+                             SoupURI     *first_party)
+{
+       SoupMessagePrivate *priv;
+
+       g_return_if_fail (SOUP_IS_MESSAGE (msg));
+       g_return_if_fail (first_party != NULL);
+
+       priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+
+       if (priv->first_party) {
+               if (soup_uri_equal (priv->first_party, first_party))
+                       return;
+
+               soup_uri_free (priv->first_party);
+       }
+
+       priv->first_party = soup_uri_copy (first_party);
+       g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_FIRST_PARTY);
+}
index 4fc9122..2638620 100644 (file)
@@ -67,6 +67,7 @@ GType soup_message_get_type (void);
 #define SOUP_MESSAGE_SERVER_SIDE   "server-side"
 #define SOUP_MESSAGE_STATUS_CODE   "status-code"
 #define SOUP_MESSAGE_REASON_PHRASE "reason-phrase"
+#define SOUP_MESSAGE_FIRST_PARTY   "first-party"
 
 SoupMessage   *soup_message_new                 (const char        *method,
                                                 const char        *uri_string);
@@ -100,6 +101,9 @@ void             soup_message_set_uri             (SoupMessage       *msg,
                                                   SoupURI           *uri);
 SoupAddress     *soup_message_get_address         (SoupMessage       *msg);
 
+SoupURI         *soup_message_get_first_party     (SoupMessage       *msg);
+void             soup_message_set_first_party     (SoupMessage       *msg,
+                                                  SoupURI           *first_party);
 typedef enum {
        SOUP_MESSAGE_NO_REDIRECT      = (1 << 1),
 #ifndef LIBSOUP_DISABLE_DEPRECATED
index 194d151..b69d27a 100644 (file)
@@ -17,6 +17,7 @@ noinst_PROGRAMS =     \
        coding-test     \
        context-test    \
        continue-test   \
+       cookies-test    \
        date            \
        dns             \
        forms-test      \
@@ -33,7 +34,7 @@ noinst_PROGRAMS =     \
        timeout-test    \
        uri-parsing     \
        $(CURL_TESTS)   \
-       $(APACHE_TESTS) \
+       $(APACHE_TESTS) \
        $(SSL_TESTS)    \
        $(XMLRPC_TESTS)
 
@@ -44,6 +45,7 @@ chunk_test_SOURCES = chunk-test.c $(TEST_SRCS)
 coding_test_SOURCES = coding-test.c $(TEST_SRCS)
 context_test_SOURCES = context-test.c $(TEST_SRCS)
 continue_test_SOURCES = continue-test.c $(TEST_SRCS)
+cookies_test_SOURCES = cookies-test.c $(TEST_SRCS)
 date_SOURCES = date.c $(TEST_SRCS)
 dns_SOURCES = dns.c
 forms_test_SOURCES = forms-test.c $(TEST_SRCS)
@@ -88,6 +90,7 @@ TESTS =                       \
        coding-test     \
        context-test    \
        continue-test   \
+       cookies-test    \
        date            \
        header-parsing  \
        misc-test       \
@@ -97,7 +100,7 @@ TESTS =                      \
        streaming-test  \
        timeout-test    \
        uri-parsing     \
-       $(APACHE_TESTS) \
+       $(APACHE_TESTS) \
        $(CURL_TESTS)   \
        $(SSL_TESTS)    \
        $(XMLRPC_TESTS)
diff --git a/tests/cookies-test.c b/tests/cookies-test.c
new file mode 100644 (file)
index 0000000..4e0c4d1
--- /dev/null
@@ -0,0 +1,119 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ */
+
+#include <glib.h>
+#include <libsoup/soup.h>
+
+#include "test-utils.h"
+
+SoupServer *server;
+SoupURI *first_party_uri, *third_party_uri;
+const char *first_party = "http://127.0.0.1/";
+const char *third_party = "http://localhost/";
+
+static void
+server_callback (SoupServer *server, SoupMessage *msg,
+                const char *path, GHashTable *query,
+                SoupClientContext *context, gpointer data)
+{
+    if (g_str_equal(path, "/index.html"))
+       soup_message_headers_replace (msg->response_headers,
+                                     "Set-Cookie",
+                                     "foo=bar");
+    else if (g_str_equal (path, "/foo.jpg"))
+       soup_message_headers_replace (msg->response_headers,
+                                     "Set-Cookie",
+                                     "baz=qux");
+    else
+       g_return_if_reached ();
+
+    soup_message_set_status (msg, SOUP_STATUS_OK);
+}
+
+typedef struct {
+    SoupCookieJarAcceptPolicy policy;
+    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 }
+};
+
+static void
+do_cookies_accept_policy_test (void)
+{
+       SoupSession *session;
+       SoupMessage *msg;
+       SoupURI *uri;
+       SoupCookieJar *jar;
+       GSList *l, *p;
+       int i;
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
+       soup_session_add_feature_by_type (session, SOUP_TYPE_COOKIE_JAR);
+       jar = SOUP_COOKIE_JAR (soup_session_get_feature (session, SOUP_TYPE_COOKIE_JAR));
+
+       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 to 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.
+            */
+           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);
+           if (g_slist_length (l) < validResults[i].n_cookies) {
+                   debug_printf (1, " accepted less cookies than it should have\n");
+                   errors++;
+           } else if (g_slist_length (l) > validResults[i].n_cookies) {
+                   debug_printf (1, " accepted more cookies than it should have\n");
+                   errors++;
+           }
+
+           for (p = l; p; p = p->next)
+               soup_cookie_jar_delete_cookie (jar, p->data);
+
+           g_slist_free (l);
+       }
+
+       soup_test_session_abort_unref (session);
+}
+
+int
+main (int argc, char **argv)
+{
+       test_init (argc, argv, NULL);
+
+       server = soup_test_server_new (TRUE);
+       soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
+       first_party_uri = soup_uri_new (first_party);
+       third_party_uri = soup_uri_new (third_party);
+       soup_uri_set_port (first_party_uri, soup_server_get_port (server));
+       soup_uri_set_port (third_party_uri, soup_server_get_port (server));
+
+       do_cookies_accept_policy_test ();
+
+       soup_uri_free (first_party_uri);
+       soup_uri_free (third_party_uri);
+
+       test_cleanup ();
+
+       return errors != 0;
+}