1 /*
2 ** Copyright (c) 2008 - present, Alexis Megas.
3 ** All rights reserved.
4 **
5 ** Redistribution and use in source and binary forms, with or without
6 ** modification, are permitted provided that the following conditions
7 ** are met:
8 ** 1. Redistributions of source code must retain the above copyright
9 ** notice, this list of conditions and the following disclaimer.
10 ** 2. Redistributions in binary form must reproduce the above copyright
11 ** notice, this list of conditions and the following disclaimer in the
12 ** documentation and/or other materials provided with the distribution.
13 ** 3. The name of the author may not be used to endorse or promote products
14 ** derived from Dooble without specific prior written permission.
15 **
16 ** DOOBLE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 ** DOOBLE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #include <QDir>
29 #include <QSqlQuery>
30 #include <QWebEngineCookieStore>
31 #include <QWebEngineProfile>
32
33 #include "dooble.h"
34 #include "dooble_cookies.h"
35 #include "dooble_cryptography.h"
36 #include "dooble_database_utilities.h"
37
dooble_cookies(bool is_private,QObject * parent)38 dooble_cookies::dooble_cookies(bool is_private, QObject *parent):QObject(parent)
39 {
40 m_is_private = is_private;
41 }
42
identifier(const QNetworkCookie & cookie)43 QByteArray dooble_cookies::identifier(const QNetworkCookie &cookie)
44 {
45 QByteArray bytes;
46
47 bytes.append(cookie.domain().toUtf8());
48 bytes.append(cookie.name());
49 bytes.append(cookie.path().toUtf8());
50 return bytes;
51 }
52
create_tables(QSqlDatabase & db)53 void dooble_cookies::create_tables(QSqlDatabase &db)
54 {
55 db.open();
56
57 QSqlQuery query(db);
58
59 query.exec("CREATE TABLE IF NOT EXISTS dooble_cookies_domains ("
60 "domain TEXT NOT NULL, "
61 "domain_digest TEXT NOT NULL PRIMARY KEY, "
62 "favorite_digest TEXT NOT NULL)");
63 query.exec("CREATE TABLE IF NOT EXISTS dooble_cookies ("
64 "domain_digest TEXT NOT NULL, "
65 "identifier_digest TEXT NOT NULL, "
66 "raw_form BLOB NOT NULL, "
67 "PRIMARY KEY (domain_digest, identifier_digest), "
68 "FOREIGN KEY (domain_digest) REFERENCES "
69 "dooble_cookies_domains (domain_digest) ON DELETE CASCADE "
70 ")");
71 }
72
purge(void)73 void dooble_cookies::purge(void)
74 {
75 auto database_name(dooble_database_utilities::database_name());
76
77 {
78 auto db = QSqlDatabase::addDatabase("QSQLITE", database_name);
79
80 db.setDatabaseName(dooble_settings::setting("home_path").toString() +
81 QDir::separator() +
82 "dooble_cookies.db");
83
84 if(db.open())
85 {
86 QSqlQuery query(db);
87
88 query.exec("PRAGMA foreign_keys = ON");
89 query.exec("PRAGMA synchronous = OFF");
90 query.exec("DELETE FROM dooble_cookies");
91 query.exec("DELETE FROM dooble_cookies_domains");
92 query.exec("VACUUM");
93 }
94
95 db.close();
96 }
97
98 QSqlDatabase::removeDatabase(database_name);
99 }
100
slot_connect_cookie_added_signal(void)101 void dooble_cookies::slot_connect_cookie_added_signal(void)
102 {
103 connect(QWebEngineProfile::defaultProfile()->cookieStore(),
104 SIGNAL(cookieAdded(const QNetworkCookie &)),
105 dooble::s_cookies,
106 SLOT(slot_cookie_added(const QNetworkCookie &)),
107 Qt::UniqueConnection);
108 emit populated();
109 }
110
slot_cookie_added(const QNetworkCookie & cookie)111 void dooble_cookies::slot_cookie_added(const QNetworkCookie &cookie)
112 {
113 emit cookies_added
114 (QList<QNetworkCookie> () << cookie, QList<bool> () << false);
115
116 if(!dooble::s_cryptography || !dooble::s_cryptography->authenticated())
117 return;
118 else if(m_is_private)
119 return;
120
121 QDateTime now;
122
123 if(cookie.isSessionCookie())
124 {
125 if(dooble_settings::cookie_policy_string(dooble_settings::
126 setting("cookie_policy_index").
127 toInt()) == "save_all")
128 /*
129 ** Allow a session cookie to be saved.
130 */
131
132 goto save_label;
133 else
134 return;
135 }
136 else if(dooble_settings::
137 cookie_policy_string(dooble::s_settings->
138 setting("cookie_policy_index").
139 toInt()) == "do_not_save")
140 return;
141
142 now = QDateTime::currentDateTime();
143
144 if(cookie.expirationDate().toLocalTime() <= now)
145 return;
146
147 save_label:
148
149 auto database_name(dooble_database_utilities::database_name());
150
151 {
152 auto db = QSqlDatabase::addDatabase("QSQLITE", database_name);
153
154 db.setDatabaseName(dooble_settings::setting("home_path").toString() +
155 QDir::separator() +
156 "dooble_cookies.db");
157
158 if(db.open())
159 {
160 create_tables(db);
161
162 QSqlQuery query(db);
163
164 query.exec("PRAGMA synchronous = OFF");
165 query.prepare
166 ("INSERT INTO dooble_cookies_domains "
167 "(domain, domain_digest, favorite_digest) VALUES (?, ?, ?)");
168
169 QByteArray bytes;
170
171 bytes = dooble::s_cryptography->encrypt_then_mac
172 (cookie.domain().toUtf8());
173
174 if(!bytes.isEmpty())
175 {
176 query.addBindValue(bytes.toBase64());
177 query.addBindValue
178 (dooble::s_cryptography->hmac(cookie.domain()).toBase64());
179 query.addBindValue
180 (dooble::s_cryptography->hmac(QByteArray("false")).toBase64());
181 query.exec();
182 }
183
184 query.prepare
185 ("INSERT OR REPLACE INTO dooble_cookies "
186 "(domain_digest, identifier_digest, raw_form) VALUES (?, ?, ?)");
187 query.addBindValue
188 (dooble::s_cryptography->hmac(cookie.domain()).toBase64());
189 query.addBindValue
190 (dooble::s_cryptography->hmac(identifier(cookie)).toBase64());
191 bytes = dooble::s_cryptography->encrypt_then_mac(cookie.toRawForm());
192
193 if(!bytes.isEmpty())
194 query.addBindValue(bytes.toBase64());
195 else
196 goto done_label;
197
198 query.exec();
199 }
200
201 done_label:
202 db.close();
203 }
204
205 QSqlDatabase::removeDatabase(database_name);
206 }
207
slot_cookie_removed(const QNetworkCookie & cookie)208 void dooble_cookies::slot_cookie_removed(const QNetworkCookie &cookie)
209 {
210 emit cookie_removed(cookie);
211
212 if(!dooble::s_cryptography || !dooble::s_cryptography->authenticated())
213 return;
214 else if(m_is_private)
215 return;
216
217 auto database_name(dooble_database_utilities::database_name());
218
219 {
220 auto db = QSqlDatabase::addDatabase("QSQLITE", database_name);
221
222 db.setDatabaseName(dooble_settings::setting("home_path").toString() +
223 QDir::separator() +
224 "dooble_cookies.db");
225
226 if(db.open())
227 {
228 QSqlQuery query(db);
229
230 query.exec("PRAGMA synchronous = OFF");
231 query.prepare("DELETE FROM dooble_cookies WHERE identifier_digest = ?");
232 query.addBindValue
233 (dooble::s_cryptography->hmac(identifier(cookie)).toBase64());
234 query.exec();
235 }
236
237 db.close();
238 }
239
240 QSqlDatabase::removeDatabase(database_name);
241 }
242
slot_delete_cookie(const QNetworkCookie & cookie)243 void dooble_cookies::slot_delete_cookie(const QNetworkCookie &cookie)
244 {
245 if(!dooble::s_cryptography || !dooble::s_cryptography->authenticated())
246 return;
247 else if(m_is_private)
248 return;
249
250 auto database_name(dooble_database_utilities::database_name());
251
252 {
253 auto db = QSqlDatabase::addDatabase("QSQLITE", database_name);
254
255 db.setDatabaseName(dooble_settings::setting("home_path").toString() +
256 QDir::separator() +
257 "dooble_cookies.db");
258
259 if(db.open())
260 {
261 QSqlQuery query(db);
262
263 query.exec("PRAGMA synchronous = OFF");
264 query.prepare("DELETE FROM dooble_cookies WHERE identifier_digest = ?");
265 query.addBindValue
266 (dooble::s_cryptography->hmac(identifier(cookie)).toBase64());
267 query.exec();
268 query.prepare("DELETE FROM dooble_cookies_domains WHERE "
269 "domain_digest NOT IN (SELECT domain_digest FROM "
270 "dooble_cookies) AND favorite_digest = ?");
271 query.addBindValue
272 (dooble::s_cryptography->hmac(QByteArray("false")).toBase64());
273 query.exec();
274 }
275
276 db.close();
277 }
278
279 QSqlDatabase::removeDatabase(database_name);
280 }
281
slot_delete_domain(const QString & domain)282 void dooble_cookies::slot_delete_domain(const QString &domain)
283 {
284 if(!dooble::s_cryptography || !dooble::s_cryptography->authenticated())
285 return;
286 else if(m_is_private)
287 return;
288
289 auto database_name(dooble_database_utilities::database_name());
290
291 {
292 auto db = QSqlDatabase::addDatabase("QSQLITE", database_name);
293
294 db.setDatabaseName(dooble_settings::setting("home_path").toString() +
295 QDir::separator() +
296 "dooble_cookies.db");
297
298 if(db.open())
299 {
300 QSqlQuery query(db);
301
302 query.exec("PRAGMA foreign_keys = ON");
303 query.exec("PRAGMA synchronous = OFF");
304 query.prepare("DELETE FROM dooble_cookies_domains WHERE "
305 "domain_digest = ?");
306 query.addBindValue
307 (dooble::s_cryptography->hmac(domain.toUtf8()).toBase64());
308 query.exec();
309 }
310
311 db.close();
312 }
313
314 QSqlDatabase::removeDatabase(database_name);
315 }
316
slot_delete_items(const QList<QNetworkCookie> & cookies,const QStringList & domains)317 void dooble_cookies::slot_delete_items(const QList<QNetworkCookie> &cookies,
318 const QStringList &domains)
319 {
320 if(!dooble::s_cryptography || !dooble::s_cryptography->authenticated())
321 return;
322 else if(m_is_private)
323 return;
324
325 auto database_name(dooble_database_utilities::database_name());
326
327 {
328 auto db = QSqlDatabase::addDatabase("QSQLITE", database_name);
329
330 db.setDatabaseName(dooble_settings::setting("home_path").toString() +
331 QDir::separator() +
332 "dooble_cookies.db");
333
334 if(db.open())
335 {
336 QSqlQuery query(db);
337
338 query.exec("PRAGMA synchronous = OFF");
339
340 for(const auto &cookie : cookies)
341 {
342 query.prepare
343 ("DELETE FROM dooble_cookies WHERE identifier_digest = ?");
344 query.addBindValue
345 (dooble::s_cryptography->
346 hmac(identifier(cookie)).toBase64());
347 query.exec();
348 }
349
350 query.prepare("DELETE FROM dooble_cookies_domains WHERE "
351 "domain_digest NOT IN (SELECT domain_digest FROM "
352 "dooble_cookies) AND favorite_digest = ?");
353 query.addBindValue
354 (dooble::s_cryptography->hmac(QByteArray("false")).toBase64());
355 query.exec();
356 query.exec("PRAGMA foreign_keys = ON");
357
358 for(const auto &domain : domains)
359 {
360 query.prepare("DELETE FROM dooble_cookies_domains WHERE "
361 "domain_digest = ?");
362 query.addBindValue
363 (dooble::s_cryptography->hmac(domain.toUtf8()).toBase64());
364 query.exec();
365 }
366 }
367
368 db.close();
369 }
370
371 QSqlDatabase::removeDatabase(database_name);
372 }
373
slot_populate(void)374 void dooble_cookies::slot_populate(void)
375 {
376 if(!dooble::s_cryptography || !dooble::s_cryptography->authenticated())
377 {
378 emit populated();
379 return;
380 }
381 else if(m_is_private)
382 {
383 emit populated();
384 return;
385 }
386
387 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
388
389 auto database_name(dooble_database_utilities::database_name());
390 auto profile = QWebEngineProfile::defaultProfile();
391 int count = 0;
392
393 disconnect(profile->cookieStore(),
394 SIGNAL(cookieAdded(const QNetworkCookie &)),
395 dooble::s_cookies,
396 SLOT(slot_cookie_added(const QNetworkCookie &)));
397
398 {
399 auto db = QSqlDatabase::addDatabase("QSQLITE", database_name);
400
401 db.setDatabaseName(dooble_settings::setting("home_path").toString() +
402 QDir::separator() +
403 "dooble_cookies.db");
404
405 if(db.open())
406 {
407 create_tables(db);
408
409 QList<QNetworkCookie> cookies;
410 QList<bool> is_favorites;
411 QSqlQuery query(db);
412
413 query.setForwardOnly(true);
414
415 if(query.exec("SELECT domain, favorite_digest FROM "
416 "dooble_cookies_domains"))
417 while(query.next())
418 {
419 auto bytes
420 (QByteArray::fromBase64(query.value(0).toByteArray()));
421
422 bytes = dooble::s_cryptography->mac_then_decrypt(bytes);
423
424 if(bytes.isEmpty())
425 {
426 QSqlQuery delete_query(db);
427
428 delete_query.exec("PRAGMA foreign_keys = ON");
429 delete_query.prepare
430 ("DELETE FROM dooble_cookies_domains WHERE domain = ?");
431 delete_query.addBindValue(query.value(0));
432 delete_query.exec();
433 continue;
434 }
435
436 QNetworkCookie cookie;
437 auto is_favorite = dooble_cryptography::memcmp
438 (dooble::s_cryptography->hmac(QByteArray("true")).toBase64(),
439 query.value(1).toByteArray());
440
441 cookie.setDomain(bytes);
442 cookies << cookie;
443 is_favorites << is_favorite;
444 }
445
446 if(!cookies.isEmpty() && !is_favorites.isEmpty())
447 emit cookies_added(cookies, is_favorites);
448
449 cookies.clear();
450 is_favorites.clear();
451
452 if(query.exec("SELECT "
453 "(SELECT favorite_digest FROM dooble_cookies_domains a "
454 "WHERE a.domain_digest = b.domain_digest) "
455 "AS favorite_digest, "
456 "raw_form FROM dooble_cookies b"))
457 while(query.next())
458 {
459 auto bytes
460 (QByteArray::fromBase64(query.value(1).toByteArray()));
461
462 bytes = dooble::s_cryptography->mac_then_decrypt(bytes);
463
464 if(bytes.isEmpty())
465 {
466 QSqlQuery delete_query(db);
467
468 delete_query.prepare
469 ("DELETE FROM dooble_cookies WHERE raw_form = ?");
470 delete_query.addBindValue(query.value(1));
471 delete_query.exec();
472 delete_query.prepare
473 ("DELETE FROM dooble_cookies_domains WHERE "
474 "domain_digest NOT IN (SELECT domain_digest FROM "
475 "dooble_cookies) AND favorite_digest = ?");
476 delete_query.addBindValue
477 (dooble::s_cryptography->hmac(QByteArray("false")).
478 toBase64());
479 delete_query.exec();
480 continue;
481 }
482
483 auto cookie = QNetworkCookie::parseCookies(bytes);
484
485 if(cookie.isEmpty())
486 {
487 QSqlQuery delete_query(db);
488
489 delete_query.prepare
490 ("DELETE FROM dooble_cookies WHERE raw_form = ?");
491 delete_query.addBindValue(query.value(1));
492 delete_query.exec();
493 delete_query.prepare
494 ("DELETE FROM dooble_cookies_domains WHERE "
495 "domain_digest NOT IN (SELECT domain_digest FROM "
496 "dooble_cookies) AND favorite_digest = ?");
497 delete_query.addBindValue
498 (dooble::s_cryptography->hmac(QByteArray("false")).
499 toBase64());
500 delete_query.exec();
501 continue;
502 }
503
504 auto allow_expired = false;
505 auto now(QDateTime::currentDateTime());
506
507 if(cookie.at(0).isSessionCookie())
508 {
509 if(dooble_settings::
510 cookie_policy_string(dooble_settings::
511 setting("cookie_policy_index").
512 toInt()) == "save_all")
513 /*
514 ** Ignore the session cookie's expiration date.
515 */
516
517 allow_expired = true;
518 }
519 else
520 allow_expired = false;
521
522 if(!allow_expired)
523 if(cookie.at(0).expirationDate().toLocalTime() <= now)
524 {
525 QSqlQuery delete_query(db);
526
527 delete_query.prepare
528 ("DELETE FROM dooble_cookies WHERE raw_form = ?");
529 delete_query.addBindValue(query.value(1));
530 delete_query.exec();
531 delete_query.prepare
532 ("DELETE FROM dooble_cookies_domains WHERE "
533 "domain_digest NOT IN (SELECT domain_digest FROM "
534 "dooble_cookies) AND favorite_digest = ?");
535 delete_query.addBindValue
536 (dooble::s_cryptography->hmac(QByteArray("false")).
537 toBase64());
538 delete_query.exec();
539 continue;
540 }
541
542 auto c(cookie.at(0));
543 auto is_favorite = dooble_cryptography::memcmp
544 (dooble::s_cryptography->hmac(QByteArray("true")).toBase64(),
545 query.value(0).toByteArray());
546
547 #ifdef DOOBLE_COOKIES_REPLACE_HYPHEN_WITH_UNDERSCORE
548 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
549 c.setName(c.name().replace('-', '_'));
550 #endif
551 #endif
552 cookies << c;
553
554 auto url(QUrl::fromUserInput(c.domain()));
555
556 if(c.isSecure())
557 url.setScheme("https");
558
559 c.setDomain("");
560 count += 1;
561 is_favorites << is_favorite;
562 profile->cookieStore()->setCookie(c, url);
563 }
564
565 if(!cookies.isEmpty() && !is_favorites.isEmpty())
566 emit cookies_added(cookies, is_favorites);
567 }
568
569 db.close();
570 }
571
572 QSqlDatabase::removeDatabase(database_name);
573 QApplication::restoreOverrideCursor();
574
575 /*
576 ** Re-connect the cookieAdded() signal.
577 */
578
579 QTimer::singleShot(count, this, SLOT(slot_connect_cookie_added_signal(void)));
580 emit populated();
581 }
582