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 <QKeyEvent>
30 #include <QSqlQuery>
31 #include <QWebEngineScript>
32 #include <QWebEngineScriptCollection>
33
34 #include "dooble.h"
35 #include "dooble_cryptography.h"
36 #include "dooble_database_utilities.h"
37 #include "dooble_style_sheet.h"
38 #include "dooble_web_engine_page.h"
39
40 /*
41 ** body { -webkit-transform: rotate(180deg); }
42 */
43
44 QMap<QPair<QString, QUrl>, QString> dooble_style_sheet::s_style_sheets;
45
dooble_style_sheet(dooble_web_engine_page * web_engine_page,QWidget * parent)46 dooble_style_sheet::dooble_style_sheet(dooble_web_engine_page *web_engine_page,
47 QWidget *parent):QDialog(parent)
48 {
49 m_ui.setupUi(this);
50 m_web_engine_page = web_engine_page;
51 connect(m_ui.add,
52 SIGNAL(clicked(void)),
53 this,
54 SLOT(slot_add(void)));
55 connect(m_ui.names,
56 SIGNAL(itemSelectionChanged(void)),
57 this,
58 SLOT(slot_item_selection_changed(void)));
59 connect(m_ui.remove,
60 SIGNAL(clicked(void)),
61 this,
62 SLOT(slot_remove(void)));
63 populate();
64 new QShortcut(QKeySequence(tr("Ctrl+W")), this, SLOT(close(void)));
65 }
66
dooble_style_sheet(void)67 dooble_style_sheet::dooble_style_sheet(void)
68 {
69 m_web_engine_page = nullptr;
70 }
71
inject(dooble_web_engine_page * web_engine_page)72 void dooble_style_sheet::inject(dooble_web_engine_page *web_engine_page)
73 {
74 if(!web_engine_page)
75 return;
76
77 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
78
79 QMapIterator<QPair<QString, QUrl>, QString> it(s_style_sheets);
80
81 while(it.hasNext())
82 {
83 it.next();
84
85 if(it.key().second == web_engine_page->simplified_url())
86 {
87 auto style_sheet
88 (QString::fromLatin1("(function() {"
89 "css = document.createElement('style');"
90 "css.id = '%1';"
91 "css.type = 'text/css';"
92 "css.innerText = '%2';"
93 "document.head.appendChild(css);"
94 "})()").
95 arg(it.key().first).
96 arg(it.value()));
97 QWebEngineScript web_engine_script;
98
99 web_engine_script.setInjectionPoint(QWebEngineScript::DocumentReady);
100 web_engine_script.setName(it.key().first);
101 web_engine_script.setRunsOnSubFrames(true);
102 web_engine_script.setSourceCode(style_sheet);
103 web_engine_script.setWorldId(QWebEngineScript::ApplicationWorld);
104 web_engine_page->runJavaScript
105 (style_sheet, QWebEngineScript::ApplicationWorld);
106 web_engine_page->scripts().insert(web_engine_script);
107 }
108 else
109 {
110 auto style_sheet
111 (QString::fromLatin1("(function() {"
112 "var element = document.getElementById('%1');"
113 "if(element) element.outerHTML = '';"
114 "delete element;})()").arg(it.key().first));
115
116 web_engine_page->runJavaScript
117 (style_sheet, QWebEngineScript::ApplicationWorld);
118 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
119 web_engine_page->scripts().remove
120 (web_engine_page->scripts().findScript(it.key().first));
121 #else
122 foreach(const auto &script,
123 web_engine_page->scripts().find(it.key().first))
124 web_engine_page->scripts().remove(script);
125 #endif
126 }
127 }
128
129 QApplication::restoreOverrideCursor();
130 }
131
keyPressEvent(QKeyEvent * event)132 void dooble_style_sheet::keyPressEvent(QKeyEvent *event)
133 {
134 if(event && event->key() == Qt::Key_Escape)
135 close();
136
137 QDialog::keyPressEvent(event);
138 }
139
populate(void)140 void dooble_style_sheet::populate(void)
141 {
142 if(!m_web_engine_page)
143 return;
144
145 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
146 m_ui.names->clear();
147
148 QMapIterator<QPair<QString, QUrl>, QString> it(s_style_sheets);
149
150 while(it.hasNext())
151 {
152 it.next();
153
154 if(it.key().second == m_web_engine_page->simplified_url())
155 m_ui.names->addItem(it.key().first);
156 }
157
158 m_ui.names->sortItems();
159 m_ui.names->setCurrentRow(0);
160 QApplication::restoreOverrideCursor();
161 }
162
purge(void)163 void dooble_style_sheet::purge(void)
164 {
165 s_style_sheets.clear();
166
167 auto database_name(dooble_database_utilities::database_name());
168
169 {
170 auto db = QSqlDatabase::addDatabase("QSQLITE", database_name);
171
172 db.setDatabaseName(dooble_settings::setting("home_path").toString() +
173 QDir::separator() +
174 "dooble_style_sheets.db");
175
176 if(db.open())
177 {
178 QSqlQuery query(db);
179
180 query.exec("PRAGMA synchronous = OFF");
181 query.exec("DELETE FROM dooble_style_sheets");
182 query.exec("VACUUM");
183 }
184
185 db.close();
186 }
187
188 QSqlDatabase::removeDatabase(database_name);
189 }
190
slot_add(void)191 void dooble_style_sheet::slot_add(void)
192 {
193 if(!m_web_engine_page)
194 return;
195
196 auto name(m_ui.name->text().trimmed());
197
198 if(m_ui.style_sheet->toPlainText().trimmed().isEmpty() || name.isEmpty())
199 return;
200
201 auto style_sheet
202 (QString::fromLatin1("(function() {"
203 "css = document.createElement('style');"
204 "css.id = '%1';"
205 "css.type = 'text/css';"
206 "css.innerText = '%2';"
207 "document.head.appendChild(css);"
208 "})()").
209 arg(name).
210 arg(m_ui.style_sheet->toPlainText().trimmed()));
211 QWebEngineScript web_engine_script;
212
213 web_engine_script.setInjectionPoint(QWebEngineScript::DocumentReady);
214 web_engine_script.setName(name);
215 web_engine_script.setRunsOnSubFrames(true);
216 web_engine_script.setSourceCode(style_sheet);
217 web_engine_script.setWorldId(QWebEngineScript::ApplicationWorld);
218
219 if(m_ui.names->findItems(name, Qt::MatchExactly).isEmpty())
220 {
221 m_ui.names->addItem(name);
222 m_ui.names->sortItems();
223 }
224
225 m_ui.style_sheet->setPlainText(m_ui.style_sheet->toPlainText().trimmed());
226 m_web_engine_page->runJavaScript
227 (style_sheet, QWebEngineScript::ApplicationWorld);
228 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
229 m_web_engine_page->scripts().remove
230 (m_web_engine_page->scripts().findScript(name));
231 #else
232 foreach(const auto &script, m_web_engine_page->scripts().find(name))
233 m_web_engine_page->scripts().remove(script);
234 #endif
235 m_web_engine_page->scripts().insert(web_engine_script);
236 s_style_sheets
237 [QPair<QString, QUrl> (name, m_web_engine_page->simplified_url())] =
238 m_ui.style_sheet->toPlainText().trimmed();
239
240 if(!dooble::s_cryptography || !dooble::s_cryptography->authenticated())
241 return;
242
243 auto database_name(dooble_database_utilities::database_name());
244
245 {
246 auto db = QSqlDatabase::addDatabase("QSQLITE", database_name);
247
248 db.setDatabaseName(dooble_settings::setting("home_path").toString() +
249 QDir::separator() +
250 "dooble_style_sheets.db");
251
252 if(db.open())
253 {
254 QSqlQuery query(db);
255
256 query.exec("CREATE TABLE IF NOT EXISTS dooble_style_sheets ("
257 "name TEXT NOT NULL, "
258 "name_digest TEXT NOT NULL, "
259 "style_sheet TEXT NOT NULL, "
260 "url TEXT NOT NULL, "
261 "url_digest TEXT NOT NULL, "
262 "PRIMARY KEY(name_digest, url_digest))");
263 query.prepare
264 ("INSERT OR REPLACE INTO dooble_style_sheets "
265 "(name, name_digest, style_sheet, url, url_digest) "
266 "VALUES (?, ?, ?, ?, ?)");
267
268 QByteArray bytes;
269
270 bytes = dooble::s_cryptography->encrypt_then_mac(name.toUtf8());
271
272 if(!bytes.isEmpty())
273 query.addBindValue(bytes.toBase64());
274 else
275 goto done_label;
276
277 query.addBindValue
278 (dooble::s_cryptography->hmac(name.toUtf8()).toBase64());
279 bytes = dooble::s_cryptography->encrypt_then_mac
280 (m_ui.style_sheet->toPlainText().trimmed().toLatin1());
281
282 if(!bytes.isEmpty())
283 query.addBindValue(bytes.toBase64());
284 else
285 goto done_label;
286
287 bytes = dooble::s_cryptography->encrypt_then_mac
288 (m_web_engine_page->simplified_url().toEncoded());
289
290 if(!bytes.isEmpty())
291 query.addBindValue(bytes.toBase64());
292 else
293 goto done_label;
294
295 query.addBindValue
296 (dooble::s_cryptography->
297 hmac(m_web_engine_page->simplified_url().toEncoded()).toBase64());
298 query.exec();
299 }
300
301 done_label:
302 db.close();
303 }
304
305 QSqlDatabase::removeDatabase(database_name);
306 }
307
slot_item_selection_changed(void)308 void dooble_style_sheet::slot_item_selection_changed(void)
309 {
310 auto list(m_ui.names->selectedItems());
311
312 if(list.isEmpty() || !list.at(0) || !m_web_engine_page)
313 {
314 m_ui.name->clear();
315 m_ui.style_sheet->clear();
316 return;
317 }
318
319 m_ui.name->setText(list.at(0)->text());
320 m_ui.style_sheet->setPlainText
321 (s_style_sheets.
322 value(QPair<QString, QUrl> (m_ui.name->text(),
323 m_web_engine_page->simplified_url())));
324 }
325
slot_populate(void)326 void dooble_style_sheet::slot_populate(void)
327 {
328 if(!dooble::s_cryptography || !dooble::s_cryptography->authenticated())
329 return;
330
331 auto database_name(dooble_database_utilities::database_name());
332
333 {
334 auto db = QSqlDatabase::addDatabase("QSQLITE", database_name);
335
336 db.setDatabaseName(dooble_settings::setting("home_path").toString() +
337 QDir::separator() +
338 "dooble_style_sheets.db");
339
340 if(db.open())
341 {
342 QSqlQuery query(db);
343
344 query.setForwardOnly(true);
345
346 if(query.exec("SELECT name, style_sheet, url, OID FROM "
347 "dooble_style_sheets"))
348 while(query.next())
349 {
350 auto name
351 (QByteArray::fromBase64(query.value(0).toByteArray()));
352 auto style_sheet
353 (QByteArray::fromBase64(query.value(1).toByteArray()));
354 auto url
355 (QByteArray::fromBase64(query.value(2).toByteArray()));
356
357 name = dooble::s_cryptography->mac_then_decrypt(name);
358 style_sheet = dooble::s_cryptography->mac_then_decrypt
359 (style_sheet);
360 url = dooble::s_cryptography->mac_then_decrypt(url);
361
362 if(name.isEmpty() || style_sheet.isEmpty() || url.isEmpty())
363 {
364 dooble_database_utilities::remove_entry
365 (db, "dooble_style_sheets", query.value(3).toLongLong());
366 continue;
367 }
368
369 s_style_sheets
370 [QPair<QString, QUrl> (name, QUrl::fromEncoded(url))] =
371 style_sheet;
372 }
373 }
374
375 db.close();
376 }
377
378 QSqlDatabase::removeDatabase(database_name);
379 emit populated();
380 }
381
slot_remove(void)382 void dooble_style_sheet::slot_remove(void)
383 {
384 if(!m_web_engine_page)
385 return;
386
387 auto list(m_ui.names->selectedItems());
388
389 if(list.isEmpty() || !list.at(0))
390 return;
391
392 auto name(list.at(0)->text());
393 auto style_sheet
394 (QString::fromLatin1("(function() {"
395 "var element = document.getElementById('%1');"
396 "if(element) element.outerHTML = '';"
397 "delete element;})()").arg(name));
398
399 delete m_ui.names->takeItem(m_ui.names->row(list.at(0)));
400 m_web_engine_page->runJavaScript
401 (style_sheet, QWebEngineScript::ApplicationWorld);
402 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
403 m_web_engine_page->scripts().remove
404 (m_web_engine_page->scripts().findScript(name));
405 #else
406 foreach(const auto &script, m_web_engine_page->scripts().find(name))
407 m_web_engine_page->scripts().remove(script);
408 #endif
409 s_style_sheets.remove
410 (QPair<QString, QUrl> (name, m_web_engine_page->simplified_url()));
411
412 if(!dooble::s_cryptography || !dooble::s_cryptography->authenticated())
413 return;
414
415 auto database_name(dooble_database_utilities::database_name());
416
417 {
418 auto db = QSqlDatabase::addDatabase("QSQLITE", database_name);
419
420 db.setDatabaseName(dooble_settings::setting("home_path").toString() +
421 QDir::separator() +
422 "dooble_style_sheets.db");
423
424 if(db.open())
425 {
426 QSqlQuery query(db);
427
428 query.prepare("DELETE FROM dooble_style_sheets WHERE "
429 "name_digest = ? AND url_digest = ?");
430 query.addBindValue
431 (dooble::s_cryptography->hmac(name.toUtf8()).toBase64());
432 query.addBindValue
433 (dooble::s_cryptography->
434 hmac(m_web_engine_page->simplified_url().toEncoded()).toBase64());
435 query.exec();
436 }
437
438 db.close();
439 }
440
441 QSqlDatabase::removeDatabase(database_name);
442 }
443