1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * soup-cookie-jar-db.c: database-based cookie storage
4 *
5 * Using danw's soup-cookie-jar-text as template
6 * Copyright (C) 2008 Diego Escalante Urrelo
7 * Copyright (C) 2009 Collabora Ltd.
8 */
9
10 #ifdef HAVE_CONFIG_H
11 #include <config.h>
12 #endif
13
14 #include <stdlib.h>
15
16 #include <sqlite3.h>
17
18 #include "soup-cookie-jar-db.h"
19 #include "soup.h"
20
21 /**
22 * SECTION:soup-cookie-jar-db
23 * @short_description: Database-based Cookie Jar
24 *
25 * #SoupCookieJarDB is a #SoupCookieJar that reads cookies from and
26 * writes them to a sqlite database in the new Mozilla format.
27 *
28 * (This is identical to <literal>SoupCookieJarSqlite</literal> in
29 * libsoup-gnome; it has just been moved into libsoup proper, and
30 * renamed to avoid conflicting.)
31 **/
32
33 enum {
34 PROP_0,
35
36 PROP_FILENAME,
37
38 LAST_PROP
39 };
40
41 typedef struct {
42 char *filename;
43 sqlite3 *db;
44 } SoupCookieJarDBPrivate;
45
46 G_DEFINE_TYPE_WITH_PRIVATE (SoupCookieJarDB, soup_cookie_jar_db, SOUP_TYPE_COOKIE_JAR)
47
48 static void load (SoupCookieJar *jar);
49
50 static void
soup_cookie_jar_db_init(SoupCookieJarDB * db)51 soup_cookie_jar_db_init (SoupCookieJarDB *db)
52 {
53 }
54
55 static void
soup_cookie_jar_db_finalize(GObject * object)56 soup_cookie_jar_db_finalize (GObject *object)
57 {
58 SoupCookieJarDBPrivate *priv =
59 soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (object));
60
61 g_free (priv->filename);
62 g_clear_pointer (&priv->db, sqlite3_close);
63
64 G_OBJECT_CLASS (soup_cookie_jar_db_parent_class)->finalize (object);
65 }
66
67 static void
soup_cookie_jar_db_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)68 soup_cookie_jar_db_set_property (GObject *object, guint prop_id,
69 const GValue *value, GParamSpec *pspec)
70 {
71 SoupCookieJarDBPrivate *priv =
72 soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (object));
73
74 switch (prop_id) {
75 case PROP_FILENAME:
76 priv->filename = g_value_dup_string (value);
77 load (SOUP_COOKIE_JAR (object));
78 break;
79 default:
80 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
81 break;
82 }
83 }
84
85 static void
soup_cookie_jar_db_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)86 soup_cookie_jar_db_get_property (GObject *object, guint prop_id,
87 GValue *value, GParamSpec *pspec)
88 {
89 SoupCookieJarDBPrivate *priv =
90 soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (object));
91
92 switch (prop_id) {
93 case PROP_FILENAME:
94 g_value_set_string (value, priv->filename);
95 break;
96 default:
97 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
98 break;
99 }
100 }
101
102 /**
103 * soup_cookie_jar_db_new:
104 * @filename: the filename to read to/write from, or %NULL
105 * @read_only: %TRUE if @filename is read-only
106 *
107 * Creates a #SoupCookieJarDB.
108 *
109 * @filename will be read in at startup to create an initial set of
110 * cookies. If @read_only is %FALSE, then the non-session cookies will
111 * be written to @filename when the 'changed' signal is emitted from
112 * the jar. (If @read_only is %TRUE, then the cookie jar will only be
113 * used for this session, and changes made to it will be lost when the
114 * jar is destroyed.)
115 *
116 * Return value: the new #SoupCookieJar
117 *
118 * Since: 2.42
119 **/
120 SoupCookieJar *
soup_cookie_jar_db_new(const char * filename,gboolean read_only)121 soup_cookie_jar_db_new (const char *filename, gboolean read_only)
122 {
123 g_return_val_if_fail (filename != NULL, NULL);
124
125 return g_object_new (SOUP_TYPE_COOKIE_JAR_DB,
126 SOUP_COOKIE_JAR_DB_FILENAME, filename,
127 SOUP_COOKIE_JAR_READ_ONLY, read_only,
128 NULL);
129 }
130
131 #define QUERY_ALL "SELECT id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly, sameSite FROM moz_cookies;"
132 #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, sameSite INTEGER)"
133 #define QUERY_INSERT "INSERT INTO moz_cookies VALUES(NULL, %Q, %Q, %Q, %Q, %d, NULL, %d, %d, %d);"
134 #define QUERY_DELETE "DELETE FROM moz_cookies WHERE name=%Q AND host=%Q;"
135
136 enum {
137 COL_ID,
138 COL_NAME,
139 COL_VALUE,
140 COL_HOST,
141 COL_PATH,
142 COL_EXPIRY,
143 COL_LAST_ACCESS,
144 COL_SECURE,
145 COL_HTTP_ONLY,
146 COL_SAME_SITE_POLICY,
147 N_COL,
148 };
149
150 static int
callback(void * data,int argc,char ** argv,char ** colname)151 callback (void *data, int argc, char **argv, char **colname)
152 {
153 SoupCookie *cookie = NULL;
154 SoupCookieJar *jar = SOUP_COOKIE_JAR (data);
155
156 char *name, *value, *host, *path;
157 gulong expire_time;
158 time_t now;
159 int max_age;
160 gboolean http_only = FALSE, secure = FALSE;
161 SoupSameSitePolicy same_site_policy;
162
163 now = time (NULL);
164
165 name = argv[COL_NAME];
166 value = argv[COL_VALUE];
167 host = argv[COL_HOST];
168 path = argv[COL_PATH];
169 expire_time = strtoul (argv[COL_EXPIRY], NULL, 10);
170
171 if (now >= expire_time)
172 return 0;
173 max_age = (expire_time - now <= G_MAXINT ? expire_time - now : G_MAXINT);
174
175 http_only = (g_strcmp0 (argv[COL_HTTP_ONLY], "1") == 0);
176 secure = (g_strcmp0 (argv[COL_SECURE], "1") == 0);
177 same_site_policy = g_ascii_strtoll (argv[COL_SAME_SITE_POLICY], NULL, 0);
178
179 cookie = soup_cookie_new (name, value, host, path, max_age);
180
181 if (secure)
182 soup_cookie_set_secure (cookie, TRUE);
183 if (http_only)
184 soup_cookie_set_http_only (cookie, TRUE);
185 if (same_site_policy)
186 soup_cookie_set_same_site_policy (cookie, same_site_policy);
187
188 soup_cookie_jar_add_cookie (jar, cookie);
189
190 return 0;
191 }
192
193 static void
try_create_table(sqlite3 * db)194 try_create_table (sqlite3 *db)
195 {
196 char *error = NULL;
197
198 if (sqlite3_exec (db, CREATE_TABLE, NULL, NULL, &error)) {
199 g_warning ("Failed to execute query: %s", error);
200 sqlite3_free (error);
201 }
202 }
203
204 static void
exec_query_with_try_create_table(sqlite3 * db,const char * sql,int (* callback)(void *,int,char **,char **),void * argument)205 exec_query_with_try_create_table (sqlite3 *db,
206 const char *sql,
207 int (*callback)(void*,int,char**,char**),
208 void *argument)
209 {
210 char *error = NULL;
211 gboolean try_create = TRUE;
212
213 try_exec:
214 if (sqlite3_exec (db, sql, callback, argument, &error)) {
215 if (try_create) {
216 try_create = FALSE;
217 try_create_table (db);
218 sqlite3_free (error);
219 error = NULL;
220 goto try_exec;
221 } else {
222 g_warning ("Failed to execute query: %s", error);
223 sqlite3_free (error);
224 }
225 }
226 }
227
228 /* Follows sqlite3 convention; returns TRUE on error */
229 static gboolean
open_db(SoupCookieJar * jar)230 open_db (SoupCookieJar *jar)
231 {
232 SoupCookieJarDBPrivate *priv =
233 soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (jar));
234
235 char *error = NULL;
236
237 if (sqlite3_open (priv->filename, &priv->db)) {
238 sqlite3_close (priv->db);
239 priv->db = NULL;
240 g_warning ("Can't open %s", priv->filename);
241 return TRUE;
242 }
243
244 if (sqlite3_exec (priv->db, "PRAGMA synchronous = OFF; PRAGMA secure_delete = 1;", NULL, NULL, &error)) {
245 g_warning ("Failed to execute query: %s", error);
246 sqlite3_free (error);
247 }
248
249 /* Migrate old DB to include same-site info. We simply always run this as it
250 will safely handle a column with the same name existing */
251 sqlite3_exec (priv->db, "ALTER TABLE moz_cookies ADD COLUMN sameSite INTEGER DEFAULT 0", NULL, NULL, NULL);
252
253 return FALSE;
254 }
255
256 static void
load(SoupCookieJar * jar)257 load (SoupCookieJar *jar)
258 {
259 SoupCookieJarDBPrivate *priv =
260 soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (jar));
261
262 if (priv->db == NULL) {
263 if (open_db (jar))
264 return;
265 }
266
267 exec_query_with_try_create_table (priv->db, QUERY_ALL, callback, jar);
268 }
269
270 static void
soup_cookie_jar_db_changed(SoupCookieJar * jar,SoupCookie * old_cookie,SoupCookie * new_cookie)271 soup_cookie_jar_db_changed (SoupCookieJar *jar,
272 SoupCookie *old_cookie,
273 SoupCookie *new_cookie)
274 {
275 SoupCookieJarDBPrivate *priv =
276 soup_cookie_jar_db_get_instance_private (SOUP_COOKIE_JAR_DB (jar));
277 char *query;
278
279 if (priv->db == NULL) {
280 if (open_db (jar))
281 return;
282 }
283
284 if (old_cookie) {
285 query = sqlite3_mprintf (QUERY_DELETE,
286 old_cookie->name,
287 old_cookie->domain);
288 exec_query_with_try_create_table (priv->db, query, NULL, NULL);
289 sqlite3_free (query);
290 }
291
292 if (new_cookie && new_cookie->expires) {
293 gulong expires;
294
295 expires = (gulong)soup_date_to_time_t (new_cookie->expires);
296 query = sqlite3_mprintf (QUERY_INSERT,
297 new_cookie->name,
298 new_cookie->value,
299 new_cookie->domain,
300 new_cookie->path,
301 expires,
302 new_cookie->secure,
303 new_cookie->http_only,
304 soup_cookie_get_same_site_policy (new_cookie));
305 exec_query_with_try_create_table (priv->db, query, NULL, NULL);
306 sqlite3_free (query);
307 }
308 }
309
310 static gboolean
soup_cookie_jar_db_is_persistent(SoupCookieJar * jar)311 soup_cookie_jar_db_is_persistent (SoupCookieJar *jar)
312 {
313 return TRUE;
314 }
315
316 static void
soup_cookie_jar_db_class_init(SoupCookieJarDBClass * db_class)317 soup_cookie_jar_db_class_init (SoupCookieJarDBClass *db_class)
318 {
319 SoupCookieJarClass *cookie_jar_class =
320 SOUP_COOKIE_JAR_CLASS (db_class);
321 GObjectClass *object_class = G_OBJECT_CLASS (db_class);
322
323 cookie_jar_class->is_persistent = soup_cookie_jar_db_is_persistent;
324 cookie_jar_class->changed = soup_cookie_jar_db_changed;
325
326 object_class->finalize = soup_cookie_jar_db_finalize;
327 object_class->set_property = soup_cookie_jar_db_set_property;
328 object_class->get_property = soup_cookie_jar_db_get_property;
329
330 /**
331 * SOUP_COOKIE_JAR_DB_FILENAME:
332 *
333 * Alias for the #SoupCookieJarDB:filename property. (The
334 * cookie-storage filename.)
335 **/
336 g_object_class_install_property (
337 object_class, PROP_FILENAME,
338 g_param_spec_string (SOUP_COOKIE_JAR_DB_FILENAME,
339 "Filename",
340 "Cookie-storage filename",
341 NULL,
342 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
343 G_PARAM_STATIC_STRINGS));
344 }
345