#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);
-static gboolean is_persistent (SoupCookieJar *jar);
-
-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
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,
}
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;
}
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 = constructed;
- object_class->finalize = finalize;
- object_class->set_property = set_property;
- object_class->get_property = get_property;
+ 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;
- jar_class->is_persistent = is_persistent;
+ jar_class->is_persistent = soup_cookie_jar_real_is_persistent;
/**
* SoupCookieJar::changed:
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);
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:
*
* 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)
/* Does nothing, obsolete */
}
-static gboolean
-is_persistent (SoupCookieJar *jar)
-{
- return FALSE;
-}
-
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);
{
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;
char *domain, *cur, *next_domain;
GSList *new_head, *cookies_to_remove = NULL, *p;
- priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+ priv = soup_cookie_jar_get_instance_private (jar);
if (!uri->host)
return NULL;
* 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.
+ * Return value: (nullable): the cookies, in string form, or %NULL if
+ * there are no cookies for @uri.
*
* Since: 2.24
**/
/**
* 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
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;
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
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;
const char *cookie)
{
SoupCookie *soup_cookie;
- SoupCookieJarPrivate *priv;
g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
g_return_if_fail (uri != NULL);
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)
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
}
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;
}
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
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);
{
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;
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);
* 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
*/
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;
}
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;