/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * soup-cookie-jar-db.c: database-based cookie storage * * Using danw's soup-cookie-jar-text as template * Copyright (C) 2008 Diego Escalante Urrelo * Copyright (C) 2009 Collabora Ltd. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "soup-cookie-jar-db.h" #include "soup.h" /** * SECTION:soup-cookie-jar-db * @short_description: Database-based Cookie Jar * * #SoupCookieJarDB is a #SoupCookieJar that reads cookies from and * writes them to a sqlite database in the new Mozilla format. * * (This is identical to SoupCookieJarSqlite in * libsoup-gnome; it has just been moved into libsoup proper, and * renamed to avoid conflicting.) **/ enum { PROP_0, PROP_FILENAME, LAST_PROP }; typedef struct { char *filename; sqlite3 *db; } SoupCookieJarDBPrivate; G_DEFINE_TYPE_WITH_PRIVATE (SoupCookieJarDB, soup_cookie_jar_db, SOUP_TYPE_COOKIE_JAR) static void load (SoupCookieJar *jar); static void soup_cookie_jar_db_init (SoupCookieJarDB *db) { } static void soup_cookie_jar_db_finalize (GObject *object) { SoupCookieJarDBPrivate *priv = soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (object)); g_free (priv->filename); g_clear_pointer (&priv->db, sqlite3_close); G_OBJECT_CLASS (soup_cookie_jar_db_parent_class)->finalize (object); } static void soup_cookie_jar_db_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { SoupCookieJarDBPrivate *priv = soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (object)); switch (prop_id) { case PROP_FILENAME: priv->filename = g_value_dup_string (value); load (SOUP_COOKIE_JAR (object)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void soup_cookie_jar_db_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { SoupCookieJarDBPrivate *priv = soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (object)); switch (prop_id) { case PROP_FILENAME: g_value_set_string (value, priv->filename); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /** * soup_cookie_jar_db_new: * @filename: the filename to read to/write from, or %NULL * @read_only: %TRUE if @filename is read-only * * Creates a #SoupCookieJarDB. * * @filename will be read in at startup to create an initial set of * cookies. If @read_only is %FALSE, then the non-session cookies will * be written to @filename when the 'changed' signal is emitted from * the jar. (If @read_only is %TRUE, then the cookie jar will only be * used for this session, and changes made to it will be lost when the * jar is destroyed.) * * Return value: the new #SoupCookieJar * * Since: 2.42 **/ SoupCookieJar * soup_cookie_jar_db_new (const char *filename, gboolean read_only) { g_return_val_if_fail (filename != NULL, NULL); return g_object_new (SOUP_TYPE_COOKIE_JAR_DB, SOUP_COOKIE_JAR_DB_FILENAME, filename, SOUP_COOKIE_JAR_READ_ONLY, read_only, NULL); } #define QUERY_ALL "SELECT id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly FROM moz_cookies;" #define CREATE_TABLE "CREATE TABLE moz_cookies (id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT, path TEXT,expiry INTEGER, lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER)" #define QUERY_INSERT "INSERT INTO moz_cookies VALUES(NULL, %Q, %Q, %Q, %Q, %d, NULL, %d, %d);" #define QUERY_DELETE "DELETE FROM moz_cookies WHERE name=%Q AND host=%Q;" enum { COL_ID, COL_NAME, COL_VALUE, COL_HOST, COL_PATH, COL_EXPIRY, COL_LAST_ACCESS, COL_SECURE, COL_HTTP_ONLY, N_COL, }; static int callback (void *data, int argc, char **argv, char **colname) { SoupCookie *cookie = NULL; SoupCookieJar *jar = SOUP_COOKIE_JAR (data); char *name, *value, *host, *path; gulong expire_time; time_t now; int max_age; gboolean http_only = FALSE, secure = FALSE; now = time (NULL); name = argv[COL_NAME]; value = argv[COL_VALUE]; host = argv[COL_HOST]; path = argv[COL_PATH]; expire_time = strtoul (argv[COL_EXPIRY], NULL, 10); if (now >= expire_time) return 0; max_age = (expire_time - now <= G_MAXINT ? expire_time - now : G_MAXINT); http_only = (g_strcmp0 (argv[COL_HTTP_ONLY], "1") == 0); secure = (g_strcmp0 (argv[COL_SECURE], "1") == 0); cookie = soup_cookie_new (name, value, host, path, max_age); if (secure) soup_cookie_set_secure (cookie, TRUE); if (http_only) soup_cookie_set_http_only (cookie, TRUE); soup_cookie_jar_add_cookie (jar, cookie); return 0; } static void try_create_table (sqlite3 *db) { char *error = NULL; if (sqlite3_exec (db, CREATE_TABLE, NULL, NULL, &error)) { g_warning ("Failed to execute query: %s", error); sqlite3_free (error); } } static void exec_query_with_try_create_table (sqlite3 *db, const char *sql, int (*callback)(void*,int,char**,char**), void *argument) { char *error = NULL; gboolean try_create = TRUE; try_exec: if (sqlite3_exec (db, sql, callback, argument, &error)) { if (try_create) { try_create = FALSE; try_create_table (db); sqlite3_free (error); error = NULL; goto try_exec; } else { g_warning ("Failed to execute query: %s", error); sqlite3_free (error); } } } /* Follows sqlite3 convention; returns TRUE on error */ static gboolean open_db (SoupCookieJar *jar) { SoupCookieJarDBPrivate *priv = soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (jar)); char *error = NULL; if (sqlite3_open (priv->filename, &priv->db)) { sqlite3_close (priv->db); priv->db = NULL; g_warning ("Can't open %s", priv->filename); return TRUE; } if (sqlite3_exec (priv->db, "PRAGMA synchronous = OFF; PRAGMA secure_delete = 1;", NULL, NULL, &error)) { g_warning ("Failed to execute query: %s", error); sqlite3_free (error); } return FALSE; } static void load (SoupCookieJar *jar) { SoupCookieJarDBPrivate *priv = soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (jar)); if (priv->db == NULL) { if (open_db (jar)) return; } exec_query_with_try_create_table (priv->db, QUERY_ALL, callback, jar); } static void soup_cookie_jar_db_changed (SoupCookieJar *jar, SoupCookie *old_cookie, SoupCookie *new_cookie) { SoupCookieJarDBPrivate *priv = soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (jar)); char *query; if (priv->db == NULL) { if (open_db (jar)) return; } if (old_cookie) { query = sqlite3_mprintf (QUERY_DELETE, old_cookie->name, old_cookie->domain); exec_query_with_try_create_table (priv->db, query, NULL, NULL); sqlite3_free (query); } if (new_cookie && new_cookie->expires) { gulong expires; expires = (gulong)soup_date_to_time_t (new_cookie->expires); query = sqlite3_mprintf (QUERY_INSERT, new_cookie->name, new_cookie->value, new_cookie->domain, new_cookie->path, expires, new_cookie->secure, new_cookie->http_only); exec_query_with_try_create_table (priv->db, query, NULL, NULL); sqlite3_free (query); } } static gboolean soup_cookie_jar_db_is_persistent (SoupCookieJar *jar) { return TRUE; } static void soup_cookie_jar_db_class_init (SoupCookieJarDBClass *db_class) { SoupCookieJarClass *cookie_jar_class = SOUP_COOKIE_JAR_CLASS (db_class); GObjectClass *object_class = G_OBJECT_CLASS (db_class); cookie_jar_class->is_persistent = soup_cookie_jar_db_is_persistent; cookie_jar_class->changed = soup_cookie_jar_db_changed; object_class->finalize = soup_cookie_jar_db_finalize; object_class->set_property = soup_cookie_jar_db_set_property; object_class->get_property = soup_cookie_jar_db_get_property; /** * SOUP_COOKIE_JAR_DB_FILENAME: * * Alias for the #SoupCookieJarDB:filename property. (The * cookie-storage filename.) **/ g_object_class_install_property ( object_class, PROP_FILENAME, g_param_spec_string (SOUP_COOKIE_JAR_DB_FILENAME, "Filename", "Cookie-storage filename", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); }