Implement manual adding of cookies.
[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 "filters/MouseEventFilter.h"
25 #include "helpers/DomainsDatabaseHelper.h"
26 #include "helpers/SearchEngineHelper.h"
27 #include "helpers/UserAgentHelper.h"
28 #include "interceptors/UrlRequestInterceptor.h"
29 #include "windows/BrowserWindow.h"
30
31 // Qt framework headers.
32 #include <QAction>
33
34 // Initialize the public static variables.
35 QString BrowserView::webEngineDefaultUserAgent = QStringLiteral("");
36
37 BrowserView::BrowserView(QWidget *parent) : QWidget(parent)
38 {
39     // Instantiate the browser view UI.
40     Ui::BrowserView browserViewUi;
41
42     // Setup the UI.
43     browserViewUi.setupUi(this);
44
45     // Get handles for the views.
46     webEngineViewPointer = browserViewUi.webEngineView;
47
48     // Create an off-the-record profile (the default when no profile name is specified).
49     webEngineProfilePointer = new QWebEngineProfile(QStringLiteral(""));
50
51     // Create a WebEngine page.
52     webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
53
54     // Set the WebEngine page.
55     webEngineViewPointer->setPage(webEnginePagePointer);
56
57     // Get handles for the aspects of the WebEngine.
58     webEngineHistoryPointer = webEnginePagePointer->history();
59     webEngineSettingsPointer = webEngineViewPointer->settings();
60     webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
61
62     // Store a copy of each cookie when it is added.
63     connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), this, SLOT(cookieAdded(QNetworkCookie)));
64
65     // Store a copy of the WebEngine default user agent.
66     webEngineDefaultUserAgent = webEngineProfilePointer->httpUserAgent();
67
68     // Update the URL line edit when the URL changes.
69     connect(webEngineViewPointer, SIGNAL(urlChanged(const QUrl)), this, SLOT(updateUrl(const QUrl)));
70
71     // Update the progress bar.
72     connect(webEngineViewPointer, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
73     connect(webEngineViewPointer, SIGNAL(loadProgress(const int)), this, SLOT(loadProgress(const int)));
74     connect(webEngineViewPointer, SIGNAL(loadFinished(const bool)), this, SLOT(loadFinished()));
75
76     // Instantiate the mouse event filter pointer.
77     MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
78
79     // Install the mouse event filter.
80     qApp->installEventFilter(mouseEventFilterPointer);
81
82     // Process mouse forward and back commands.
83     connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
84     connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
85
86     // Listen for hovered link URLs.
87     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
88
89     // Instantiate the URL request interceptor.
90     UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
91
92     // Set the URL request interceptor.
93     webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
94
95     // Reapply the domain settings when the host changes.
96     connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
97
98     // Don't allow JavaScript to open windows.
99     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
100
101     // Allow keyboard navigation.
102     webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
103
104     // Enable full screen support.
105     webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
106
107     // Require user interaction to play media.
108     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
109
110     // Limit WebRTC to public IP addresses.
111     webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
112
113     // Set the focus on the WebEngine view.
114     webEngineViewPointer->setFocus();
115 }
116
117 BrowserView::~BrowserView()
118 {
119     // Delay the deletion of the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
120     webEnginePagePointer->deleteLater();
121 }
122
123 // 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.
124 void BrowserView::addCookieToStore(QNetworkCookie cookie) const
125 {
126     // Create a url.
127     QUrl url;
128
129     // 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>
130     if (!cookie.domain().startsWith(QStringLiteral(".")))
131     {
132         // Populate the URL.
133         url.setHost(cookie.domain());
134         url.setScheme(QStringLiteral("https"));
135
136         // Clear the domain from the cookie.
137         cookie.setDomain(QStringLiteral(""));
138     }
139
140     // Add the cookie to the store.
141     webEngineCookieStorePointer->setCookie(cookie, url);
142 }
143
144 void BrowserView::applyApplicationSettings()
145 {
146     // Set the search engine URL.
147     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
148
149     // Emit the update search engine actions signal.
150     emit updateSearchEngineActions(Settings::searchEngine());
151 }
152
153 // 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.
154 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
155 void BrowserView::applyDomainSettingsAndReload()
156 {
157     // Apply the domain settings.  `true` reloads the website.
158     applyDomainSettings(webEngineViewPointer->url().host(), true);
159 }
160
161 // 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.
162 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
163 void BrowserView::applyDomainSettingsWithoutReloading(const QString &hostname)
164 {
165     // Apply the domain settings  `false` does not reload the website.
166     applyDomainSettings(hostname, false);
167 }
168
169 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
170 void BrowserView::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
171 {
172     // Get the record for the hostname.
173     QSqlQuery domainQuery = DomainsDatabaseHelper::getDomainQuery(hostname);
174
175     // Check if the hostname has domain settings.
176     if (domainQuery.isValid())  // The hostname has domain settings.
177     {
178         // Get the domain record.
179         QSqlRecord domainRecord = domainQuery.record();
180
181         // Set the JavaScript status.
182         switch (domainRecord.field(DomainsDatabaseHelper::JAVASCRIPT).value().toInt())
183         {
184             case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
185             {
186                 // Set the default JavaScript status.
187                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScript());
188
189                 break;
190             }
191
192             case (DomainsDatabaseHelper::DISABLED):
193             {
194                 // Disable JavaScript.
195                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
196
197                 break;
198             }
199
200             case (DomainsDatabaseHelper::ENABLED):
201             {
202                 // Enable JavaScript.
203                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
204
205                 break;
206             }
207         }
208
209         // Set local storage.
210         switch (domainRecord.field(DomainsDatabaseHelper::LOCAL_STORAGE).value().toInt())
211         {
212             case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
213             {
214                 // Set the default local storage status.
215                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::localStorage());
216
217                 break;
218             }
219
220             case (DomainsDatabaseHelper::DISABLED):
221             {
222                 // Disable local storage.
223                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
224
225                 break;
226             }
227
228             case (DomainsDatabaseHelper::ENABLED):
229             {
230                 // Enable local storage.
231                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
232
233                 break;
234             }
235         }
236
237         // Set the user agent.
238         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabaseHelper::USER_AGENT).value().toString()));
239
240         // Check if a custom zoom factor is set.
241         if (domainRecord.field(DomainsDatabaseHelper::ZOOM_FACTOR).value().toInt())
242         {
243             // Store the current zoom factor.
244             currentZoomFactor = domainRecord.field(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR).value().toDouble();
245         }
246         else
247         {
248             // Reset the current zoom factor.
249             currentZoomFactor = Settings::zoomFactor();
250         }
251
252         // Set the zoom factor.    The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
253         webEngineViewPointer->setZoomFactor(currentZoomFactor);
254
255         // Apply the domain settings palette to the URL line edit.
256         emit updateDomainSettingsIndicator(true, domainRecord.field(DomainsDatabaseHelper::DOMAIN_NAME).value().toString());
257     }
258     else  // The hostname does not have domain settings.
259     {
260         // Set the JavaScript status.
261         webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScript());
262
263         // Set local storage.
264         webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::localStorage());
265
266         // Set the user agent.
267         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
268
269         // Store the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
270         currentZoomFactor = Settings::zoomFactor();
271
272         // Set the zoom factor.
273         webEngineViewPointer->setZoomFactor(Settings::zoomFactor());
274
275         // Apply the no domain settings palette to the URL line edit.
276         emit updateDomainSettingsIndicator(false, QStringLiteral(""));
277     }
278
279     // Emit the update actions signals.
280     emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
281     emit updateLocalStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
282     emit updateUserAgentActions(webEngineProfilePointer->httpUserAgent());
283     emit updateZoomFactorAction(webEngineViewPointer->zoomFactor());
284
285     // Reload the website if requested.
286     if (reloadWebsite)
287     {
288         webEngineViewPointer->reload();
289     }
290 }
291
292 void BrowserView::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
293 {
294     // Store the search engine name.
295     QString searchEngineName = searchEngineActionPointer->text();
296
297     // Strip out any `&` characters.
298     searchEngineName.remove('&');
299
300     // Store the search engine string.
301     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
302 }
303
304 void BrowserView::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
305 {
306     // Get the user agent name.
307     QString userAgentName = userAgentActionPointer->text();
308
309     // Strip out any `&` characters.
310     userAgentName.remove('&');
311
312     // Apply the user agent.
313     webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
314
315     // Reload the website.
316     webEngineViewPointer->reload();
317 }
318
319 // This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
320 void BrowserView::applyOnTheFlyZoomFactor(const double &zoomFactor)
321 {
322     // Update the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
323     currentZoomFactor = zoomFactor;
324
325     // Set the zoom factor.
326     webEngineViewPointer->setZoomFactor(zoomFactor);
327 }
328
329 void BrowserView::back() const
330 {
331     // Go back.
332     webEngineViewPointer->back();
333 }
334
335 void BrowserView::cookieAdded(const QNetworkCookie &cookie) const
336 {
337     // Add the cookie to the cookie list.
338     emit addCookie(cookie);
339 }
340
341 void BrowserView::deleteAllCookies() const
342 {
343     // Delete all the cookies.
344     webEngineCookieStorePointer->deleteAllCookies();
345 }
346
347 void BrowserView::forward() const
348 {
349     // Go forward.
350     webEngineViewPointer->forward();
351 }
352
353 void BrowserView::home() const
354 {
355     // Load the homepage.
356     webEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
357 }
358
359 void BrowserView::loadFinished() const
360 {
361     // Hide the progress bar.
362     emit hideProgressBar();
363 }
364
365 void BrowserView::loadInitialWebsite()
366 {
367     // Apply the application settings.
368     applyApplicationSettings();
369
370     // Get the arguments.
371     QStringList argumentsStringList = qApp->arguments();
372
373     // Check to see if the arguments lists contains a URL.
374     if (argumentsStringList.size() > 1)
375     {
376         // Load the URL from the arguments list.
377         webEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
378     }
379     else
380     {
381         // Load the homepage.
382         home();
383     }
384 }
385
386 void BrowserView::loadProgress(const int &progress) const
387 {
388     // Show the progress bar.
389     emit showProgressBar(progress);
390 }
391
392 void BrowserView::loadStarted() const
393 {
394     // Show the progress bar.
395     emit showProgressBar(0);
396 }
397
398 void BrowserView::loadUrlFromLineEdit(QString url) const
399 {
400     // Decide if the text is more likely to be a URL or a search.
401     if (url.startsWith("file://"))  // The text is likely a file URL.
402     {
403         // Load the URL.
404         webEngineViewPointer->load(QUrl::fromUserInput(url));
405     }
406     else if (url.contains("."))  // The text is likely a URL.
407     {
408         // Check if the URL does not start with a valid protocol.
409         if (!url.startsWith("http"))
410         {
411             // Add `https://` to the beginning of the URL.
412             url = "https://" + url;
413         }
414
415         // Load the URL.
416         webEngineViewPointer->load(QUrl::fromUserInput(url));
417     }
418     else  // The text is likely a search.
419     {
420         // Load the search.
421         webEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
422     }
423 }
424
425 void BrowserView::mouseBack() const
426 {
427     // Go back if possible.
428     if (webEngineHistoryPointer->canGoBack())
429     {
430         // Clear the URL line edit focus.
431         emit clearUrlLineEditFocus();
432
433         // Go back.
434         webEngineViewPointer->back();
435     }
436 }
437
438 void BrowserView::mouseForward() const
439 {
440     // Go forward if possible.
441     if (webEngineHistoryPointer->canGoForward())
442     {
443         // Clear the URL line edit focus.
444         emit clearUrlLineEditFocus();
445
446         // Go forward.
447         webEngineViewPointer->forward();
448     }
449 }
450
451 void BrowserView::pageLinkHovered(const QString &linkUrl) const
452 {
453     // Emit a signal so that the browser window can update the status bar.
454     emit linkHovered(linkUrl);
455 }
456
457 void BrowserView::refresh() const
458 {
459     // Reload the website.
460     webEngineViewPointer->reload();
461 }
462
463 void BrowserView::toggleJavaScript() const
464 {
465     // Toggle JavaScript.
466     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
467
468     // Update the JavaScript icon.
469     emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
470
471     // Reload the website.
472     webEngineViewPointer->reload();
473 }
474
475 void BrowserView::toggleLocalStorage() const
476 {
477     // Toggle local storage.
478     webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
479
480     // Update the local storage icon.
481     emit updateLocalStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
482
483     // Reload the website.
484     webEngineViewPointer->reload();
485 }
486
487 void BrowserView::updateUrl(const QUrl &url) const
488 {
489     // Update the URL line edit.
490     emit updateUrlLineEdit(url);
491
492     // Update the status of the forward and back buttons.
493     emit updateBackAction(webEngineHistoryPointer->canGoBack());
494     emit updateForwardAction(webEngineHistoryPointer->canGoForward());
495
496     // Reapply the zoom factor.  This is a bug in QWebEngineView that resets the zoom with every load.  <https://redmine.stoutner.com/issues/799>
497     webEngineViewPointer->setZoomFactor(currentZoomFactor);
498 }