X-Git-Url: https://gitweb.stoutner.com/?a=blobdiff_plain;f=src%2Fhelpers%2FFilterListHelper.cpp;h=e4ce25f529d3a2cbce839fa7fcac435b6eaaa055;hb=be625d0fcb1f4e8184ed62a28d23629f8c246d8e;hp=e49af5b20e5f2d7a09a4fa0e517f2b888d31c65a;hpb=a44e607fb5398c80c5de2629017865ae749e8fbf;p=PrivacyBrowserPC.git diff --git a/src/helpers/FilterListHelper.cpp b/src/helpers/FilterListHelper.cpp index e49af5b..e4ce25f 100644 --- a/src/helpers/FilterListHelper.cpp +++ b/src/helpers/FilterListHelper.cpp @@ -19,15 +19,17 @@ // Application headers. #include "FilterListHelper.h" +#include "structs/OverrideStruct.h" + +// KDE Framework headers. +#include // Qt toolkit headers. #include #include +#include #include -// KDE Framework headers. -#include - // Construct the class. FilterListHelper::FilterListHelper() { @@ -36,6 +38,11 @@ FilterListHelper::FilterListHelper() ALLOWED_STRING = i18nc("Allowed disposition", "Allowed"); BLOCKED_STRING = i18nc("Blocked disposition", "Blocked"); + // Populate the translated filter option disposition strings. Translated entries cannot be public static const. + FILTER_OPTION_NULL = QString(); + FILTER_OPTION_APPLY = i18nc("Apply filter option", "Apply"); + FILTER_OPTION_OVERRIDE = i18nc("Override filter option", "Override"); + // 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"); @@ -69,66 +76,663 @@ FilterListHelper::FilterListHelper() RESOURCE_TYPE_UNKNOWN = i18nc("Resource type unknown", "Unknown"); // Populate the translated sublist strings. Translated entries cannot be public static const. + MAIN_ALLOWLIST_STRING = i18nc("Main allowlist sublist", "Main Allow List"); MAIN_BLOCKLIST_STRING = i18nc("Main blocklist sublist", "Main Block List"); + INITIAL_DOMAIN_BLOCKLIST_STRING = i18nc("Initial domain blocklist string", "Initial Domain Block List"); + REGULAR_EXPRESSION_BLOCKLIST_STRING = i18nc("Regular expression blocklist string", "Regular Expression Block List"); // Populate the filter lists. - ultraListStructPointer = populateFilterList(QLatin1String(":/filterlists/ultralist.txt")); ultraPrivacyStructPointer = populateFilterList(QLatin1String(":/filterlists/ultraprivacy.txt")); - easyListStructPointer = populateFilterList(QLatin1String(":/filterlists/easylist.txt")); + ultraListStructPointer = populateFilterList(QLatin1String(":/filterlists/ultralist.txt")); easyPrivacyStructPointer = populateFilterList(QLatin1String(":/filterlists/easyprivacy.txt")); + easyListStructPointer = populateFilterList(QLatin1String(":/filterlists/easylist.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; + // Initiate a continue checking tracker. If the tracker changes to false, all processing of the request will be stopped. + bool continueChecking = true; + + // Create a URL struct. + UrlStruct urlStruct; + + // Get the URLs. + QUrl firstPartyUrl = urlRequestInfo.firstPartyUrl(); + QUrl requestUrl = urlRequestInfo.requestUrl(); + + // Get the hosts. + QString firstPartyHost = firstPartyUrl.host(); + urlStruct.fqdn = requestUrl.host(); + + // Determine if this is a third-party request. + urlStruct.isThirdPartyRequest = (firstPartyHost != urlStruct.fqdn); + + // Get the request URL string. + urlStruct.urlString = requestUrl.toString(); + + // Create a URL string with separators. + urlStruct.urlStringWithSeparators = urlStruct.urlString; + + // Replace the separators characters with `^`. + urlStruct.urlStringWithSeparators.replace(QLatin1Char(':'), QLatin1Char('^')); + urlStruct.urlStringWithSeparators.replace(QLatin1Char('/'), QLatin1Char('^')); + urlStruct.urlStringWithSeparators.replace(QLatin1Char('?'), QLatin1Char('^')); + urlStruct.urlStringWithSeparators.replace(QLatin1Char('='), QLatin1Char('^')); + urlStruct.urlStringWithSeparators.replace(QLatin1Char('&'), QLatin1Char('^')); + + // Add a `^` to the end of the string it it doesn't already contain one. + if (!urlStruct.urlStringWithSeparators.endsWith(QLatin1Char('^'))) + urlStruct.urlStringWithSeparators.append(QLatin1Char('^')); + + // Create truncated URL strings and initially populate it with the original URL strings. + urlStruct.truncatedUrlString = urlStruct.urlString; + urlStruct.truncatedUrlStringWithSeparators = urlStruct.urlStringWithSeparators; + + // Get the index of the beginning of the fully qualified domain name. + int fqdnIndex = urlStruct.truncatedUrlString.indexOf(QLatin1String("://")) + 3; + + // Truncate the URL to the beginning of the fully qualified domain name. + urlStruct.truncatedUrlString.remove(0, fqdnIndex); + urlStruct.truncatedUrlStringWithSeparators.remove(0, fqdnIndex); // Check UltraList. - status = checkIndividualList(urlRequestInfo, requestStructPointer, ultraListStructPointer); + continueChecking = checkIndividualList(urlRequestInfo, urlStruct, requestStructPointer, ultraPrivacyStructPointer); - // check UltraPrivacy if the status is still true. - if (status) { - status = checkIndividualList(urlRequestInfo, requestStructPointer, ultraPrivacyStructPointer); - } + // Check UltraPrivacy. + if (continueChecking) + continueChecking = checkIndividualList(urlRequestInfo, urlStruct, requestStructPointer, ultraListStructPointer); - // Return the status. - return status; + // Check EasyPrivacy. + if (continueChecking) + continueChecking = checkIndividualList(urlRequestInfo, urlStruct, requestStructPointer, easyPrivacyStructPointer); + + // Check EasyList. + if (continueChecking) + continueChecking = checkIndividualList(urlRequestInfo, urlStruct, requestStructPointer, easyListStructPointer); + + // Check Fanboy's Annoyance list. + if (continueChecking) + continueChecking = checkIndividualList(urlRequestInfo, urlStruct, requestStructPointer, fanboyAnnoyanceStructPointer); + + // Return the continue checking status. + return continueChecking; } -bool FilterListHelper::checkIndividualList(QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer, FilterListStruct *filterListStruct) const +bool FilterListHelper::checkIndividualList(QWebEngineUrlRequestInfo &urlRequestInfo, UrlStruct &urlStruct, RequestStruct *requestStructPointer, FilterListStruct *filterListStructPointer) const { - // Get the request URL. - QUrl url = urlRequestInfo.requestUrl(); + // Initiate a continue checking tracker. If the tracker changes to false, all process of the request will be stopped. + bool continueChecking = true; - // Get the request URL string. - QString urlString = url.toString(); + // Check the main allow list. + for (auto filterListEntry = filterListStructPointer->mainAllowListPointer->begin(); filterListEntry != filterListStructPointer->mainAllowListPointer->end(); ++filterListEntry) + { + // Get the entry struct. + EntryStruct *entryStructPointer = *filterListEntry; + + // TODO. Temporarily ignore empty applied entries. + if (!entryStructPointer->appliedEntryList[0].isEmpty()) + { + // Check if the URL string contains the applied entry. TODO. + if (urlStruct.urlString.contains(entryStructPointer->appliedEntryList[0]) || urlStruct.urlStringWithSeparators.contains(entryStructPointer->appliedEntryList[0])) + { + // Allow the request. + urlRequestInfo.block(false); + + // Populate the request struct. + populateRequestStruct(requestStructPointer, ALLOWED, filterListStructPointer->title, MAIN_ALLOWLIST, entryStructPointer); + + // Log the allow. + //qDebug().noquote().nospace() << "Allowed request: " << urlStruct.urlString << ", Filter list entry: " << entryStructPointer->appliedEntry; + + // Returning `false` stops all processing of the request. + return false; + } + } + } + + // Get the main block list end. + auto mainBlockListEnd = filterListStructPointer->mainBlockListPointer->end(); // Check the main block list. - for (auto filterListEntry = filterListStruct->mainBlockList.begin(); filterListEntry != filterListStruct->mainBlockList.end(); ++filterListEntry) { + for (auto mainBlockListEntry = filterListStructPointer->mainBlockListPointer->begin(); mainBlockListEntry != mainBlockListEnd; ++mainBlockListEntry) + { + // Exit the loop if continue checking is false. + if (!continueChecking) + break; + // Get the entry struct. - EntryStruct *entryStructPointer = *filterListEntry; + EntryStruct *entryStructPointer = *mainBlockListEntry; - // Check if the URL string contains the applied entry - if (urlString.contains(entryStructPointer->appliedEntry)) { - // Block the request. - urlRequestInfo.block(true); + // Check the applied entries. + continueChecking = checkAppliedEntry(urlRequestInfo, urlStruct, requestStructPointer, filterListStructPointer->title, MAIN_BLOCKLIST, entryStructPointer, urlStruct.urlString, + urlStruct.urlStringWithSeparators); + } - // Populate the request struct. - populateRequestStruct(requestStructPointer, BLOCKED, filterListStruct->title, MAIN_BLOCKLIST, entryStructPointer->appliedEntry, entryStructPointer->originalEntry); + // Get the initial domain block list end. + auto initialDomainBlockListEnd = filterListStructPointer->initialDomainBlockListPointer->end(); - // Log the block. - //qDebug().noquote().nospace() << "Blocked request: " << urlString << ", Filter list entry: " << entryStructPointer->appliedEntry; + // Check the initial domain block list. + for (auto initialDomainBlockListEntry = filterListStructPointer->initialDomainBlockListPointer->begin(); initialDomainBlockListEntry != initialDomainBlockListEnd; + ++initialDomainBlockListEntry) + { + // Exit the loop if continue checking is false. + if (!continueChecking) + break; + + // Get the entry struct. + EntryStruct *entryStructPointer = *initialDomainBlockListEntry; + + // Check the applied entries. + continueChecking = checkAppliedEntry(urlRequestInfo, urlStruct, requestStructPointer, filterListStructPointer->title, INITIAL_DOMAIN_BLOCKLIST, entryStructPointer, + urlStruct.truncatedUrlString, urlStruct.truncatedUrlStringWithSeparators); + } + + // Get the regular expression block list end. + auto regularExpressionBlockListEnd = filterListStructPointer->regularExpressionBlockListPointer->end(); + + // Check the regular expression block list. + for (auto regularExpressionBlockListEntry = filterListStructPointer->regularExpressionBlockListPointer->begin(); regularExpressionBlockListEntry != regularExpressionBlockListEnd; + ++regularExpressionBlockListEntry) + { + // Exit the loop if continue checking is false. + if (!continueChecking) + break; + + // Get the entry struct. + EntryStruct *entryStructPointer = *regularExpressionBlockListEntry; + + // Check the applied entries. + continueChecking = checkRegularExpression(urlRequestInfo, urlStruct, requestStructPointer, filterListStructPointer->title, REGULAR_EXPRESSION_BLOCKLIST, entryStructPointer); + } - // Returning `false` stops all processing of the request. - return false; + // Return the continue checking status. + return continueChecking; +} + +bool FilterListHelper::checkAppliedEntry(QWebEngineUrlRequestInfo &urlRequestInfo, UrlStruct &urlStruct, RequestStruct *requestStructPointer, const QString &filterListTitle, + const int sublistInt, EntryStruct *entryStructPointer, QString &urlString, QString &urlStringWithSeparators) const +{ + // Check the entries according to the number. + if (entryStructPointer->singleAppliedEntry) + { + // Process initial and final matches. + if (entryStructPointer->initialMatch && entryStructPointer->finalMatch) // This is both an initial and final match. + { + // Check the URL against the applied entry. + if ((urlString == entryStructPointer->appliedEntryList[0]) || (urlStringWithSeparators == entryStructPointer->appliedEntryList[0])) + { + // Check the domain status. + return checkDomain(urlRequestInfo, urlStruct, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + } + else if (entryStructPointer->initialMatch) // This is an initial match. + { + // Check the URL against the applied entry. + if (urlString.startsWith(entryStructPointer->appliedEntryList[0]) || urlStringWithSeparators.startsWith(entryStructPointer->appliedEntryList[0])) + { + // Check the domain status. + return checkDomain(urlRequestInfo, urlStruct, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + } + else if (entryStructPointer->finalMatch) // This is a final match. + { + // Check the URL against the applied entry. + if (urlString.endsWith(entryStructPointer->appliedEntryList[0]) || urlStringWithSeparators.endsWith(entryStructPointer->appliedEntryList[0])) + { + // Check the domain status. + return checkDomain(urlRequestInfo, urlStruct, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + } + else // There is no initial or final matching. + { + // Check if the URL string contains the applied entry. + if (urlString.contains(entryStructPointer->appliedEntryList[0]) || urlStringWithSeparators.contains(entryStructPointer->appliedEntryList[0])) + { + // Check the domain status. + return checkDomain(urlRequestInfo, urlStruct, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } } } + else // There are multiple entries. + { + // Create a URL matches flag. + bool urlMatches = true; + + // Process initial and final matches. + if (entryStructPointer->initialMatch && entryStructPointer->finalMatch) // This is both an initial and final match. + { + // Check the first entry. + if (urlString.startsWith(entryStructPointer->appliedEntryList[0]) || + urlStringWithSeparators.startsWith(entryStructPointer->appliedEntryList[0])) // The URL string starts with the first applied entry. + { + // Get the number of characters to remove from the front of the URL strings. + int charactersToRemove = entryStructPointer->appliedEntryList[0].size(); - // Return `true` to continue processing the URL request. + // Remove the entry from the front of the URL string copies. + urlString.remove(0, charactersToRemove); + urlStringWithSeparators.remove(0, charactersToRemove); + } + else // The URL string does not end with the last applied entry. + { + // Mark the URL matches flag as false. + urlMatches = false; + } + + // Check the other entries if the URL still matches. + if (urlMatches) + { + // Calculate the penultimate entry. + int penultimateEntryNumber = (entryStructPointer->sizeOfAppliedEntryList - 1); + int ultimateEntryIndex = penultimateEntryNumber; + + // Check all the middle entries. + for (int i = 1; i < penultimateEntryNumber; ++i) + { + // Get the index of the applied entry, which will be `-1` if it doesn't exist. + int stringIndex = urlString.indexOf(entryStructPointer->appliedEntryList[i]); + int stringWithSeparatorsIndex = urlStringWithSeparators.indexOf(entryStructPointer->appliedEntryList[i]); + + // Get the larger of the two indexes. + int index = std::max(stringIndex, stringWithSeparatorsIndex); + + // Check if the entry was found. + if (index >= 0) // The entry is contained in the URL string. + { + // Get the number of characters to remove from the front of the URL strings. + int charactersToRemove = index + entryStructPointer->appliedEntryList[i].size(); + + // Remove the entry from the front of the URL string copies. + urlString.remove(0, charactersToRemove); + urlStringWithSeparators.remove(0, charactersToRemove); + } + else // The entry is not contained in the URL string. + { + // Mark the URL matches flag as false. + urlMatches = false; + } + } + + // Check the final entry if the URL still matches. + if (urlMatches) + { + if (urlString.endsWith(entryStructPointer->appliedEntryList[ultimateEntryIndex]) || + urlStringWithSeparators.endsWith(entryStructPointer->appliedEntryList[ultimateEntryIndex])) // The URL string ends with the last applied entry. + { + // There is no need to modify the URL string copies as no further checks will be performed. + } + else // The URL string does not end with the last applied entry. + { + // Mark the URL matches flag as false. + urlMatches = false; + } + } + } + } + else if (entryStructPointer->initialMatch) // This is an initial match. + { + // Check the first entry. + if (urlString.startsWith(entryStructPointer->appliedEntryList[0]) || + urlStringWithSeparators.startsWith(entryStructPointer->appliedEntryList[0])) // The URL string starts with the first applied entry. + { + // Get the number of characters to remove from the front of the URL strings. + int charactersToRemove = entryStructPointer->appliedEntryList[0].size(); + + // Remove the entry from the front of the URL string copies. + urlString.remove(0, charactersToRemove); + urlStringWithSeparators.remove(0, charactersToRemove); + } + else // The URL string does not end with the last applied entry. + { + // Mark the URL matches flag as false. + urlMatches = false; + } + + // Check the other entries if the URL still matches. + if (urlMatches) + { + for (int i = 1; i < entryStructPointer->sizeOfAppliedEntryList; ++i) + { + // Get the index of the applied entry, which will be `-1` if it doesn't exist. + int stringIndex = urlString.indexOf(entryStructPointer->appliedEntryList[i]); + int stringWithSeparatorsIndex = urlStringWithSeparators.indexOf(entryStructPointer->appliedEntryList[i]); + + // Get the larger of the two indexes. + int index = std::max(stringIndex, stringWithSeparatorsIndex); + + // Check if the entry was found. + if (index >= 0) // The entry is contained in the URL string. + { + // Get the number of characters to remove from the front of the URL strings. + int charactersToRemove = index + entryStructPointer->appliedEntryList[i].size(); + + // Remove the entry from the front of the URL string copies. + urlString.remove(0, charactersToRemove); + urlStringWithSeparators.remove(0, charactersToRemove); + } + else // The entry is not contained in the URL string. + { + // Mark the URL matches flag as false. + urlMatches = false; + } + } + } + } + else if (entryStructPointer->finalMatch) // This is a final match. + { + // Calculate the penultimate entry. + int penultimateEntryNumber = (entryStructPointer->sizeOfAppliedEntryList - 1); + int ultimateEntryIndex = penultimateEntryNumber; + + // Check all the entries except the last one. + for (int i = 0; i < penultimateEntryNumber; ++i) + { + // Get the index of the applied entry, which will be `-1` if it doesn't exist. + int stringIndex = urlString.indexOf(entryStructPointer->appliedEntryList[i]); + int stringWithSeparatorsIndex = urlStringWithSeparators.indexOf(entryStructPointer->appliedEntryList[i]); + + // Get the larger of the two indexes. + int index = std::max(stringIndex, stringWithSeparatorsIndex); + + // Check if the entry was found. + if (index >= 0) // The entry is contained in the URL string. + { + // Get the number of characters to remove from the front of the URL strings. + int charactersToRemove = index + entryStructPointer->appliedEntryList[i].size(); + + // Remove the entry from the front of the URL string copies. + urlString.remove(0, charactersToRemove); + urlStringWithSeparators.remove(0, charactersToRemove); + } + else // The entry is not contained in the URL string. + { + // Mark the URL matches flag as false. + urlMatches = false; + } + } + + // Check the final entry if the URL still matches. + if (urlMatches) + { + if (urlString.endsWith(entryStructPointer->appliedEntryList[ultimateEntryIndex]) || + urlStringWithSeparators.endsWith(entryStructPointer->appliedEntryList[ultimateEntryIndex])) // The URL string ends with the last applied entry. + { + // There is no need to modify the URL string copies as no further checks will be performed. + } + else // The URL string does not end with the last applied entry. + { + // Mark the URL matches flag as false. + urlMatches = false; + } + } + } + else // There is no initial or final matching. + { + for (int i = 0; i < entryStructPointer->sizeOfAppliedEntryList; ++i) + { + // Get the index of the applied entry, which will be `-1` if it doesn't exist. + int stringIndex = urlString.indexOf(entryStructPointer->appliedEntryList[i]); + int stringWithSeparatorsIndex = urlStringWithSeparators.indexOf(entryStructPointer->appliedEntryList[i]); + + // Get the larger of the two indexes. + int index = std::max(stringIndex, stringWithSeparatorsIndex); + + // Check if the entry was found. + if (index >= 0) // The entry is contained in the URL string. + { + + // Get the number of characters to remove from the front of the URL strings. + int charactersToRemove = index + entryStructPointer->appliedEntryList[i].size(); + + // Remove the entry from the front of the URL string copies. + urlString.remove(0, charactersToRemove); + urlStringWithSeparators.remove(0, charactersToRemove); + } + else // The entry is not contained in the URL string. + { + // Mark the URL matches flag as false. + urlMatches = false; + } + } + } + + // Check the domain status if the URL matches. + if (urlMatches) + return checkDomain(urlRequestInfo, urlStruct, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // If the applied entry doesn't match, return `true` to continue processing the URL request. return true; } +bool FilterListHelper::checkRegularExpression(QWebEngineUrlRequestInfo &urlRequestInfo, UrlStruct &urlStruct, RequestStruct *requestStructPointer, const QString &filterListTitle, + const int sublistInt, EntryStruct *entryStructPointer) const +{ + // Create an applied entry regular expression. + QRegularExpression appliedEntryRegularExpression(entryStructPointer->appliedEntryList[0]); + + // Check if the regular expression matches the applied entry. + if (urlStruct.urlString.contains(appliedEntryRegularExpression)) + { + // Check the domain status. + return checkDomain(urlRequestInfo, urlStruct, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // If the regular expression doesn't match, return `true` to continue processing the URL request. + return true; +} + +bool FilterListHelper::checkDomain(QWebEngineUrlRequestInfo &urlRequestInfo, UrlStruct &urlStruct, RequestStruct *requestStructPointer, const QString &filterListTitle, const int sublistInt, + EntryStruct *entryStructPointer) const +{ + // Check domain status. + if (entryStructPointer->domain == FilterOptionEnum::Disposition::Null) // Ignore domain status. + { + // Check the third-party status. + return checkThirdParty(urlRequestInfo, requestStructPointer, urlStruct.isThirdPartyRequest, filterListTitle, sublistInt, entryStructPointer); + } + else if (entryStructPointer->domain == FilterOptionEnum::Disposition::Apply) // Block requests from listed domains. + { + // Check each domain. + foreach (QString blockedDomain, entryStructPointer->domainList) + { + // Check if the request came from a blocked domain. + if (urlStruct.fqdn.endsWith(blockedDomain)) + { + // Check the third-party status. + return checkThirdParty(urlRequestInfo, requestStructPointer, urlStruct.isThirdPartyRequest, filterListTitle, sublistInt, entryStructPointer); + } + } + } + else if (entryStructPointer->domain == FilterOptionEnum::Disposition::Override) // Block domains that are not overridden. + { + // Create a block domain flag. + bool blockDomain = true; + + // Check each overridden domain. + foreach (QString overriddenDomain, entryStructPointer->domainList) + { + // Check if the request came from an overridden domain. + if (urlStruct.fqdn.endsWith(overriddenDomain)) + { + // Don't block the domain. + blockDomain = false; + } + } + + // Continue checking if the domain is blocked. + if (blockDomain) + { + // Check the third-party status. + return checkThirdParty(urlRequestInfo, requestStructPointer, urlStruct.isThirdPartyRequest, filterListTitle, sublistInt, entryStructPointer); + } + } + + // There is a domain specified that doesn't match this request. Return `true` to continue processing the URL request. + return true; +} + +bool FilterListHelper::checkThirdParty(QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer, const bool isThirdPartyRequest, const QString &filterListTitle, + const int sublistInt, EntryStruct *entryStructPointer) const +{ + // Check third-party status. + if (entryStructPointer->thirdParty == FilterOptionEnum::Disposition::Null) // Ignore third-party status. + { + // Check if request options are applied. + if (entryStructPointer->hasRequestOptions) // Request options are applied. + { + // Check the request options. + return checkRequestOptions(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + else // Request options are not applied. + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + } + else if ((entryStructPointer->thirdParty == FilterOptionEnum::Disposition::Apply) && isThirdPartyRequest) // Block third-party request. + { + // Check if request options are applied. + if (entryStructPointer->hasRequestOptions) // Request options are applied. + { + // Check the request options. + return checkRequestOptions(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + else // Request options are not applied. + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + } + else if ((entryStructPointer->thirdParty == FilterOptionEnum::Disposition::Override) && !isThirdPartyRequest) // Block first-party requests. + { + // Check if request options are applied. + if (entryStructPointer->hasRequestOptions) // Request options are applied. + { + // Check the request options. + return checkRequestOptions(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + else // Request options are not applied. + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + } + + // The third-party option specified doesn't match this request. Return `true` to continue processing the URL request. + return true; +} + +bool FilterListHelper::checkRequestOptions(QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer, const QString &filterListTitle, const int sublistInt, + EntryStruct *entryStructPointer) const +{ + // Block font requests. + if ((entryStructPointer->font == FilterOptionEnum::Disposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeFontResource)) + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // Block image requests. + if ((entryStructPointer->image == FilterOptionEnum::Disposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeImage)) + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // Block main frame requests. + if ((entryStructPointer->mainFrame == FilterOptionEnum::Disposition::Apply) && ((requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeMainFrame) || + (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeNavigationPreloadMainFrame))) + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // Block media requests. + if ((entryStructPointer->media == FilterOptionEnum::Disposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeMedia)) + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // Block object requests. + if ((entryStructPointer->object == FilterOptionEnum::Disposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeObject)) + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // Block other requests. + if ((entryStructPointer->other == FilterOptionEnum::Disposition::Apply) && ((requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeSubResource) || + (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeWorker) || + (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeSharedWorker) || + (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypePrefetch) || + (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeFavicon) || + (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeServiceWorker) || + (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeCspReport) || + (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypePluginResource) || + (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeUnknown))) + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // Block ping requests + if ((entryStructPointer->ping == FilterOptionEnum::Disposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypePing)) + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // Block script requests. + if ((entryStructPointer->script == FilterOptionEnum::Disposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeScript)) + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // Block style sheet requests. + if ((entryStructPointer->styleSheet == FilterOptionEnum::Disposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeStylesheet)) + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // Block sub resource requests. + if ((entryStructPointer->subFrame == FilterOptionEnum::Disposition::Apply) && ((requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeSubFrame) || + (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeNavigationPreloadSubFrame))) + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // Block XML HTTP requests. + if ((entryStructPointer->xmlHttpRequest == FilterOptionEnum::Disposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeXhr)) + { + // Block the request. + return blockRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, entryStructPointer); + } + + // The request options specified don't match this request. Return `true` to continue processing the URL request. + return true; +} + +bool FilterListHelper::blockRequest(QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer, const QString &filterListTitle, const int sublistInt, + EntryStruct *entryStructPointer) const +{ + // Block the request. + urlRequestInfo.block(true); + + // Populate the request struct. + populateRequestStruct(requestStructPointer, BLOCKED, filterListTitle, sublistInt, entryStructPointer); + + // Log the block. + //qDebug().noquote().nospace() << "Blocked request: " << urlRequestInfo.firstPartyUrl() << ", Filter list entry: " << entryStructPointer->appliedEntry; + + // Returning `false` stops all processing of the request. + return false; +} + QString FilterListHelper::getDispositionString(int dispositionInt) const { // Return the translated disposition string. @@ -155,6 +759,17 @@ QString FilterListHelper::getNavigationTypeString(int navigationTypeInt) const } } +QString FilterListHelper::getRequestOptionDispositionString(const FilterOptionEnum::Disposition filterOptionDisposition) const +{ + // Return the translated filter option disposition string. + switch (filterOptionDisposition) + { + case FilterOptionEnum::Disposition::Apply: return FILTER_OPTION_APPLY; + case FilterOptionEnum::Disposition::Override: return FILTER_OPTION_OVERRIDE; + default: return FILTER_OPTION_NULL; + } +} + QString FilterListHelper::getResourceTypeString(int resourceTypeInt) const { // Return the translated resource type string. @@ -189,7 +804,10 @@ QString FilterListHelper::getSublistName(int sublistInt) const // Return the name of the requested sublist. switch (sublistInt) { + case MAIN_ALLOWLIST: return MAIN_ALLOWLIST_STRING; case MAIN_BLOCKLIST: return MAIN_BLOCKLIST_STRING; + case INITIAL_DOMAIN_BLOCKLIST: return INITIAL_DOMAIN_BLOCKLIST_STRING; + case REGULAR_EXPRESSION_BLOCKLIST: return REGULAR_EXPRESSION_BLOCKLIST_STRING; default: return QString(); // The default return should never be reached. } } @@ -223,12 +841,32 @@ FilterListStruct* FilterListHelper::populateFilterList(const QString &filterList entryStructPointer->originalEntry = filterListString; // Process the entry. - if (filterListString.startsWith(QLatin1Char('['))) { // The line starts with `[`, which is the file format. + if (filterListString.isEmpty()) // Ignore empty lines. + { + // Do nothing. + + // Log the dropping of the line. + //qDebug().noquote().nospace() << filterListString << " NOT added from " << filterListFileName << " (empty line)."; + } + else 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 << " (file format)."; + } + else if (filterListString.contains(QLatin1String("##")) || + filterListString.contains(QLatin1String("#?#")) || + filterListString.contains(QLatin1String("#@#")) || + filterListString.contains(QLatin1String("#$#"))) // The line contains unimplemented content filtering. + { // 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. + //qDebug().noquote().nospace() << filterListString << " NOT added from " << filterListFileName << " (content filtering)."; + } + 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. @@ -250,15 +888,369 @@ FilterListStruct* FilterListHelper::populateFilterList(const QString &filterList // 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; + } + else // Process the filter options. + { + // Get the index of the last dollar sign. + int indexOfLastDollarSign = filterListString.lastIndexOf(QLatin1Char('$')); + + // Process the filter options if they exist. + if (indexOfLastDollarSign > -1) + { + // Get the filter options. + entryStructPointer->originalFilterOptions = filterListString.section(QLatin1Char('$'), -1); + + // Store the entry without the filter options as the filter list string. + filterListString.truncate(indexOfLastDollarSign); + + // Split the filter options. + QStringList originalFilterOptionsList = entryStructPointer->originalFilterOptions.split(QLatin1Char(',')); + + // Create an applied filter options list. + QStringList appliedFilterOptionsList; + + // Populate the applied filter options list. + foreach (QString filterOption, originalFilterOptionsList) + { + // Only add filter options that are handled by Privacy Browser. + if (!(filterOption.startsWith(QLatin1String("csp=")) || + filterOption.startsWith(QLatin1String("method=")) || + filterOption.startsWith(QLatin1String("redirect=")) || + filterOption.startsWith(QLatin1String("rewrite=")))) + appliedFilterOptionsList.append(filterOption); + } + + // Store the applied filter options list. + entryStructPointer->appliedFilterOptionsList = appliedFilterOptionsList; + + // Initialize an override struct. + OverrideStruct overrideStruct; + + // Populate the filter options entries. + foreach (QString filterOption, appliedFilterOptionsList) + { + // Parse the filter options. + if (filterOption.startsWith(QLatin1String("domain="))) // Domain. + { + // Remove `domain=` from the filter option. + filterOption.remove(0, 7); + + // Store the domain list. + entryStructPointer->domainList = filterOption.split(QLatin1Char('|')); + + // Set the disposition. + if (entryStructPointer->domainList[0].startsWith(QLatin1Char('~'))) // Override domains. + { + // Populate the domain filter disposition. + entryStructPointer->domain = FilterOptionEnum::Disposition::Override; + + // Remove the initial `~` from each domain. + entryStructPointer->domainList.replaceInStrings(QLatin1String("~"), QLatin1String("")); + } + else // Standard domains. + { + // Populate the domain filter disposition. + entryStructPointer->domain = FilterOptionEnum::Disposition::Apply; + } + } + else if (filterOption == QLatin1String("third-party")) // Third-party. + { + // Populate the third-party filter disposition. + entryStructPointer->thirdParty = FilterOptionEnum::Disposition::Apply; + } + else if (filterOption == QLatin1String("~third-party")) // Third-party override. + { + // Populate the third-party filter disposition. + entryStructPointer->thirdParty = FilterOptionEnum::Disposition::Override; + } + else if ((filterOption == QLatin1String("document")) || (filterOption == QLatin1String("popup"))) // Document (and popup). + { + // Populate the main frame disposition. + entryStructPointer->mainFrame = FilterOptionEnum::Disposition::Apply; + + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + } + else if (filterOption == QLatin1String("font")) // Font. + { + // Populate the font disposition. + entryStructPointer->font = FilterOptionEnum::Disposition::Apply; + + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + } + else if (filterOption == QLatin1String("image")) // Image. + { + // Populate the image disposition. + entryStructPointer->image = FilterOptionEnum::Disposition::Apply; + + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + } + else if (filterOption == QLatin1String("media")) // Media. + { + // Populate the media disposition. + entryStructPointer->media = FilterOptionEnum::Disposition::Apply; + + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + } + else if (filterOption == QLatin1String("object")) // Object. + { + // Populate the object disposition. + entryStructPointer->object = FilterOptionEnum::Disposition::Apply; + + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + } + else if ((filterOption == QLatin1String("other")) || (filterOption == QLatin1String("webrtc")) || (filterOption == QLatin1String("websocket"))) // Other. + { // `websocket` will get its own section in Qt6. + // Populate the other disposition. + entryStructPointer->other = FilterOptionEnum::Disposition::Apply; + + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + } + else if (filterOption == QLatin1String("ping")) // Ping. + { + // Populate the ping disposition. + entryStructPointer->ping = FilterOptionEnum::Disposition::Apply; + + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + } + else if (filterOption == QLatin1String("script")) // Script. + { + // Populate the script disposition. + entryStructPointer->script = FilterOptionEnum::Disposition::Apply; + + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + } + else if (filterOption == QLatin1String("stylesheet")) // Style sheet. + { + // Populate the script disposition. + entryStructPointer->styleSheet = FilterOptionEnum::Disposition::Apply; + + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + } + else if (filterOption == QLatin1String("subdocument")) // Sub document. + { + // Populate the sub resource disposition. + entryStructPointer->subFrame = FilterOptionEnum::Disposition::Apply; + + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + } + else if (filterOption == QLatin1String("xmlhttprequest")) // XML HTTP request. + { + //Populate the XML HTTP request disposition. + entryStructPointer->xmlHttpRequest = FilterOptionEnum::Disposition::Apply; + + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + } + else if (filterOption == QLatin1String("~document")) // Document override. + { + // Populate the override struct. + overrideStruct.hasOverride = true; + overrideStruct.mainFrame = true; + } + else if (filterOption == QLatin1String("~font")) // Font override. + { + // Populate the override struct. + overrideStruct.hasOverride = true; + overrideStruct.font = true; + } + else if (filterOption == QLatin1String("~image")) // Image override. + { + // Populate the override struct. + overrideStruct.hasOverride = true; + overrideStruct.image = true; + } + else if (filterOption == QLatin1String("~media")) // Media override. + { + // Populate the override struct. + overrideStruct.hasOverride = true; + overrideStruct.media = true; + } + else if (filterOption == QLatin1String("~object")) // Object override. + { + // Populate the override struct. + overrideStruct.hasOverride = true; + overrideStruct.object = true; + } + else if ((filterOption == QLatin1String("~other")) || (filterOption == QLatin1String("~webrtc")) || (filterOption == QLatin1String("~websocket"))) // Other override. + { // `websocket` will get its own section in Qt6. + // Populate the override struct. + overrideStruct.hasOverride = true; + overrideStruct.other = true; + } + else if (filterOption == QLatin1String("~ping")) // Ping override. + { + // Populate the override struct. + overrideStruct.hasOverride = true; + overrideStruct.ping = true; + } + else if (filterOption == QLatin1String("~script")) // Script override. + { + // Populate the override struct. + overrideStruct.hasOverride = true; + overrideStruct.script = true; + } + else if (filterOption == QLatin1String("~stylesheet")) // Style sheet override. + { + // Populate the override struct. + overrideStruct.hasOverride = true; + overrideStruct.styleSheet = true; + } + else if (filterOption == QLatin1String("~subdocument")) // Sub document override. + { + // Populate the override struct. + overrideStruct.hasOverride = true; + overrideStruct.subFrame = true; + } + else if (filterOption == QLatin1String("~xmlhttprequest")) // XML HTTP request override. + { + // Populate the override struct. + overrideStruct.hasOverride = true; + overrideStruct.xmlHttpRequest = true; + } + } + + // Apply the overrides. + if (overrideStruct.hasOverride) + { + // Set the has request options flag. + entryStructPointer->hasRequestOptions = true; + + // Font. + if (overrideStruct.font) + entryStructPointer->font = FilterOptionEnum::Disposition::Override; + else + entryStructPointer->font = FilterOptionEnum::Disposition::Apply; + + // Image. + if (overrideStruct.image) + entryStructPointer->image = FilterOptionEnum::Disposition::Override; + else + entryStructPointer->image = FilterOptionEnum::Disposition::Apply; + + // Main Frame (document). + if (overrideStruct.mainFrame) + entryStructPointer->mainFrame = FilterOptionEnum::Disposition::Override; + else + entryStructPointer->mainFrame = FilterOptionEnum::Disposition::Apply; + + // Media. + if (overrideStruct.media) + entryStructPointer->media = FilterOptionEnum::Disposition::Override; + else + entryStructPointer->media = FilterOptionEnum::Disposition::Apply; + + // Object. + if (overrideStruct.object) + entryStructPointer->object = FilterOptionEnum::Disposition::Override; + else + entryStructPointer->object = FilterOptionEnum::Disposition::Apply; + + // Other. + if (overrideStruct.other) + entryStructPointer->other = FilterOptionEnum::Disposition::Override; + else + entryStructPointer->other = FilterOptionEnum::Disposition::Apply; + + // Ping. + if (overrideStruct.ping) + entryStructPointer->ping = FilterOptionEnum::Disposition::Override; + else + entryStructPointer->ping = FilterOptionEnum::Disposition::Apply; + + // Script. + if (overrideStruct.script) + entryStructPointer->script = FilterOptionEnum::Disposition::Override; + else + entryStructPointer->script = FilterOptionEnum::Disposition::Apply; + + // Style Sheet. + if (overrideStruct.styleSheet) + entryStructPointer->styleSheet = FilterOptionEnum::Disposition::Override; + else + entryStructPointer->styleSheet = FilterOptionEnum::Disposition::Apply; + + // Sub Resource. + if (overrideStruct.subFrame) + entryStructPointer->subFrame = FilterOptionEnum::Disposition::Override; + else + entryStructPointer->subFrame = FilterOptionEnum::Disposition::Apply; - // Add the filter list entry struct to the main block list. - filterListStructPointer->mainBlockList.push_front(entryStructPointer); + // XML HTTP Request. + if (overrideStruct.xmlHttpRequest) + entryStructPointer->xmlHttpRequest = FilterOptionEnum::Disposition::Override; + else + entryStructPointer->xmlHttpRequest = FilterOptionEnum::Disposition::Apply; + } + } // Finish processing filter options. - // Log the addition to the filter list. - //qDebug().noquote().nospace() << originalFilterListString << " added from " << filterListFileName; + + if (filterListString.isEmpty() && !entryStructPointer->hasRequestOptions) // There are no applied entries and no request options. + { + // Ignore these entries as they will block all requests generally or for a specified domain. Typically these are left over after removing `csp=` filter options. + + // Log the dropping of the entry. + //qDebug().noquote().nospace() << entryStructPointer->originalEntry << " NOT added from " << filterListFileName << "."; + } + else if (filterListString.startsWith(QLatin1String("@@"))) // Process an allow list entry. + { + // Remove the initial `@@`. + filterListString.remove(0, 2); + + // Prepare the filter list string. TODO. Initial allow lists must be processed first. + prepareFilterListString(filterListString, entryStructPointer); + + // Add the entry struct to the main allow list. + filterListStructPointer->mainAllowListPointer->push_front(entryStructPointer); + + // Log the addition to the filter list. + //qDebug().noquote().nospace() << entryStructPointer->originalEntry << " added to Main Allow List from " << filterListFileName << "."; + } + else if (filterListString.startsWith(QLatin1String("||"))) // Process an initial domain block list entry. + { + // Remove the initial `||`. + filterListString.remove(0, 2); + + // Set the initial match flag. + entryStructPointer->initialMatch = true; + + // Prepare the filter list string. + prepareFilterListString(filterListString, entryStructPointer); + + // Add the entry struct to the initial domain block list. + filterListStructPointer->initialDomainBlockListPointer->push_front(entryStructPointer); + + // Log the addition to the filter list. + //qDebug().noquote().nospace() << entryStructPointer->originalEntry << " added to Initial Domain Block List from " << filterListFileName << "."; + } + else if (filterListString.contains(QLatin1String("\\"))) // Process a regular expression block list entry. + { + // Add the regular expression to the applied entry list. + entryStructPointer->appliedEntryList.append(filterListString); + + // Add the entry struct to the regular expression block list. + filterListStructPointer->regularExpressionBlockListPointer->push_front(entryStructPointer); + } + else // Process a main block list entry. + { + // Prepare the filter list string. + prepareFilterListString(filterListString, entryStructPointer); + + // Add the entry struct to the main block list. + filterListStructPointer->mainBlockListPointer->push_front(entryStructPointer); + + // Log the addition to the filter list. + //qDebug().noquote().nospace() << entryStructPointer->originalEntry << " added to Main Block List from " << filterListFileName << "."; + } } } @@ -269,13 +1261,52 @@ FilterListStruct* FilterListHelper::populateFilterList(const QString &filterList return filterListStructPointer; } -void FilterListHelper::populateRequestStruct(RequestStruct *requestStructPointer, const int disposition, const QString &filterListTitle, const int sublistInt, const QString &appliedEntry, - const QString &originalEntry) const +void FilterListHelper::populateRequestStruct(RequestStruct *requestStructPointer, const int disposition, const QString &filterListTitle, const int sublistInt, EntryStruct *entryStructPointer) const { // Populate the request struct. requestStructPointer->dispositionInt = disposition; requestStructPointer->filterListTitle = filterListTitle; requestStructPointer->sublistInt = sublistInt; - requestStructPointer->entryStruct.appliedEntry = appliedEntry; - requestStructPointer->entryStruct.originalEntry = originalEntry; + requestStructPointer->entryStruct.appliedEntryList = entryStructPointer->appliedEntryList; + requestStructPointer->entryStruct.originalEntry = entryStructPointer->originalEntry; +} + +void FilterListHelper::prepareFilterListString(QString &filterListString, EntryStruct *entryStructPointer) const +{ + // Check if this is an initial match. + if (filterListString.startsWith(QLatin1Char('|'))) + { + // Strip the initial `|`. + filterListString.remove(0, 1); + + // Set the initial match flag. + entryStructPointer->initialMatch = true; + } + + // Check if this is a final match. + if (filterListString.endsWith(QLatin1Char('|'))) + { + // Strip the final `|`. + filterListString.chop(1); + + // Set the final match flag. + entryStructPointer->finalMatch = true; + } + + // Remove the initial asterisk if it exists. + if (filterListString.startsWith(QLatin1Char('*'))) + filterListString.remove(0, 1); + + // Remove the final asterisk if it exists. + if (filterListString.endsWith(QLatin1Char('*'))) + filterListString.chop(1); + + // Split the filter list string and set it as the applied entry list. + entryStructPointer->appliedEntryList = filterListString.split(QLatin1Char('*')); + + // Store the size of the applied entry list. + entryStructPointer->sizeOfAppliedEntryList = entryStructPointer->appliedEntryList.size(); + + // Determine if this is a single applied entry (including an empty entry). + entryStructPointer->singleAppliedEntry = (entryStructPointer->sizeOfAppliedEntryList == 1); }