Upgrade to 2.62.2
[platform/upstream/libsoup.git] / libsoup / soup-cookie-jar.c
old mode 100644 (file)
new mode 100755 (executable)
index 2abb165..2369c8a
@@ -9,21 +9,15 @@
 #include <config.h>
 #endif
 
-#include <stdio.h>
 #include <string.h>
 
-#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"
-#include "soup-uri.h"
+#include "soup-misc-private.h"
+#include "soup.h"
 
 /**
  * SECTION:soup-cookie-jar
- * @short_description: Automatic cookie handling for #SoupSession
+ * @short_description: Automatic cookie handling for SoupSession
  *
  * A #SoupCookieJar stores #SoupCookie<!-- -->s and arrange for them
  * to be sent with the appropriate #SoupMessage<!-- -->s.
  * of long-term cookie persistence.
  **/
 
-static void soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
-static void request_queued (SoupSessionFeature *feature, SoupSession *session,
-                           SoupMessage *msg);
-static void request_started (SoupSessionFeature *feature, SoupSession *session,
-                            SoupMessage *msg, SoupSocket *socket);
-static void request_unqueued (SoupSessionFeature *feature, SoupSession *session,
-                             SoupMessage *msg);
-
-G_DEFINE_TYPE_WITH_CODE (SoupCookieJar, soup_cookie_jar, G_TYPE_OBJECT,
-                        G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
-                                               soup_cookie_jar_session_feature_init))
-
 enum {
        CHANGED,
        LAST_SIGNAL
@@ -69,17 +51,18 @@ typedef struct {
        guint serial;
        SoupCookieJarAcceptPolicy accept_policy;
 } SoupCookieJarPrivate;
-#define SOUP_COOKIE_JAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_COOKIE_JAR, SoupCookieJarPrivate))
 
-static void set_property (GObject *object, guint prop_id,
-                         const GValue *value, GParamSpec *pspec);
-static void get_property (GObject *object, guint prop_id,
-                         GValue *value, GParamSpec *pspec);
+static void soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SoupCookieJar, soup_cookie_jar, G_TYPE_OBJECT,
+                         G_ADD_PRIVATE (SoupCookieJar)
+                        G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
+                                               soup_cookie_jar_session_feature_init))
 
 static void
 soup_cookie_jar_init (SoupCookieJar *jar)
 {
-       SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
 
        priv->domains = g_hash_table_new_full (soup_str_case_hash,
                                               soup_str_case_equal,
@@ -89,17 +72,19 @@ soup_cookie_jar_init (SoupCookieJar *jar)
 }
 
 static void
-constructed (GObject *object)
+soup_cookie_jar_constructed (GObject *object)
 {
-       SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (object);
+       SoupCookieJarPrivate *priv =
+               soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
 
        priv->constructed = TRUE;
 }
 
 static void
-finalize (GObject *object)
+soup_cookie_jar_finalize (GObject *object)
 {
-       SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (object);
+       SoupCookieJarPrivate *priv =
+               soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
        GHashTableIter iter;
        gpointer key, value;
 
@@ -113,19 +98,65 @@ finalize (GObject *object)
 }
 
 static void
+soup_cookie_jar_set_property (GObject *object, guint prop_id,
+                             const GValue *value, GParamSpec *pspec)
+{
+       SoupCookieJarPrivate *priv =
+               soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
+
+       switch (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;
+       }
+}
+
+static void
+soup_cookie_jar_get_property (GObject *object, guint prop_id,
+                             GValue *value, GParamSpec *pspec)
+{
+       SoupCookieJarPrivate *priv =
+               soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
+
+       switch (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;
+       }
+}
+
+static gboolean
+soup_cookie_jar_real_is_persistent (SoupCookieJar *jar)
+{
+       return FALSE;
+}
+
+static void
 soup_cookie_jar_class_init (SoupCookieJarClass *jar_class)
 {
        GObjectClass *object_class = G_OBJECT_CLASS (jar_class);
 
-       g_type_class_add_private (jar_class, sizeof (SoupCookieJarPrivate));
+       object_class->constructed = soup_cookie_jar_constructed;
+       object_class->finalize = soup_cookie_jar_finalize;
+       object_class->set_property = soup_cookie_jar_set_property;
+       object_class->get_property = soup_cookie_jar_get_property;
 
-       object_class->constructed = constructed;
-       object_class->finalize = finalize;
-       object_class->set_property = set_property;
-       object_class->get_property = get_property;
+       jar_class->is_persistent = soup_cookie_jar_real_is_persistent;
 
        /**
-        * SoupCookieJar::changed
+        * SoupCookieJar::changed:
         * @jar: the #SoupCookieJar
         * @old_cookie: the old #SoupCookie value
         * @new_cookie: the new #SoupCookie value
@@ -144,7 +175,7 @@ soup_cookie_jar_class_init (SoupCookieJarClass *jar_class)
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (SoupCookieJarClass, changed),
                              NULL, NULL,
-                             soup_marshal_NONE__BOXED_BOXED,
+                             NULL,
                              G_TYPE_NONE, 2, 
                              SOUP_TYPE_COOKIE | G_SIGNAL_TYPE_STATIC_SCOPE,
                              SOUP_TYPE_COOKIE | G_SIGNAL_TYPE_STATIC_SCOPE);
@@ -187,55 +218,6 @@ soup_cookie_jar_class_init (SoupCookieJarClass *jar_class)
                                   G_PARAM_READWRITE));
 }
 
-static void
-soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface,
-                                     gpointer interface_data)
-{
-       feature_interface->request_queued = request_queued;
-       feature_interface->request_started = request_started;
-       feature_interface->request_unqueued = request_unqueued;
-}
-
-static void
-set_property (GObject *object, guint prop_id,
-             const GValue *value, GParamSpec *pspec)
-{
-       SoupCookieJarPrivate *priv =
-               SOUP_COOKIE_JAR_GET_PRIVATE (object);
-
-       switch (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;
-       }
-}
-
-static void
-get_property (GObject *object, guint prop_id,
-             GValue *value, GParamSpec *pspec)
-{
-       SoupCookieJarPrivate *priv =
-               SOUP_COOKIE_JAR_GET_PRIVATE (object);
-
-       switch (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;
-       }
-}
-
 /**
  * soup_cookie_jar_new:
  *
@@ -259,6 +241,10 @@ soup_cookie_jar_new (void)
  * This function exists for backward compatibility, but does not do
  * anything any more; cookie jars are saved automatically when they
  * are changed.
+ *
+ * Since: 2.24
+ *
+ * Deprecated: This is a no-op.
  */
 void
 soup_cookie_jar_save (SoupCookieJar *jar)
@@ -270,7 +256,7 @@ static void
 soup_cookie_jar_changed (SoupCookieJar *jar,
                         SoupCookie *old, SoupCookie *new)
 {
-       SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
 
        if (old && old != new)
                g_hash_table_remove (priv->serials, old);
@@ -290,7 +276,7 @@ compare_cookies (gconstpointer a, gconstpointer b, gpointer jar)
 {
        SoupCookie *ca = (SoupCookie *)a;
        SoupCookie *cb = (SoupCookie *)b;
-       SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
        int alen, blen;
        guint aserial, bserial;
 
@@ -311,41 +297,15 @@ compare_cookies (gconstpointer a, gconstpointer b, gpointer jar)
        return aserial - bserial;
 }
 
-/**
- * soup_cookie_jar_get_cookies:
- * @jar: a #SoupCookieJar
- * @uri: a #SoupURI
- * @for_http: whether or not the return value is being passed directly
- * to an HTTP operation
- *
- * Retrieves (in Cookie-header form) the list of cookies that would
- * be sent with a request to @uri.
- *
- * If @for_http is %TRUE, the return value will include cookies marked
- * "HttpOnly" (that is, cookies that the server wishes to keep hidden
- * from client-side scripting operations such as the JavaScript
- * document.cookies property). Since #SoupCookieJar sets the Cookie
- * header itself when making the actual HTTP request, you should
- * almost certainly be setting @for_http to %FALSE if you are calling
- * this.
- *
- * Return value: the cookies, in string form, or %NULL if there are no
- * cookies for @uri.
- *
- * Since: 2.24
- **/
-char *
-soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri,
-                            gboolean for_http)
+static GSList *
+get_cookies (SoupCookieJar *jar, SoupURI *uri, gboolean for_http, gboolean copy_cookies)
 {
        SoupCookieJarPrivate *priv;
        GSList *cookies, *domain_cookies;
-       char *domain, *cur, *next_domain, *result;
+       char *domain, *cur, *next_domain;
        GSList *new_head, *cookies_to_remove = NULL, *p;
 
-       g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
-       priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
-       g_return_val_if_fail (uri != NULL, NULL);
+       priv = soup_cookie_jar_get_instance_private (jar);
 
        if (!uri->host)
                return NULL;
@@ -373,7 +333,7 @@ soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri,
                                                     new_head);
                        } else if (soup_cookie_applies_to_uri (cookie, uri) &&
                                   (for_http || !cookie->http_only))
-                               cookies = g_slist_append (cookies, cookie);
+                               cookies = g_slist_append (cookies, copy_cookies ? soup_cookie_copy (cookie) : cookie);
 
                        domain_cookies = next;
                }
@@ -391,9 +351,45 @@ soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri,
        }
        g_slist_free (cookies_to_remove);
 
+       return g_slist_sort_with_data (cookies, compare_cookies, jar);
+}
+
+/**
+ * soup_cookie_jar_get_cookies:
+ * @jar: a #SoupCookieJar
+ * @uri: a #SoupURI
+ * @for_http: whether or not the return value is being passed directly
+ * to an HTTP operation
+ *
+ * Retrieves (in Cookie-header form) the list of cookies that would
+ * be sent with a request to @uri.
+ *
+ * If @for_http is %TRUE, the return value will include cookies marked
+ * "HttpOnly" (that is, cookies that the server wishes to keep hidden
+ * from client-side scripting operations such as the JavaScript
+ * document.cookies property). Since #SoupCookieJar sets the Cookie
+ * header itself when making the actual HTTP request, you should
+ * almost certainly be setting @for_http to %FALSE if you are calling
+ * this.
+ *
+ * Return value: (nullable): the cookies, in string form, or %NULL if
+ * there are no cookies for @uri.
+ *
+ * Since: 2.24
+ **/
+char *
+soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri,
+                            gboolean for_http)
+{
+       GSList *cookies;
+
+       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);
+
        if (cookies) {
-               cookies = g_slist_sort_with_data (cookies, compare_cookies, jar);
-               result = soup_cookies_to_cookie_header (cookies);
+               char *result = soup_cookies_to_cookie_header (cookies);
                g_slist_free (cookies);
 
                if (!*result) {
@@ -406,9 +402,41 @@ soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri,
 }
 
 /**
+ * soup_cookie_jar_get_cookie_list:
+ * @jar: a #SoupCookieJar
+ * @uri: a #SoupURI
+ * @for_http: whether or not the return value is being passed directly
+ * to an HTTP operation
+ *
+ * Retrieves the list of cookies that would be sent with a request to @uri
+ * as a #GSList of #SoupCookie objects.
+ *
+ * If @for_http is %TRUE, the return value will include cookies marked
+ * "HttpOnly" (that is, cookies that the server wishes to keep hidden
+ * from client-side scripting operations such as the JavaScript
+ * document.cookies property). Since #SoupCookieJar sets the Cookie
+ * header itself when making the actual HTTP request, you should
+ * almost certainly be setting @for_http to %FALSE if you are calling
+ * this.
+ *
+ * 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.40
+ **/
+GSList *
+soup_cookie_jar_get_cookie_list (SoupCookieJar *jar, SoupURI *uri, gboolean for_http)
+{
+       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);
+}
+
+/**
  * soup_cookie_jar_add_cookie:
  * @jar: a #SoupCookieJar
- * @cookie: a #SoupCookie
+ * @cookie: (transfer full): a #SoupCookie
  *
  * Adds @cookie to @jar, emitting the 'changed' signal if we are modifying
  * an existing cookie or adding a valid new cookie ('valid' means
@@ -428,7 +456,14 @@ soup_cookie_jar_add_cookie (SoupCookieJar *jar, SoupCookie *cookie)
        g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
        g_return_if_fail (cookie != NULL);
 
-       priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       /* Never accept cookies for public domains. */
+       if (!g_hostname_is_ip_address (cookie->domain) &&
+           soup_tld_domain_is_public_suffix (cookie->domain)) {
+               soup_cookie_free (cookie);
+               return;
+       }
+
+       priv = soup_cookie_jar_get_instance_private (jar);
        old_cookies = g_hash_table_lookup (priv->domains, cookie->domain);
        for (oc = old_cookies; oc; oc = oc->next) {
                old_cookie = oc->data;
@@ -475,6 +510,79 @@ soup_cookie_jar_add_cookie (SoupCookieJar *jar, SoupCookie *cookie)
        soup_cookie_jar_changed (jar, NULL, cookie);
 }
 
+static const char *
+normalize_cookie_domain (const char *domain)
+{
+       /* Trim any leading dot if present to transform the cookie
+         * domain into a valid hostname.
+         */
+       if (domain != NULL && domain[0] == '.')
+               return domain + 1;
+       return domain;
+}
+
+static gboolean
+incoming_cookie_is_third_party (SoupCookie *cookie, SoupURI *first_party)
+{
+       const char *normalized_cookie_domain;
+       const char *cookie_base_domain;
+       const char *first_party_base_domain;
+
+       if (first_party == NULL || first_party->host == NULL)
+               return TRUE;
+
+       normalized_cookie_domain = normalize_cookie_domain (cookie->domain);
+       cookie_base_domain = soup_tld_get_base_domain (normalized_cookie_domain, NULL);
+       if (cookie_base_domain == NULL)
+               cookie_base_domain = cookie->domain;
+
+       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);
+}
+
+/**
+ * soup_cookie_jar_add_cookie_with_first_party:
+ * @jar: a #SoupCookieJar
+ * @first_party: the URI for the main document
+ * @cookie: (transfer full): a #SoupCookie
+ *
+ * Adds @cookie to @jar, emitting the 'changed' signal if we are modifying
+ * an existing cookie or adding a valid new cookie ('valid' means
+ * that the cookie's expire date is not in the past).
+ *
+ * @first_party will be used to reject cookies coming from third party
+ * resources in case such a security policy is set in the @jar.
+ *
+ * @cookie will be 'stolen' by the jar, so don't free it afterwards.
+ *
+ * Since: 2.40
+ **/
+void
+soup_cookie_jar_add_cookie_with_first_party (SoupCookieJar *jar, SoupURI *first_party, SoupCookie *cookie)
+{
+       SoupCookieJarPrivate *priv;
+
+       g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
+       g_return_if_fail (first_party != NULL);
+       g_return_if_fail (cookie != NULL);
+
+       priv = soup_cookie_jar_get_instance_private (jar);
+       if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER) {
+               soup_cookie_free (cookie);
+               return;
+       }
+
+       if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS ||
+           !incoming_cookie_is_third_party (cookie, first_party)) {
+               /* will steal or free soup_cookie */
+               soup_cookie_jar_add_cookie (jar, cookie);
+       } else {
+               soup_cookie_free (cookie);
+       }
+}
+
 /**
  * soup_cookie_jar_set_cookie:
  * @jar: a #SoupCookieJar
@@ -506,7 +614,7 @@ soup_cookie_jar_set_cookie (SoupCookieJar *jar, SoupURI *uri,
        if (!uri->host)
                return;
 
-       priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       priv = soup_cookie_jar_get_instance_private (jar);
        if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
                return;
 
@@ -540,7 +648,6 @@ soup_cookie_jar_set_cookie_with_first_party (SoupCookieJar *jar,
                                             const char *cookie)
 {
        SoupCookie *soup_cookie;
-       SoupCookieJarPrivate *priv;
 
        g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
        g_return_if_fail (uri != NULL);
@@ -550,27 +657,16 @@ soup_cookie_jar_set_cookie_with_first_party (SoupCookieJar *jar,
        if (!uri->host)
                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);
-               }
-       }
+       if (soup_cookie)
+               soup_cookie_jar_add_cookie_with_first_party (jar, first_party, 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);
+       SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
        GSList *new_cookies, *nc;
 
        if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
@@ -581,8 +677,7 @@ process_set_cookie_header (SoupMessage *msg, gpointer user_data)
                SoupURI *first_party = soup_message_get_first_party (msg);
                
                if ((priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY &&
-                    first_party != NULL && first_party->host &&
-                    soup_cookie_domain_matches (nc->data, first_party->host)) ||
+                    !incoming_cookie_is_third_party (nc->data, first_party)) ||
                    priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
                        soup_cookie_jar_add_cookie (jar, nc->data);
                else
@@ -592,18 +687,7 @@ process_set_cookie_header (SoupMessage *msg, gpointer user_data)
 }
 
 static void
-request_queued (SoupSessionFeature *feature, SoupSession *session,
-               SoupMessage *msg)
-{
-       soup_message_add_header_handler (msg, "got-headers",
-                                        "Set-Cookie",
-                                        G_CALLBACK (process_set_cookie_header),
-                                        feature);
-}
-
-static void
-request_started (SoupSessionFeature *feature, SoupSession *session,
-                SoupMessage *msg, SoupSocket *socket)
+msg_starting_cb (SoupMessage *msg, gpointer feature)
 {
        SoupCookieJar *jar = SOUP_COOKIE_JAR (feature);
        char *cookies;
@@ -618,12 +702,36 @@ request_started (SoupSessionFeature *feature, SoupSession *session,
 }
 
 static void
-request_unqueued (SoupSessionFeature *feature, SoupSession *session,
-                 SoupMessage *msg)
+soup_cookie_jar_request_queued (SoupSessionFeature *feature,
+                               SoupSession *session,
+                               SoupMessage *msg)
+{
+       g_signal_connect (msg, "starting",
+                         G_CALLBACK (msg_starting_cb),
+                         feature);
+
+       soup_message_add_header_handler (msg, "got-headers",
+                                        "Set-Cookie",
+                                        G_CALLBACK (process_set_cookie_header),
+                                        feature);
+}
+
+static void
+soup_cookie_jar_request_unqueued (SoupSessionFeature *feature,
+                                 SoupSession *session,
+                                 SoupMessage *msg)
 {
        g_signal_handlers_disconnect_by_func (msg, process_set_cookie_header, feature);
 }
 
+static void
+soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface,
+                                     gpointer interface_data)
+{
+       feature_interface->request_queued = soup_cookie_jar_request_queued;
+       feature_interface->request_unqueued = soup_cookie_jar_request_unqueued;
+}
+
 /**
  * soup_cookie_jar_all_cookies:
  * @jar: a #SoupCookieJar
@@ -647,7 +755,7 @@ soup_cookie_jar_all_cookies (SoupCookieJar *jar)
 
        g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
 
-       priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       priv = soup_cookie_jar_get_instance_private (jar);
 
        g_hash_table_iter_init (&iter, priv->domains);
 
@@ -675,16 +783,13 @@ soup_cookie_jar_delete_cookie (SoupCookieJar *jar,
 {
        SoupCookieJarPrivate *priv;
        GSList *cookies, *p;
-       char *domain;
 
        g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
        g_return_if_fail (cookie != NULL);
 
-       priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       priv = soup_cookie_jar_get_instance_private (jar);
 
-       domain = g_strdup (cookie->domain);
-
-       cookies = g_hash_table_lookup (priv->domains, domain);
+       cookies = g_hash_table_lookup (priv->domains, cookie->domain);
        if (cookies == NULL)
                return;
 
@@ -693,7 +798,7 @@ soup_cookie_jar_delete_cookie (SoupCookieJar *jar,
                if (soup_cookie_equal (cookie, c)) {
                        cookies = g_slist_delete_link (cookies, p);
                        g_hash_table_insert (priv->domains,
-                                            domain,
+                                            g_strdup (cookie->domain),
                                             cookies);
                        soup_cookie_jar_changed (jar, c, NULL);
                        soup_cookie_free (c);
@@ -718,6 +823,9 @@ soup_cookie_jar_delete_cookie (SoupCookieJar *jar,
  * 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.
  *
+ * The policy for accepting or rejecting cookies returned in
+ * responses.
+ *
  * Since: 2.30
  */
 
@@ -738,7 +846,7 @@ soup_cookie_jar_get_accept_policy (SoupCookieJar *jar)
 
        g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), SOUP_COOKIE_JAR_ACCEPT_ALWAYS);
 
-       priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       priv = soup_cookie_jar_get_instance_private (jar);
        return priv->accept_policy;
 }
 
@@ -759,10 +867,28 @@ soup_cookie_jar_set_accept_policy (SoupCookieJar *jar,
 
        g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
 
-       priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+       priv = soup_cookie_jar_get_instance_private (jar);
 
        if (priv->accept_policy != policy) {
                priv->accept_policy = policy;
                g_object_notify (G_OBJECT (jar), SOUP_COOKIE_JAR_ACCEPT_POLICY);
        }
 }
+
+/**
+ * soup_cookie_jar_is_persistent:
+ * @jar: a #SoupCookieJar
+ *
+ * Gets whether @jar stores cookies persistenly.
+ *
+ * Returns: %TRUE if @jar storage is persistent or %FALSE otherwise.
+ *
+ * Since: 2.40
+ **/
+gboolean
+soup_cookie_jar_is_persistent (SoupCookieJar *jar)
+{
+       g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), FALSE);
+
+       return SOUP_COOKIE_JAR_GET_CLASS (jar)->is_persistent (jar);
+}