+/*
+ * Copyright 2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc/>.
+ *
+ * Privacy Browser PC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser PC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser PC. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "FilterListHelper.h"
+
+// Qt toolkit headers.
+#include <QDebug>
+#include <QFile>
+#include <QTextStream>
+
+// KDE Framework headers.
+#include <KLocalizedString>
+
+// Construct the class.
+FilterListHelper::FilterListHelper()
+{
+ // Populate the translated disposition strings. Translated entries cannot be public static const.
+ DEFAULT_STRING = i18nc("Default disposition", "Default - Allowed");
+ ALLOWED_STRING = i18nc("Allowed disposition", "Allowed");
+ BLOCKED_STRING = i18nc("Blocked disposition", "Blocked");
+
+ // Populate the translated navigation type strings. Translated entries cannot be public static const.
+ NAVIGATION_TYPE_LINK = i18nc("Navigation type link", "Link");
+ NAVIGATION_TYPE_TYPED = i18nc("Navigation type typed", "Typed");
+ NAVIGATION_TYPE_FORM_SUBMITTED = i18nc("Navigation type form submitted", "Form Submitted");
+ NAVIGATION_TYPE_BACK_FORWARD = i18nc("Navigation type back/forward", "Back/Forward");
+ NAVIGATION_TYPE_RELOAD = i18nc("Navigation type reload", "Reload");
+ NAVIGATION_TYPE_REDIRECT = i18nc("Navigation type redirect", "Redirect");
+ NAVIGATION_TYPE_OTHER = i18nc("Navigation type other", "Other");
+
+ // Populate the translated resource type strings. Translated entries cannot be public static const.
+ RESOURCE_TYPE_MAIN_FRAME = i18nc("Resource type main frame", "Main Frame");
+ RESOURCE_TYPE_SUB_FRAME = i18nc("Resource type sub frame", "Sub Frame");
+ RESOURCE_TYPE_STYLESHEET = i18nc("Resource type stylesheet", "Stylesheet");
+ RESOURCE_TYPE_SCRIPT = i18nc("Resource type script", "Script");
+ RESOURCE_TYPE_IMAGE = i18nc("Resource type image", "Image");
+ RESOURCE_TYPE_FONT_RESOURCE = i18nc("Resource type font", "Font");
+ RESOURCE_TYPE_SUB_RESOURCE = i18nc("Resource type sub resource", "Sub Resource");
+ RESOURCE_TYPE_OBJECT = i18nc("Resource type object", "Object");
+ RESOURCE_TYPE_MEDIA = i18nc("Resource type media", "Media");
+ RESOURCE_TYPE_WORKER = i18nc("Resource type worker", "Worker");
+ RESOURCE_TYPE_SHARED_WORKER = i18nc("Resource type shared worker", "Shared Worker");
+ RESOURCE_TYPE_PREFETCH = i18nc("Resource type prefetch", "Prefetch");
+ RESOURCE_TYPE_FAVICON = i18nc("Resource type favicon", "Favicon");
+ RESOURCE_TYPE_XHR = i18nc("Resource type XML HTTP request", "XML HTTP Request");
+ RESOURCE_TYPE_PING = i18nc("Resource type HTTP ping", "HTTP Ping");
+ RESOURCE_TYPE_SERVICE_WORKER = i18nc("Resource type service worker", "Service Worker");
+ RESOURCE_TYPE_CSP_REPORT = i18nc("Resource type content security policy report", "Content Security Policy Report");
+ RESOURCE_TYPE_PLUGIN_RESOURCE = i18nc("Resource type plugin request", "Plugin Request");
+ RESOURCE_TYPE_NAVIGATION_PRELOAD_MAIN_FRAME = i18nc("Resource type preload main frame", "Preload Main Frame");
+ RESOURCE_TYPE_NAVIGATION_PRELOAD_SUB_FRAME = i18nc("Resource type preload sub frame", "Preload Sub Frame");
+ RESOURCE_TYPE_UNKNOWN = i18nc("Resource type unknown", "Unknown");
+
+ // Populate the translated sublist strings. Translated entries cannot be public static const.
+ MAIN_BLOCKLIST_STRING = i18nc("Main blocklist sublist", "Main Block List");
+
+ // Populate the filter lists.
+ ultraListStructPointer = populateFilterList(QLatin1String(":/filterlists/ultralist.txt"));
+ ultraPrivacyStructPointer = populateFilterList(QLatin1String(":/filterlists/ultraprivacy.txt"));
+ easyListStructPointer = populateFilterList(QLatin1String(":/filterlists/easylist.txt"));
+ easyPrivacyStructPointer = populateFilterList(QLatin1String(":/filterlists/easyprivacy.txt"));
+ fanboyAnnoyanceStructPointer = populateFilterList(QLatin1String(":/filterlists/fanboy-annoyance.txt"));
+}
+
+bool FilterListHelper::checkFilterLists(QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer) const
+{
+ // Initiate a status tracker. If the tracker changes to false, all process of the request will be stopped.
+ bool status = true;
+
+ // Check UltraList.
+ status = checkIndividualList(urlRequestInfo, requestStructPointer, ultraListStructPointer);
+
+ // check UltraPrivacy if the status is still true.
+ if (status) {
+ status = checkIndividualList(urlRequestInfo, requestStructPointer, ultraPrivacyStructPointer);
+ }
+
+ // Return the status.
+ return status;
+}
+
+bool FilterListHelper::checkIndividualList(QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer, FilterListStruct *filterListStruct) const
+{
+ // Get the request URL.
+ QUrl url = urlRequestInfo.requestUrl();
+
+ // Get the request URL string.
+ QString urlString = url.toString();
+
+ // Check the main block list.
+ for (auto filterListEntry = filterListStruct->mainBlockList.begin(); filterListEntry != filterListStruct->mainBlockList.end(); ++filterListEntry) {
+ // Get the entry struct.
+ EntryStruct *entryStructPointer = *filterListEntry;
+
+ // Check if the URL string contains the applied entry
+ if (urlString.contains(entryStructPointer->appliedEntry)) {
+ // Block the request.
+ urlRequestInfo.block(true);
+
+ // Populate the request struct.
+ populateRequestStruct(requestStructPointer, BLOCKED, filterListStruct->title, MAIN_BLOCKLIST, entryStructPointer->appliedEntry, entryStructPointer->originalEntry);
+
+ // Log the block.
+ //qDebug().noquote().nospace() << "Blocked request: " << urlString << ", Filter list entry: " << entryStructPointer->appliedEntry;
+
+ // Returning `false` stops all processing of the request.
+ return false;
+ }
+ }
+
+ // Return `true` to continue processing the URL request.
+ return true;
+}
+
+QString FilterListHelper::getDispositionString(int dispositionInt) const
+{
+ // Return the translated disposition string.
+ switch (dispositionInt)
+ {
+ case ALLOWED: return ALLOWED_STRING;
+ case BLOCKED: return BLOCKED_STRING;
+ default: return DEFAULT_STRING;
+ }
+}
+
+QString FilterListHelper::getNavigationTypeString(int navigationTypeInt) const
+{
+ // Return the translated navigation type string.
+ switch (navigationTypeInt)
+ {
+ case QWebEngineUrlRequestInfo::NavigationTypeLink: return NAVIGATION_TYPE_LINK;
+ case QWebEngineUrlRequestInfo::NavigationTypeTyped: return NAVIGATION_TYPE_TYPED;
+ case QWebEngineUrlRequestInfo::NavigationTypeFormSubmitted: return NAVIGATION_TYPE_FORM_SUBMITTED;
+ case QWebEngineUrlRequestInfo::NavigationTypeBackForward: return NAVIGATION_TYPE_BACK_FORWARD;
+ case QWebEngineUrlRequestInfo::NavigationTypeReload: return NAVIGATION_TYPE_RELOAD;
+ case QWebEngineUrlRequestInfo::NavigationTypeRedirect: return NAVIGATION_TYPE_REDIRECT;
+ default: return NAVIGATION_TYPE_OTHER;
+ }
+}
+
+QString FilterListHelper::getResourceTypeString(int resourceTypeInt) const
+{
+ // Return the translated resource type string.
+ switch (resourceTypeInt)
+ {
+ case QWebEngineUrlRequestInfo::ResourceTypeMainFrame: return RESOURCE_TYPE_MAIN_FRAME;
+ case QWebEngineUrlRequestInfo::ResourceTypeSubFrame: return RESOURCE_TYPE_SUB_FRAME;
+ case QWebEngineUrlRequestInfo::ResourceTypeStylesheet: return RESOURCE_TYPE_STYLESHEET;
+ case QWebEngineUrlRequestInfo::ResourceTypeScript: return RESOURCE_TYPE_SCRIPT;
+ case QWebEngineUrlRequestInfo::ResourceTypeImage: return RESOURCE_TYPE_IMAGE;
+ case QWebEngineUrlRequestInfo::ResourceTypeFontResource: return RESOURCE_TYPE_FONT_RESOURCE;
+ case QWebEngineUrlRequestInfo::ResourceTypeSubResource: return RESOURCE_TYPE_SUB_RESOURCE;
+ case QWebEngineUrlRequestInfo::ResourceTypeObject: return RESOURCE_TYPE_OBJECT;
+ case QWebEngineUrlRequestInfo::ResourceTypeMedia: return RESOURCE_TYPE_MEDIA;
+ case QWebEngineUrlRequestInfo::ResourceTypeWorker: return RESOURCE_TYPE_WORKER;
+ case QWebEngineUrlRequestInfo::ResourceTypeSharedWorker: return RESOURCE_TYPE_SHARED_WORKER;
+ case QWebEngineUrlRequestInfo::ResourceTypePrefetch: return RESOURCE_TYPE_PREFETCH;
+ case QWebEngineUrlRequestInfo::ResourceTypeFavicon: return RESOURCE_TYPE_FAVICON;
+ case QWebEngineUrlRequestInfo::ResourceTypeXhr: return RESOURCE_TYPE_XHR;
+ case QWebEngineUrlRequestInfo::ResourceTypePing: return RESOURCE_TYPE_PING;
+ case QWebEngineUrlRequestInfo::ResourceTypeServiceWorker: return RESOURCE_TYPE_SERVICE_WORKER;
+ case QWebEngineUrlRequestInfo::ResourceTypeCspReport: return RESOURCE_TYPE_CSP_REPORT;
+ case QWebEngineUrlRequestInfo::ResourceTypePluginResource: return RESOURCE_TYPE_PLUGIN_RESOURCE;
+ case QWebEngineUrlRequestInfo::ResourceTypeNavigationPreloadMainFrame: return RESOURCE_TYPE_NAVIGATION_PRELOAD_MAIN_FRAME;
+ case QWebEngineUrlRequestInfo::ResourceTypeNavigationPreloadSubFrame: return RESOURCE_TYPE_NAVIGATION_PRELOAD_SUB_FRAME;
+ default: return RESOURCE_TYPE_UNKNOWN;
+ }
+}
+
+QString FilterListHelper::getSublistName(int sublistInt) const
+{
+ // Return the name of the requested sublist.
+ switch (sublistInt)
+ {
+ case MAIN_BLOCKLIST: return MAIN_BLOCKLIST_STRING;
+ default: return QString(); // The default return should never be reached.
+ }
+}
+
+FilterListStruct* FilterListHelper::populateFilterList(const QString &filterListFileName) const
+{
+ // Get the filter list file.
+ QFile filterListFile(filterListFileName);
+
+ // Open the filter list file.
+ filterListFile.open(QIODevice::ReadOnly);
+
+ // Create a filter list text stream.
+ QTextStream filterListTextStream(&filterListFile);
+
+ // Create a filter list struct.
+ FilterListStruct *filterListStructPointer = new FilterListStruct;
+
+ // Populate the filter list file name.
+ filterListStructPointer->filePath = filterListFileName;
+
+ // Create a filter list string.
+ QString filterListString;
+
+ // Process each line of the filter list.
+ while (filterListTextStream.readLineInto(&filterListString)) {
+ // Create an entry struct.
+ EntryStruct *entryStructPointer = new EntryStruct;
+
+ // Store the original entry.
+ entryStructPointer->originalEntry = filterListString;
+
+ // Process the entry.
+ if (filterListString.startsWith(QLatin1Char('['))) { // The line starts with `[`, which is the file format.
+ // Do nothing.
+
+ // Log the dropping of the line.
+ //qDebug().noquote().nospace() << filterListString << " NOT added from " << filterListFileName;
+ } else if (filterListString.startsWith(QLatin1Char('!'))) { // The line starts with `!`, which are comments.
+ if (filterListString.startsWith(QLatin1String("! Title: "))) // The line contains the title.
+ {
+ // Add the title to the filter list struct.
+ filterListStructPointer->title = filterListString.remove(0, 9);
+
+ // Log the addition of the filter list title.
+ //qDebug().noquote().nospace() << "Filter list title: " << filterListString << " added from " << filterListFileName;
+ }
+ else if (filterListString.startsWith(QLatin1String("! Version: "))) // The line contains the version.
+ {
+ // Add the version to the filter list struct.
+ filterListStructPointer->version = filterListString.remove(0, 11);
+
+ // Log the addition of the filter list version.
+ //qDebug().noquote().nospace() << "Filter list version: " << filterListString << " added from " << filterListFileName;
+ }
+
+ // Else do nothing.
+
+ // Log the dropping of the line.
+ //qDebug().noquote().nospace() << originalFilterListString << " NOT added from " << filterListFileName;
+ } else { // Process the entry.
+ // Add the applied entry to the struct.
+ entryStructPointer->appliedEntry = filterListString;
+
+ // Add the filter list entry struct to the main block list.
+ filterListStructPointer->mainBlockList.push_front(entryStructPointer);
+
+ // Log the addition to the filter list.
+ //qDebug().noquote().nospace() << originalFilterListString << " added from " << filterListFileName;
+ }
+ }
+
+ // Close the filter list file.
+ filterListFile.close();
+
+ // Return the filter list pair.
+ return filterListStructPointer;
+}
+
+void FilterListHelper::populateRequestStruct(RequestStruct *requestStructPointer, const int disposition, const QString &filterListTitle, const int sublistInt, const QString &appliedEntry,
+ const QString &originalEntry) const
+{
+ // Populate the request struct.
+ requestStructPointer->dispositionInt = disposition;
+ requestStructPointer->filterListTitle = filterListTitle;
+ requestStructPointer->sublistInt = sublistInt;
+ requestStructPointer->entryStruct.appliedEntry = appliedEntry;
+ requestStructPointer->entryStruct.originalEntry = originalEntry;
+}