1 #include <QDir>
2 #include <QSqlDriver>
3 #include <QSqlQuery>
4 #include <QSqlError>
5 #include <QMultiMap>
6 #include <QUuid>
7
8 #include "settings.h"
9 #include "options.h"
10 #include "cookiejar.h"
11 #include "qmc2main.h"
12 #include "macros.h"
13
14 extern MainWindow *qmc2MainWindow;
15 extern Settings *qmc2Config;
16
CookieJar(QObject * parent)17 CookieJar::CookieJar(QObject *parent) : QNetworkCookieJar(parent)
18 {
19 QString userScopePath = Options::configPath();
20 db = QSqlDatabase::addDatabase("QSQLITE", "cookie-db-connection-" + QUuid::createUuid().toString());
21 db.setDatabaseName(qmc2Config->value(QMC2_FRONTEND_PREFIX + "WebBrowser/CookieDatabase", userScopePath + "/qmc2-" + QMC2_EMU_NAME_VARIANT.toLower() + "-cookies.db").toString());
22 if ( !db.open() ) {
23 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to open cookie database: error = '%1'").arg(db.lastError().text()));
24 return;
25 }
26 QStringList tables = db.driver()->tables(QSql::Tables);
27 if ( tables.count() != 1 || !tables.contains("qmc2_cookies") )
28 recreateDatabase();
29 static QStringList dbSyncModes = QStringList() << "OFF" << "NORMAL" << "FULL";
30 QSqlQuery query(db);
31 query.exec("PRAGMA synchronous = OFF");
32 query.exec("PRAGMA journal_mode = MEMORY");
33 }
34
~CookieJar()35 CookieJar::~CookieJar()
36 {
37 if ( db.isOpen() ) {
38 saveCookies();
39 db.close();
40 }
41 }
42
recreateDatabase()43 void CookieJar::recreateDatabase()
44 {
45 if ( !db.isOpen() )
46 return;
47
48 QSqlQuery query(db);
49 if ( !query.exec("DROP TABLE IF EXISTS qmc2_cookies") ) {
50 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to remove cookie database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
51 return;
52 }
53 query.finish();
54 // vaccum'ing the database frees all disk-space previously used
55 query.exec("VACUUM");
56 query.finish();
57 if ( !query.exec("CREATE TABLE qmc2_cookies (id INTEGER PRIMARY KEY, domain TEXT, name TEXT, value TEXT, path TEXT, expiry INTEGER, secure INTEGER, http_only INTEGER, CONSTRAINT qmc2_uniqueid UNIQUE (name, domain, path))") )
58 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to create cookie database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
59 cookieMap.clear();
60 setAllCookies(QList<QNetworkCookie>());
61 }
62
cookiesForUrl(const QUrl & url) const63 QList<QNetworkCookie> CookieJar::cookiesForUrl(const QUrl &url) const
64 {
65 QString domain = url.host();
66 QString path = url.path();
67 QString defaultPath = path.left(path.lastIndexOf(QLatin1Char('/')) + 1);
68 if ( defaultPath.isEmpty() )
69 defaultPath = QLatin1Char('/');
70 QList<QNetworkCookie> cookieList;
71 if ( loadCookies(cookieList, domain, defaultPath) )
72 return cookieList;
73 else
74 return QNetworkCookieJar::cookiesForUrl(url);
75 }
76
setCookiesFromUrl(const QList<QNetworkCookie> & cookieList,const QUrl & url)77 bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
78 {
79 QString domain = url.host();
80 QString path = url.path();
81 QString defaultPath = path.left(path.lastIndexOf(QLatin1Char('/')) + 1);
82 if ( defaultPath.isEmpty() )
83 defaultPath = QLatin1Char('/');
84 for (int i = 0; i < cookieList.count(); i++) {
85 QNetworkCookie cookie = cookieList[i];
86 cookie.setDomain(domain);
87 cookie.setPath(defaultPath);
88 cookieMap.insertMulti(domain + defaultPath, cookie);
89 }
90 return QNetworkCookieJar::setCookiesFromUrl(cookieList, url);
91 }
92
saveCookies()93 void CookieJar::saveCookies()
94 {
95 if ( !db.isOpen() )
96 return;
97
98 QSqlQuery query(db);
99 QDateTime now = QDateTime::currentDateTime();
100
101 QMapIterator<QString, QNetworkCookie> it(cookieMap);
102 QStringList cookieKeysProcessed;
103 db.driver()->beginTransaction();
104 while ( it.hasNext() ) {
105 it.next();
106 QNetworkCookie cookie = it.value();
107 QString cookieKey = cookie.domain() + cookie.path() + cookie.name();
108 if ( cookieKeysProcessed.contains(cookieKey) )
109 continue;
110 query.prepare("SELECT domain, name, path FROM qmc2_cookies WHERE domain=:domain AND path=:path AND name=:name");
111 query.bindValue(":domain", cookie.domain());
112 query.bindValue(":path", cookie.path());
113 query.bindValue(":name", cookie.name());
114 if ( query.exec() ) {
115 if ( query.next() ) {
116 query.finish();
117 if ( cookie.value().isEmpty() ) {
118 query.prepare("DELETE FROM qmc2_cookies WHERE domain=:domain AND path=:path AND name=:name");
119 query.bindValue(":domain", cookie.domain());
120 query.bindValue(":path", cookie.path());
121 query.bindValue(":name", cookie.name());
122 if ( !query.exec() )
123 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to remove expired cookie from database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
124 query.finish();
125 cookieKeysProcessed << cookieKey;
126 } else if ( cookie.expirationDate() < now ) {
127 query.prepare("DELETE FROM qmc2_cookies WHERE domain=:domain AND path=:path AND name=:name");
128 query.bindValue(":domain", cookie.domain());
129 query.bindValue(":path", cookie.path());
130 query.bindValue(":name", cookie.name());
131 if ( !query.exec() )
132 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to remove expired cookie from database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
133 query.finish();
134 cookieKeysProcessed << cookieKey;
135 } else if ( !cookie.isSessionCookie() ) {
136 query.prepare("UPDATE qmc2_cookies SET value=:value, expiry=" + QString::number(cookie.expirationDate().toTime_t()) + " WHERE domain=:domain AND path=:path AND name=:name");
137 query.bindValue(":value", cookie.value());
138 query.bindValue(":domain", cookie.domain());
139 query.bindValue(":path", cookie.path());
140 query.bindValue(":name", cookie.name());
141 if ( !query.exec() )
142 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to update cookie in database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
143 query.finish();
144 cookieKeysProcessed << cookieKey;
145 }
146 } else {
147 query.finish();
148 if ( cookie.expirationDate() > now && !cookie.isSessionCookie() ) {
149 query.prepare("INSERT INTO qmc2_cookies (domain, name, value, path, expiry, secure, http_only) VALUES (:domain, :name, :value, :path, " + QString::number(cookie.expirationDate().toTime_t()) + ", " + QString(cookie.isSecure() ? "1" : "0") + ", " + QString(cookie.isHttpOnly() ? "1" : "0") + ")");
150 query.bindValue(":value", cookie.value());
151 query.bindValue(":domain", cookie.domain());
152 query.bindValue(":path", cookie.path());
153 query.bindValue(":name", cookie.name());
154 if ( !query.exec() )
155 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to add cookie to database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
156 query.finish();
157 cookieKeysProcessed << cookieKey;
158 }
159 }
160 } else
161 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to query cookie database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
162 }
163 db.driver()->commitTransaction();
164 }
165
loadCookies(QList<QNetworkCookie> & cookieList,QString domain,QString path) const166 bool CookieJar::loadCookies(QList<QNetworkCookie> &cookieList, QString domain, QString path) const
167 {
168 cookieList.clear();
169
170 if ( cookieMap.contains(domain + path) ) {
171 cookieList = cookieMap.values(domain + path);
172 return !cookieList.isEmpty();
173 }
174
175 if ( !db.isOpen() )
176 return false;
177
178 QSqlQuery query(db);
179 query.prepare("SELECT domain, name, value, path, expiry, secure, http_only FROM qmc2_cookies WHERE domain=:domain AND path=:path");
180 query.bindValue(":domain", domain);
181 query.bindValue(":path", path);
182 if ( !query.exec() ) {
183 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch cookies from database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
184 return false;
185 }
186 QDateTime now = QDateTime::currentDateTime();
187 QDateTime dt;
188 db.driver()->beginTransaction();
189 while ( query.next() ) {
190 QNetworkCookie cookie;
191 cookie.setDomain(query.value(0).toString());
192 cookie.setName(query.value(1).toByteArray());
193 cookie.setValue(query.value(2).toByteArray());
194 cookie.setPath(query.value(3).toString());
195 dt.setTime_t((uint) query.value(4).toULongLong());
196 cookie.setExpirationDate(dt);
197 cookie.setSecure(query.value(5).toBool());
198 cookie.setHttpOnly(query.value(6).toBool());
199 if ( dt < now ) {
200 QSqlQuery delquery(db);
201 delquery.prepare("DELETE FROM qmc2_cookies WHERE domain=:domain AND path=:path AND name=:name");
202 delquery.bindValue(":domain", cookie.domain());
203 delquery.bindValue(":path", cookie.path());
204 delquery.bindValue(":name", cookie.name());
205 if ( !delquery.exec() )
206 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to remove expired cookie from database: query = '%1', error = '%2'").arg(delquery.lastQuery()).arg(query.lastError().text()));
207 } else {
208 cookieList << cookie;
209 cookieMap.insertMulti(domain + path, cookie);
210 }
211 }
212 db.driver()->commitTransaction();
213 return !cookieList.isEmpty();
214 }
215