]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/views/BrowserView.cpp
Add durable cookie support.
[PrivacyBrowserPC.git] / src / views / BrowserView.cpp
1 /*
2  * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
5  *
6  * Privacy Browser PC is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser PC is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 // Application headers.
21 #include "BrowserView.h"
22 #include "Settings.h"
23 #include "ui_BrowserView.h"
24 #include "databases/CookiesDatabase.h"
25 #include "databases/DomainsDatabase.h"
26 #include "filters/MouseEventFilter.h"
27 #include "helpers/SearchEngineHelper.h"
28 #include "helpers/UserAgentHelper.h"
29 #include "interceptors/UrlRequestInterceptor.h"
30 #include "windows/BrowserWindow.h"
31
32 // Qt framework headers.
33 #include <QAction>
34
35 // Initialize the public static variables.
36 QString BrowserView::webEngineDefaultUserAgent = QStringLiteral("");
37
38 // Construct the class.
39 BrowserView::BrowserView(QWidget *parent) : QWidget(parent)
40 {
41     // Initialize the variables.
42     privacyWebEngineListPointer = new QList<PrivacyWebEngine*>;
43
44     // Instantiate the browser view UI.
45     Ui::BrowserView browserViewUi;
46
47     // Setup the UI.
48     browserViewUi.setupUi(this);
49
50     // Get handles for the views.
51     webEngineViewPointer = browserViewUi.webEngineView;
52
53     // Create an off-the-record profile (the default when no profile name is specified).
54     webEngineProfilePointer = new QWebEngineProfile(QStringLiteral(""));
55
56     // Create a WebEngine page.
57     webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
58
59     // Set the WebEngine page.
60     webEngineViewPointer->setPage(webEnginePagePointer);
61
62     // Get handles for the aspects of the WebEngine.
63     webEngineHistoryPointer = webEnginePagePointer->history();
64     webEngineSettingsPointer = webEngineViewPointer->settings();
65     webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
66
67     // Initialize the current privacy web engine pointer.
68     currentPrivacyWebEnginePointer = new PrivacyWebEngine(webEngineViewPointer);
69
70     // Populate the privacy web engine list.
71     privacyWebEngineListPointer->append(currentPrivacyWebEnginePointer);
72
73     // Set the local storage filter.
74     webEngineCookieStorePointer->setCookieFilter([this](const QWebEngineCookieStore::FilterRequest &filterRequest)
75     {
76         // qDebug() << "Page URL:  " << filterRequest.firstPartyUrl << ", Local storage URL:  " << filterRequest.origin << ",  Is third-party:  " << filterRequest.thirdParty;
77
78         // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
79         if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
80             return false;
81
82         // Check each tab to see if this local storage request should be allowed.
83         for (PrivacyWebEngine *privacyWebEnginePointer : *privacyWebEngineListPointer)
84         {
85             // Allow this local storage request if it comes from a tab with local storage enabled.
86             if (privacyWebEnginePointer->localStorageEnabled && (webEngineViewPointer->url().host() == filterRequest.firstPartyUrl.host()))
87                 return true;
88         }
89
90         // Block any remaining local storage requests.
91         return false;
92     });
93
94     // Process cookie changes.
95     connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), this, SLOT(cookieAdded(QNetworkCookie)));
96     connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), this, SLOT(cookieRemoved(QNetworkCookie)));
97
98     // Get a list of durable cookies.
99     QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
100
101     // Add the durable cookies to the store.
102     for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
103         addCookieToStore(*cookiePointer);
104
105     // Store a copy of the WebEngine default user agent.
106     webEngineDefaultUserAgent = webEngineProfilePointer->httpUserAgent();
107
108     // Update the URL line edit when the URL changes.
109     connect(webEngineViewPointer, SIGNAL(urlChanged(const QUrl)), this, SLOT(updateUrl(const QUrl)));
110
111     // Update the progress bar.
112     connect(webEngineViewPointer, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
113     connect(webEngineViewPointer, SIGNAL(loadProgress(const int)), this, SLOT(loadProgress(const int)));
114     connect(webEngineViewPointer, SIGNAL(loadFinished(const bool)), this, SLOT(loadFinished()));
115
116     // Instantiate the mouse event filter pointer.
117     MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
118
119     // Install the mouse event filter.
120     qApp->installEventFilter(mouseEventFilterPointer);
121
122     // Process mouse forward and back commands.
123     connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
124     connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
125
126     // Listen for hovered link URLs.
127     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
128
129     // Instantiate the URL request interceptor.
130     UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
131
132     // Set the URL request interceptor.
133     webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
134
135     // Reapply the domain settings when the host changes.
136     connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
137
138     // Don't allow JavaScript to open windows.
139     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
140
141     // Allow keyboard navigation.
142     webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
143
144     // Enable full screen support.
145     webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
146
147     // Require user interaction to play media.
148     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
149
150     // Limit WebRTC to public IP addresses.
151     webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
152
153     // Set the focus on the WebEngine view.
154     webEngineViewPointer->setFocus();
155 }
156
157 BrowserView::~BrowserView()
158 {
159     // Delay the deletion of the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
160     webEnginePagePointer->deleteLater();
161 }
162
163 // The cookie is copied instead of referenced so that changes made to the cookie do not create a race condition with the display of the cookie in the dialog.
164 void BrowserView::addCookieToStore(QNetworkCookie cookie) const
165 {
166     // Create a url.
167     QUrl url;
168
169     // Check to see if the domain does not start with a `.` because Qt makes this harder than it should be.  <https://doc.qt.io/qt-5/qwebenginecookiestore.html#setCookie>
170     if (!cookie.domain().startsWith(QStringLiteral(".")))
171     {
172         // Populate the URL.
173         url.setHost(cookie.domain());
174         url.setScheme(QStringLiteral("https"));
175
176         // Clear the domain from the cookie.
177         cookie.setDomain(QStringLiteral(""));
178     }
179
180     // Add the cookie to the store.
181     webEngineCookieStorePointer->setCookie(cookie, url);
182 }
183
184 void BrowserView::applyApplicationSettings()
185 {
186     // Set the search engine URL.
187     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
188
189     // Emit the update search engine actions signal.
190     emit updateSearchEngineActions(Settings::searchEngine());
191 }
192
193 // This exists as a separate function from `applyDomainSettings()` so it can be listed as a slot and function without the need for a boolean argument.
194 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
195 void BrowserView::applyDomainSettingsAndReload()
196 {
197     // Apply the domain settings.  `true` reloads the website.
198     applyDomainSettings(webEngineViewPointer->url().host(), true);
199 }
200
201 // This exists as a separate function from `applyDomainSettings()` so it can be listed as a slot and function without the need for a boolean argument.
202 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
203 void BrowserView::applyDomainSettingsWithoutReloading(const QString &hostname)
204 {
205     // Apply the domain settings  `false` does not reload the website.
206     applyDomainSettings(hostname, false);
207 }
208
209 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
210 void BrowserView::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
211 {
212     // Get the record for the hostname.
213     QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
214
215     // Check if the hostname has domain settings.
216     if (domainQuery.isValid())  // The hostname has domain settings.
217     {
218         // Get the domain record.
219         QSqlRecord domainRecord = domainQuery.record();
220
221         // Set the JavaScript status.
222         switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
223         {
224             // Set the default JavaScript status.
225             case (DomainsDatabase::SYSTEM_DEFAULT):
226             {
227                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
228
229                 break;
230             }
231
232             // Disable JavaScript.
233             case (DomainsDatabase::DISABLED):
234             {
235                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
236
237                 break;
238             }
239
240             // Enable JavaScript.
241             case (DomainsDatabase::ENABLED):
242             {
243                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
244
245                 break;
246             }
247         }
248
249         // Set the local storage status.
250         switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
251         {
252             // Set the default local storage status.
253             case (DomainsDatabase::SYSTEM_DEFAULT):
254             {
255                 currentPrivacyWebEnginePointer->localStorageEnabled = Settings::localStorageEnabled();
256
257                 break;
258             }
259
260             // Disable local storage.
261             case (DomainsDatabase::DISABLED):
262             {
263                 currentPrivacyWebEnginePointer->localStorageEnabled = false;
264
265                 break;
266             }
267
268             // Enable local storage.
269             case (DomainsDatabase::ENABLED):
270             {
271                 currentPrivacyWebEnginePointer->localStorageEnabled = true;
272
273                 break;
274             }
275         }
276
277         // Set the DOM storage status.
278         switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
279         {
280             // Set the default DOM storage status.
281             case (DomainsDatabase::SYSTEM_DEFAULT):
282             {
283                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
284
285                 break;
286             }
287
288             // Disable DOM storage.
289             case (DomainsDatabase::DISABLED):
290             {
291                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
292
293                 break;
294             }
295
296             // Enable DOM storage.
297             case (DomainsDatabase::ENABLED):
298             {
299                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
300
301                 break;
302             }
303         }
304
305         // Set the user agent.
306         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
307
308         // Check if a custom zoom factor is set.
309         if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
310         {
311             // Store the current zoom factor.
312             currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
313         }
314         else
315         {
316             // Reset the current zoom factor.
317             currentZoomFactor = Settings::zoomFactor();
318         }
319
320         // Set the zoom factor.    The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
321         webEngineViewPointer->setZoomFactor(currentZoomFactor);
322
323         // Apply the domain settings palette to the URL line edit.
324         emit updateDomainSettingsIndicator(true, domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString());
325     }
326     else  // The hostname does not have domain settings.
327     {
328         // Set the JavaScript status.
329         webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
330
331         // Set the local storage status.
332         currentPrivacyWebEnginePointer->localStorageEnabled = Settings::localStorageEnabled();
333
334         // Set DOM storage.
335         webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
336
337         // Set the user agent.
338         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
339
340         // Store the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
341         currentZoomFactor = Settings::zoomFactor();
342
343         // Set the zoom factor.
344         webEngineViewPointer->setZoomFactor(Settings::zoomFactor());
345
346         // Apply the no domain settings palette to the URL line edit.
347         emit updateDomainSettingsIndicator(false, QStringLiteral(""));
348     }
349
350     // Emit the update actions signals.
351     emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
352     emit updateLocalStorageAction(currentPrivacyWebEnginePointer->localStorageEnabled);
353     emit updateDomStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
354     emit updateUserAgentActions(webEngineProfilePointer->httpUserAgent());
355     emit updateZoomFactorAction(webEngineViewPointer->zoomFactor());
356
357     // Reload the website if requested.
358     if (reloadWebsite)
359     {
360         webEngineViewPointer->reload();
361     }
362 }
363
364 void BrowserView::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
365 {
366     // Store the search engine name.
367     QString searchEngineName = searchEngineActionPointer->text();
368
369     // Strip out any `&` characters.
370     searchEngineName.remove('&');
371
372     // Store the search engine string.
373     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
374 }
375
376 void BrowserView::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
377 {
378     // Get the user agent name.
379     QString userAgentName = userAgentActionPointer->text();
380
381     // Strip out any `&` characters.
382     userAgentName.remove('&');
383
384     // Apply the user agent.
385     webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
386
387     // Reload the website.
388     webEngineViewPointer->reload();
389 }
390
391 // This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
392 void BrowserView::applyOnTheFlyZoomFactor(const double &zoomFactor)
393 {
394     // Update the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
395     currentZoomFactor = zoomFactor;
396
397     // Set the zoom factor.
398     webEngineViewPointer->setZoomFactor(zoomFactor);
399 }
400
401 void BrowserView::back() const
402 {
403     // Go back.
404     webEngineViewPointer->back();
405 }
406
407 void BrowserView::cookieAdded(const QNetworkCookie &cookie) const
408 {
409     // Add the cookie to the cookie list.
410     emit addCookie(cookie);
411 }
412
413 void BrowserView::cookieRemoved(const QNetworkCookie &cookie) const
414 {
415     // Remove the cookie from the cookie list.
416     emit removeCookie(cookie);
417 }
418
419 void BrowserView::deleteAllCookies() const
420 {
421     // Delete all the cookies.
422     webEngineCookieStorePointer->deleteAllCookies();
423 }
424
425 void BrowserView::deleteCookieFromStore(const QNetworkCookie &cookie) const
426 {
427     // Delete the cookie.
428     webEngineCookieStorePointer->deleteCookie(cookie);
429 }
430
431 void BrowserView::forward() const
432 {
433     // Go forward.
434     webEngineViewPointer->forward();
435 }
436
437 void BrowserView::home() const
438 {
439     // Load the homepage.
440     webEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
441 }
442
443 void BrowserView::loadFinished() const
444 {
445     // Hide the progress bar.
446     emit hideProgressBar();
447 }
448
449 void BrowserView::loadInitialWebsite()
450 {
451     // Apply the application settings.
452     applyApplicationSettings();
453
454     // Get the arguments.
455     QStringList argumentsStringList = qApp->arguments();
456
457     // Check to see if the arguments lists contains a URL.
458     if (argumentsStringList.size() > 1)
459     {
460         // Load the URL from the arguments list.
461         webEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
462     }
463     else
464     {
465         // Load the homepage.
466         home();
467     }
468 }
469
470 void BrowserView::loadProgress(const int &progress) const
471 {
472     // Show the progress bar.
473     emit showProgressBar(progress);
474 }
475
476 void BrowserView::loadStarted() const
477 {
478     // Show the progress bar.
479     emit showProgressBar(0);
480 }
481
482 void BrowserView::loadUrlFromLineEdit(QString url) const
483 {
484     // Decide if the text is more likely to be a URL or a search.
485     if (url.startsWith("file://"))  // The text is likely a file URL.
486     {
487         // Load the URL.
488         webEngineViewPointer->load(QUrl::fromUserInput(url));
489     }
490     else if (url.contains("."))  // The text is likely a URL.
491     {
492         // Check if the URL does not start with a valid protocol.
493         if (!url.startsWith("http"))
494         {
495             // Add `https://` to the beginning of the URL.
496             url = "https://" + url;
497         }
498
499         // Load the URL.
500         webEngineViewPointer->load(QUrl::fromUserInput(url));
501     }
502     else  // The text is likely a search.
503     {
504         // Load the search.
505         webEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
506     }
507 }
508
509 void BrowserView::mouseBack() const
510 {
511     // Go back if possible.
512     if (webEngineHistoryPointer->canGoBack())
513     {
514         // Clear the URL line edit focus.
515         emit clearUrlLineEditFocus();
516
517         // Go back.
518         webEngineViewPointer->back();
519     }
520 }
521
522 void BrowserView::mouseForward() const
523 {
524     // Go forward if possible.
525     if (webEngineHistoryPointer->canGoForward())
526     {
527         // Clear the URL line edit focus.
528         emit clearUrlLineEditFocus();
529
530         // Go forward.
531         webEngineViewPointer->forward();
532     }
533 }
534
535 void BrowserView::pageLinkHovered(const QString &linkUrl) const
536 {
537     // Emit a signal so that the browser window can update the status bar.
538     emit linkHovered(linkUrl);
539 }
540
541 void BrowserView::refresh() const
542 {
543     // Reload the website.
544     webEngineViewPointer->reload();
545 }
546
547 void BrowserView::toggleDomStorage() const
548 {
549     // Toggle DOM storage.
550     webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
551
552     // Update the DOM storage action.
553     emit updateDomStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
554
555     // Reload the website.
556     webEngineViewPointer->reload();
557 }
558
559 void BrowserView::toggleJavaScript() const
560 {
561     // Toggle JavaScript.
562     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
563
564     // Update the JavaScript action.
565     emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
566
567     // Reload the website.
568     webEngineViewPointer->reload();
569 }
570
571 void BrowserView::toggleLocalStorage()
572 {
573     // Toggle local storeage.
574     currentPrivacyWebEnginePointer->localStorageEnabled = !currentPrivacyWebEnginePointer->localStorageEnabled;
575
576     // Update the local storage action.
577     emit updateLocalStorageAction(currentPrivacyWebEnginePointer->localStorageEnabled);
578
579     // Reload the website.
580     webEngineViewPointer->reload();
581 }
582
583 void BrowserView::updateUrl(const QUrl &url) const
584 {
585     // Update the URL line edit.
586     emit updateUrlLineEdit(url);
587
588     // Update the status of the forward and back buttons.
589     emit updateBackAction(webEngineHistoryPointer->canGoBack());
590     emit updateForwardAction(webEngineHistoryPointer->canGoForward());
591
592     // Reapply the zoom factor.  This is a bug in QWebEngineView that resets the zoom with every load.  <https://redmine.stoutner.com/issues/799>
593     webEngineViewPointer->setZoomFactor(currentZoomFactor);
594 }