From f5447b921f8ff7796a04bb9190c62973d9ff3a06 Mon Sep 17 00:00:00 2001 From: Alexander Butenko Date: Sat, 8 Oct 2011 16:38:29 -0400 Subject: [PATCH] Implement sqlite3 cookie storage backend From now on cookies are stored in an sqlite3 database, existing cookies are imported on first startup. --- katze/katze-http-cookies-sqlite.c | 298 ++++++++++++++++++++++++++++++ katze/katze-http-cookies-sqlite.h | 42 +++++ katze/katze.h | 1 + midori/main.c | 21 ++- 4 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 katze/katze-http-cookies-sqlite.c create mode 100644 katze/katze-http-cookies-sqlite.h diff --git a/katze/katze-http-cookies-sqlite.c b/katze/katze-http-cookies-sqlite.c new file mode 100644 index 00000000..be3202ed --- /dev/null +++ b/katze/katze-http-cookies-sqlite.c @@ -0,0 +1,298 @@ +/* + Copyright (C) 2008-2010 Christian Dywan + Copyright (C) 2011 Alexander Butenko + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See the file COPYING for the full license text. +*/ + +#if HAVE_CONFIG_H + #include +#endif + +#include "katze-http-cookies-sqlite.h" + +#include +#ifdef HAVE_UNISTD_H + #include +#endif +#include +#include +#include +#include +#include + +#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, +}; + +struct _KatzeHttpCookiesSqlite +{ + GObject parent_instance; + gchar* filename; + SoupCookieJar* jar; + sqlite3 *db; + guint counter; +}; + +struct _KatzeHttpCookiesSqliteClass +{ + GObjectClass parent_class; +}; + +static void +katze_http_cookies_sqlite_session_feature_iface_init (SoupSessionFeatureInterface *iface, + gpointer data); + +G_DEFINE_TYPE_WITH_CODE (KatzeHttpCookiesSqlite, katze_http_cookies_sqlite, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, + katze_http_cookies_sqlite_session_feature_iface_init)); + +/* Cookie jar saving into sqlite database + Copyright (C) 2008 Diego Escalante Urrelo + Copyright (C) 2009 Collabora Ltd. + Mostly copied from libSoup 2.30, coding style retained */ + +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); + } + } +} + +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; + gint64 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 = g_ascii_strtoull (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; +} + +/* Follows sqlite3 convention; returns TRUE on error */ +static gboolean +katze_http_cookies_sqlite_open_db (KatzeHttpCookiesSqlite* http_cookies) +{ + char *error = NULL; + + if (sqlite3_open (http_cookies->filename, &http_cookies->db)) { + sqlite3_close (http_cookies->db); + g_warning ("Can't open %s", http_cookies->filename); + return TRUE; + } + + if (sqlite3_exec (http_cookies->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 +katze_http_cookies_sqlite_load (KatzeHttpCookiesSqlite* http_cookies) +{ + if (http_cookies->db == NULL) { + if (katze_http_cookies_sqlite_open_db (http_cookies)) + return; + } + + exec_query_with_try_create_table (http_cookies->db, QUERY_ALL, callback, http_cookies->jar); +} +static void +katze_http_cookies_sqlite_jar_changed_cb (SoupCookieJar* jar, + SoupCookie* old_cookie, + SoupCookie* new_cookie, + KatzeHttpCookiesSqlite* http_cookies) +{ + GObject* settings; + char *query; + time_t expires = 0; /* Avoid warning */ + + if (http_cookies->db == NULL) { + if (katze_http_cookies_sqlite_open_db (http_cookies)) + return; + } + + if (new_cookie && new_cookie->expires) + { + gint age; + + expires = soup_date_to_time_t (new_cookie->expires); + settings = g_object_get_data (G_OBJECT (jar), "midori-settings"); + age = katze_object_get_int (settings, "maximum-cookie-age"); + if (age > 0) + { + SoupDate* max_date = soup_date_new_from_now ( + age * SOUP_COOKIE_MAX_AGE_ONE_DAY); + if (soup_date_to_time_t (new_cookie->expires) + > soup_date_to_time_t (max_date)) + soup_cookie_set_expires (new_cookie, max_date); + } + else + { + /* An age of 0 to SoupCookie means already-expired + A user choosing 0 days probably expects 1 hour. */ + soup_cookie_set_max_age (new_cookie, SOUP_COOKIE_MAX_AGE_ONE_HOUR); + } + } + + if (g_getenv ("MIDORI_COOKIES_DEBUG") != NULL) + http_cookies->counter++; + + if (old_cookie) { + query = sqlite3_mprintf (QUERY_DELETE, + old_cookie->name, + old_cookie->domain); + exec_query_with_try_create_table (http_cookies->db, query, NULL, NULL); + sqlite3_free (query); + } + + if (new_cookie && 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 (http_cookies->db, query, NULL, NULL); + sqlite3_free (query); + } +} + +static void +katze_http_cookies_sqlite_attach (SoupSessionFeature* feature, + SoupSession* session) +{ + KatzeHttpCookiesSqlite* http_cookies = (KatzeHttpCookiesSqlite*)feature; + const gchar* filename = g_object_get_data (G_OBJECT (feature), "filename"); + SoupSessionFeature* jar = soup_session_get_feature (session, SOUP_TYPE_COOKIE_JAR); + g_return_if_fail (jar != NULL); + g_return_if_fail (filename != NULL); + katze_assign (http_cookies->filename, g_strdup (filename)); + http_cookies->jar = g_object_ref (jar); + katze_http_cookies_sqlite_open_db (http_cookies); + katze_http_cookies_sqlite_load (http_cookies); + g_signal_connect (jar, "changed", + G_CALLBACK (katze_http_cookies_sqlite_jar_changed_cb), feature); + +} + +static void +katze_http_cookies_sqlite_detach (SoupSessionFeature* feature, + SoupSession* session) +{ + KatzeHttpCookiesSqlite* http_cookies = (KatzeHttpCookiesSqlite*)feature; + katze_assign (http_cookies->filename, NULL); + katze_object_assign (http_cookies->jar, NULL); + sqlite3_close (http_cookies->db); +} + +static void +katze_http_cookies_sqlite_session_feature_iface_init (SoupSessionFeatureInterface *iface, + gpointer data) +{ + iface->attach = katze_http_cookies_sqlite_attach; + iface->detach = katze_http_cookies_sqlite_detach; +} + +static void +katze_http_cookies_sqlite_finalize (GObject* object) +{ + katze_http_cookies_sqlite_detach ((SoupSessionFeature*)object, NULL); +} + +static void +katze_http_cookies_sqlite_class_init (KatzeHttpCookiesSqliteClass* class) +{ + GObjectClass* gobject_class = (GObjectClass*)class; + gobject_class->finalize = katze_http_cookies_sqlite_finalize; +} + +static void +katze_http_cookies_sqlite_init (KatzeHttpCookiesSqlite* http_cookies) +{ + http_cookies->filename = NULL; + http_cookies->jar = NULL; + http_cookies->db = NULL; + http_cookies->counter = 0; +} diff --git a/katze/katze-http-cookies-sqlite.h b/katze/katze-http-cookies-sqlite.h new file mode 100644 index 00000000..41f17d2e --- /dev/null +++ b/katze/katze-http-cookies-sqlite.h @@ -0,0 +1,42 @@ +/* + Copyright (C) 2009 Christian Dywan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See the file COPYING for the full license text. +*/ + +#ifndef __KATZE_HTTP_COOKIES_SQLITE_H__ +#define __KATZE_HTTP_COOKIES_SQLITE_H__ + +#include "katze-utils.h" + +#include + +G_BEGIN_DECLS + +#define KATZE_TYPE_HTTP_COOKIES_SQLITE \ + (katze_http_cookies_sqlite_get_type ()) +#define KATZE_HTTP_COOKIES_SQLITE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), KATZE_TYPE_HTTP_COOKIES_SQLITE, KatzeHttpCookiesSqlite)) +#define KATZE_HTTP_COOKIES_SQLITE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), KATZE_TYPE_HTTP_COOKIES_SQLITE, KatzeHttpCookiesSqliteClass)) +#define KATZE_IS_HTTP_COOKIES_SQLITE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), KATZE_TYPE_HTTP_COOKIES_SQLITE)) +#define KATZE_IS_HTTP_COOKIES_SQLITE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), KATZE_TYPE_HTTP_COOKIES_SQLITE)) +#define KATZE_HTTP_COOKIES_SQLITE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), KATZE_TYPE_HTTP_COOKIES_SQLITE, KatzeHttpCookiesSqliteClass)) + +typedef struct _KatzeHttpCookiesSqlite KatzeHttpCookiesSqlite; +typedef struct _KatzeHttpCookiesSqliteClass KatzeHttpCookiesSqliteClass; + +GType +katze_http_cookies_sqlite_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __KATZE_HTTP_COOKIES_SQLITE_H__ */ diff --git a/katze/katze.h b/katze/katze.h index 367fd447..c31fb962 100644 --- a/katze/katze.h +++ b/katze/katze.h @@ -14,6 +14,7 @@ #include "katze-http-auth.h" #include "katze-http-cookies.h" +#include "katze-http-cookies-sqlite.h" #include "katze-throbber.h" #include "katze-utils.h" #include "katze-item.h" diff --git a/midori/main.c b/midori/main.c index 2059b6a5..ec68c213 100644 --- a/midori/main.c +++ b/midori/main.c @@ -1158,6 +1158,8 @@ midori_load_soup_session_full (gpointer settings) SoupCookieJar* jar; gchar* config_file; SoupSessionFeature* feature; + gboolean have_new_cookies; + SoupSessionFeature* feature_import; midori_load_soup_session (settings); @@ -1172,13 +1174,28 @@ midori_load_soup_session_full (gpointer settings) soup_session_add_feature (session, SOUP_SESSION_FEATURE (jar)); g_object_unref (jar); - feature = g_object_new (KATZE_TYPE_HTTP_COOKIES, NULL); - config_file = build_config_filename ("cookies.txt"); + katze_assign (config_file, build_config_filename ("cookies.db")); + have_new_cookies = g_access (config_file, F_OK) == 0; + feature = g_object_new (KATZE_TYPE_HTTP_COOKIES_SQLITE, NULL); g_object_set_data_full (G_OBJECT (feature), "filename", config_file, (GDestroyNotify)g_free); soup_session_add_feature (session, feature); g_object_unref (feature); + if (!have_new_cookies) + { + katze_assign (config_file, build_config_filename ("cookies.txt")); + if (g_access (config_file, F_OK) == 0) + { + g_message ("Importing cookies from txt to sqlite3"); + feature_import = g_object_new (KATZE_TYPE_HTTP_COOKIES, NULL); + g_object_set_data_full (G_OBJECT (feature_import), "filename", + config_file, (GDestroyNotify)g_free); + soup_session_add_feature (session, SOUP_SESSION_FEATURE (feature_import)); + soup_session_remove_feature (session, SOUP_SESSION_FEATURE (feature_import)); + } + } + #if WEBKIT_CHECK_VERSION (1, 3, 11) config_file = g_build_filename (g_get_user_cache_dir (), PACKAGE_NAME, "web", NULL);