/* SPDX-License-Identifier: GPL-3.0-or-later
 * SPDX-FileCopyrightText: 2024-2026 Soren Stoutner <soren@stoutner.com>
 *
 * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc/>.
 *
 * This program 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.
 *
 * This program 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
 * this program.  If not, see <https://www.gnu.org/licenses/>.
 */

// Application headers.
#include "FilterListHelper.h"
#include "structs/FilterOptionsOverrideStruct.h"

// KDE Framework headers.
#include <KLocalizedString>

// Qt toolkit headers.
#include <QDebug>
#include <QFile>
#include <QRegularExpression>
#include <QTextStream>

// 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 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");
    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 (in order of their values <https://doc.qt.io/qt-6/qwebengineurlrequestinfo.html#ResourceType-enum>).
    // 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_WEB_SOCKET = i18nc("Resource type web socket", "Web Socket");
    RESOURCE_TYPE_UNKNOWN = i18nc("Resource type unknown", "Unknown");
    RESOURCE_TYPE_JSON = i18nc("Resource type JSON", "JSON");

    // Populate the translated sublist strings.  Translated entries cannot be public static const.
    MAIN_ALLOWLIST_STRING = i18nc("Main allowlist", "Main Allow List");
    INITIAL_DOMAIN_ALLOWLIST_STRING = i18nc("Initial domain allowlist", "Initial Domain Allow List");
    REGULAR_EXPRESSION_ALLOWLIST_STRING = i18nc("Regular expression allowlist", "Regular Expression Allow List");
    MAIN_BLOCKLIST_STRING = i18nc("Main blocklist", "Main Block List");
    INITIAL_DOMAIN_BLOCKLIST_STRING = i18nc("Initial domain blocklist", "Initial Domain Block List");
    REGULAR_EXPRESSION_BLOCKLIST_STRING = i18nc("Regular expression blocklist", "Regular Expression Block List");

    // Populate the filter lists.
    ultraPrivacyStructPointer = populateFilterList(QLatin1String(":/filterlists/ultraprivacy.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(PrivacyWebEngineView *privacyWebEngineViewPointer, QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer) const
{
    // Get the URLs.
    QUrl firstPartyUrl = urlRequestInfo.firstPartyUrl();
    QUrl requestUrl = urlRequestInfo.requestUrl();

    // Get the hosts.
    requestStructPointer->firstPartyHost = firstPartyUrl.host();
    QString requestHost = requestUrl.host();

    // Determine if this is a third-party request.
    requestStructPointer->isThirdPartyRequest = (requestStructPointer->firstPartyHost != requestHost);

    // Get the request URL string.
    requestStructPointer->urlString = requestUrl.toString();

    // Create a URL string with separators.
    requestStructPointer->urlStringWithSeparators = requestStructPointer->urlString;

    // Replace the separators characters with `^`.
    requestStructPointer->urlStringWithSeparators.replace(QLatin1Char(':'), QLatin1Char('^'));
    requestStructPointer->urlStringWithSeparators.replace(QLatin1Char('/'), QLatin1Char('^'));
    requestStructPointer->urlStringWithSeparators.replace(QLatin1Char('?'), QLatin1Char('^'));
    requestStructPointer->urlStringWithSeparators.replace(QLatin1Char('='), QLatin1Char('^'));
    requestStructPointer->urlStringWithSeparators.replace(QLatin1Char('&'), QLatin1Char('^'));

    // Add a `^` to the end of the url string with separators if it doesn't already contain one.
    if (!requestStructPointer->urlStringWithSeparators.endsWith(QLatin1Char('^')))
        requestStructPointer->urlStringWithSeparators.append(QLatin1Char('^'));

    // Create truncated URL strings and initially populate it with the original URL strings.
    requestStructPointer->truncatedUrlString = requestStructPointer->urlString;
    requestStructPointer->truncatedUrlStringWithSeparators = requestStructPointer->urlStringWithSeparators;

    // Get the index of the beginning of the fully qualified domain name.
    int fqdnIndex = requestStructPointer->truncatedUrlString.indexOf(QLatin1String("://")) + 3;

    // Truncate the URL to the beginning of the fully qualified domain name.
    requestStructPointer->truncatedUrlString.remove(0, fqdnIndex);
    requestStructPointer->truncatedUrlStringWithSeparators.remove(0, fqdnIndex);

    // Create a filter list struct list.
    QList<FilterListStruct*> filterListStructList;

    // Populate the filter list struct.
    if (privacyWebEngineViewPointer->ultraPrivacyEnabled) filterListStructList.append(ultraPrivacyStructPointer);
    if (privacyWebEngineViewPointer->ultraListEnabled) filterListStructList.append(ultraListStructPointer);
    if (privacyWebEngineViewPointer->easyPrivacyEnabled) filterListStructList.append(easyPrivacyStructPointer);
    if (privacyWebEngineViewPointer->easyListEnabled) filterListStructList.append(easyListStructPointer);
    if (privacyWebEngineViewPointer->fanboysAnnoyanceListEnabled) filterListStructList.append(fanboyAnnoyanceStructPointer);

    // Initialize a continue checking tracker.  If the tracker changes to false, all processing of the request will be stopped.
    bool continueChecking = true;

    // Check each filter list.
    for (FilterListStruct *filterListStructPointer : filterListStructList)
    {
        // Check the list unless a match has been found by a previous list.
        if (continueChecking)
            continueChecking = checkIndividualList(urlRequestInfo, requestStructPointer, filterListStructPointer);
    }

    // Return the continue checking status.
    return continueChecking;
}

bool FilterListHelper::checkIndividualList(QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer, FilterListStruct *filterListStructPointer) const
{
    // Initiate a continue checking tracker.  If the tracker changes to false, all further checking of the request will be bypassed.
    bool continueChecking = true;

    // Get the main allow list iterators.
    auto mainAllowListEntry = filterListStructPointer->mainAllowListPointer->begin();
    auto mainAllowListEnd = filterListStructPointer->mainAllowListPointer->end();

    // Check the main allow list.
    while (continueChecking && (mainAllowListEntry != mainAllowListEnd))
    {
        // Get the filter list entry struct.
        FilterListEntryStruct *filterListEntryStructPointer = *mainAllowListEntry;

        // Check the applied entry against the request URL.
        continueChecking = checkAppliedEntry(urlRequestInfo, requestStructPointer, filterListStructPointer->title, MAIN_ALLOWLIST, requestStructPointer->urlString,
                                             RequestUrlType::Url, filterListEntryStructPointer);  // TODO:  Consider changing RequestUrlType to MatchedUrlType to align with the Privacy Browser Android terminology.

        // Check the applied entry against the request URL with separators.
        if (continueChecking)
            continueChecking = checkAppliedEntry(urlRequestInfo, requestStructPointer, filterListStructPointer->title, MAIN_ALLOWLIST, requestStructPointer->urlStringWithSeparators,
                                                 RequestUrlType::UrlWithSeparators, filterListEntryStructPointer);

        // Increment the main allow list entry.
        ++mainAllowListEntry;
    }

    // Check the initial domain allow list unless a match has already been found.
    if (continueChecking)
    {
        // Get the initial domain allow list iterators.
        auto initialDomainAllowListEntry = filterListStructPointer->initialDomainAllowListPointer->begin();
        auto initialDomainAllowListEnd = filterListStructPointer->initialDomainAllowListPointer->end();

        // Check the initial domain allow list.
        while (continueChecking && (initialDomainAllowListEntry != initialDomainAllowListEnd))
        {
            // Get the filter list entry struct.
            FilterListEntryStruct *filterListEntryStructPointer = *initialDomainAllowListEntry;

            // Check the applied entry against the truncated URL.
            continueChecking = checkAppliedEntry(urlRequestInfo, requestStructPointer, filterListStructPointer->title, INITIAL_DOMAIN_ALLOWLIST, requestStructPointer->truncatedUrlString,
                                                 RequestUrlType::TruncatedUrl, filterListEntryStructPointer);

            //Check the applied entry against the truncated URL with separators.
            if (continueChecking)
                continueChecking = checkAppliedEntry(urlRequestInfo, requestStructPointer, filterListStructPointer->title, INITIAL_DOMAIN_ALLOWLIST,
                                                     requestStructPointer->truncatedUrlStringWithSeparators, RequestUrlType::TruncatedUrlWithSeparators, filterListEntryStructPointer);

            // Increment the initial domain allow list entry.
            ++initialDomainAllowListEntry;
        }
    }

    // Check the regular expression allow list unless a match has already been found.
    if (continueChecking)
    {
        // Get the regular expression allow list iterators.
        auto regularExpressionAllowListEntry = filterListStructPointer->regularExpressionAllowListPointer->begin();
        auto regularExpressionAllowListEnd = filterListStructPointer->regularExpressionAllowListPointer->end();

        // Check the regular expression allow list.
        while (continueChecking && (regularExpressionAllowListEntry != regularExpressionAllowListEnd))
        {
            // Get the filter list entry struct.
            FilterListEntryStruct *filterListEntryStructPointer = *regularExpressionAllowListEntry;

            // Check the applied entries.
            continueChecking = checkRegularExpression(urlRequestInfo, requestStructPointer, filterListStructPointer->title, REGULAR_EXPRESSION_ALLOWLIST, filterListEntryStructPointer);

            // Increment the initial domain allow list entry.
            ++regularExpressionAllowListEntry;
        }
    }

    // Check the main block list unless a match has already been found.
    if (continueChecking)
    {
        // Get the main block list iterators.
        auto mainBlockListEntry = filterListStructPointer->mainBlockListPointer->begin();
        auto mainBlockListEnd = filterListStructPointer->mainBlockListPointer->end();

        // Check the main block list.
        while (continueChecking && (mainBlockListEntry != mainBlockListEnd))
        {
            // Get the filter list entry struct.
            FilterListEntryStruct *filterListEntryStructPointer = *mainBlockListEntry;

            // Check the applied entry against the request URL.
            continueChecking = checkAppliedEntry(urlRequestInfo, requestStructPointer, filterListStructPointer->title, MAIN_BLOCKLIST, requestStructPointer->urlString,
                                                 RequestUrlType::Url, filterListEntryStructPointer);

            // Check the applied entry against the request URL with separators.
            if (continueChecking)
                continueChecking = checkAppliedEntry(urlRequestInfo, requestStructPointer, filterListStructPointer->title, MAIN_BLOCKLIST, requestStructPointer->urlStringWithSeparators,
                                                 RequestUrlType::UrlWithSeparators, filterListEntryStructPointer);

            // Increment the main block list entry.
            ++mainBlockListEntry;
        }
    }

    // Check the initial domain block list unless a match has already been found.
    if (continueChecking)
    {
        // Get the initial domain block list iterators.
        auto initialDomainBlockListEntry = filterListStructPointer->initialDomainBlockListPointer->begin();
        auto initialDomainBlockListEnd = filterListStructPointer->initialDomainBlockListPointer->end();

        // Check the initial domain block list.
        while (continueChecking && (initialDomainBlockListEntry != initialDomainBlockListEnd))
        {
            // Get the entry struct.
            FilterListEntryStruct *filterListEntryStructPointer = *initialDomainBlockListEntry;

            // Check the applied entry against the truncated URL.
            continueChecking = checkAppliedEntry(urlRequestInfo, requestStructPointer, filterListStructPointer->title, INITIAL_DOMAIN_BLOCKLIST, requestStructPointer->truncatedUrlString,
                                                 RequestUrlType::TruncatedUrl, filterListEntryStructPointer);

            // Check the applied entry against the truncated URL with separators.
            if (continueChecking)
                continueChecking = checkAppliedEntry(urlRequestInfo, requestStructPointer, filterListStructPointer->title, INITIAL_DOMAIN_BLOCKLIST,
                                                     requestStructPointer->truncatedUrlStringWithSeparators, RequestUrlType::TruncatedUrlWithSeparators, filterListEntryStructPointer);

            // Increment the initial domain block list entry.
            ++initialDomainBlockListEntry;
        }
    }

    // Check the regular expression block list unless a match has already been found.
    if (continueChecking)
    {
        // Get the regular expression block list end.
        auto regularExpressionBlockListEntry = filterListStructPointer->regularExpressionBlockListPointer->begin();
        auto regularExpressionBlockListEnd = filterListStructPointer->regularExpressionBlockListPointer->end();

        // Check the regular expression block list.
        while (continueChecking && (regularExpressionBlockListEntry != regularExpressionBlockListEnd))
        {
            // Get the entry struct.
            FilterListEntryStruct *filterListEntryStructPointer = *regularExpressionBlockListEntry;

            // Check the applied entries.
            continueChecking = checkRegularExpression(urlRequestInfo, requestStructPointer, filterListStructPointer->title, REGULAR_EXPRESSION_BLOCKLIST, filterListEntryStructPointer);

            // Increment the regular expression block list entry.
            ++regularExpressionBlockListEntry;
        }
    }

    // Return the continue checking status.
    return continueChecking;
}

bool FilterListHelper::checkAppliedEntry(QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer, const QString &filterListTitle, const int sublistInt, QString urlString,
                                         const RequestUrlType requestUrlType, FilterListEntryStruct *filterListEntryStructPointer) const
{
    // Check the entries according to the number.
    if (filterListEntryStructPointer->singleAppliedEntry)  // There is a single entry.
    {
        // Process initial and final matches.
        if (filterListEntryStructPointer->initialMatch && filterListEntryStructPointer->finalMatch)  // This is both an initial and final match.
        {
            // Check the URL against the applied entry.
            if (urlString == filterListEntryStructPointer->appliedEntryList[0])
            {
                // Set the matched URL type.
                requestStructPointer->matchedUrlType = requestUrlType;

                // Check the domain status.
                return checkDomain(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
            }
        }
        else if (filterListEntryStructPointer->initialMatch)  // This is an initial match.
        {
            // Check the URL against the applied entry.
            if (urlString.startsWith(filterListEntryStructPointer->appliedEntryList[0]))
            {
                // Set the matched URL type.
                requestStructPointer->matchedUrlType = requestUrlType;

                // Check the domain status.
                return checkDomain(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
            }
        }
        else if (filterListEntryStructPointer->finalMatch)  // This is a final match.
        {
            // Check the URL against the applied entry.
            if (urlString.endsWith(filterListEntryStructPointer->appliedEntryList[0]))
            {
                // Set the matched URL type.
                requestStructPointer->matchedUrlType = requestUrlType;

                // Check the domain status.
                return checkDomain(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
            }
        }
        else  // There is no initial or final matching.
        {
            // Check if the URL string contains the applied entry.
            if (urlString.contains(filterListEntryStructPointer->appliedEntryList[0]))
            {
                // Set the matched URL type.
                requestStructPointer->matchedUrlType = requestUrlType;

                // Check the domain status.
                return checkDomain(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
            }
        }
    }
    else  // There are multiple entries.
    {
        // Process initial and final matches.
        if (filterListEntryStructPointer->initialMatch && filterListEntryStructPointer->finalMatch)  // This is both an initial and final match.
        {
            // Check the URL.
            if (urlString.startsWith(filterListEntryStructPointer->appliedEntryList[0]))
            {
                // Get the number of characters to remove from the front of the URL string.
                int charactersToRemove = filterListEntryStructPointer->appliedEntryList[0].size();

                // Remove the entry from the front of the URL string copy.
                urlString.remove(0, charactersToRemove);

                // Get the entry locations.
                int penultimateEntryNumber = (filterListEntryStructPointer->sizeOfAppliedEntryList - 1);
                int ultimateEntryIndex = penultimateEntryNumber;

                // Create a URL matches flag.
                bool urlMatches = true;

                // Check all the middle entries.
                for (int i = 1; i < penultimateEntryNumber; ++i)
                {
                    // Get the applied entry index, which will be `-1` if it doesn't exist.
                    int appliedEntryIndex = urlString.indexOf(filterListEntryStructPointer->appliedEntryList[i]);

                    // Check if the entry was found.
                    if (appliedEntryIndex >= 0)  // The entry is contained in the URL string.
                    {
                        // Get the number of characters to remove from the front of the URL string.
                        int charactersToRemove = appliedEntryIndex + filterListEntryStructPointer->appliedEntryList[i].size();

                        // Remove the entry from the front of the URL string copy.
                        urlString.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)
                {
                    // Check if the URL string ends with the last applied entry.
                    if (urlString.endsWith(filterListEntryStructPointer->appliedEntryList[ultimateEntryIndex]))
                    {
                        // Set the matched URL type.
                        requestStructPointer->matchedUrlType = requestUrlType;

                        // Check the domain status.
                        return checkDomain(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
                    }
                }
            }
        }
        else if (filterListEntryStructPointer->initialMatch)  // This is an initial match.
        {
            // Check the URL.
            if (urlString.startsWith(filterListEntryStructPointer->appliedEntryList[0]))
            {
                // Get the number of characters to remove from the front of the URL string.
                int charactersToRemove = filterListEntryStructPointer->appliedEntryList[0].size();

                // Remove the entry from the front of the URL string copy.
                urlString.remove(0, charactersToRemove);

                // Create a URL matches flag.
                bool urlMatches = true;

                // Check the rest of the entries.
                for (int i = 1; i < filterListEntryStructPointer->sizeOfAppliedEntryList; ++i)
                {
                    // Get the applied entry index, which will be `-1` if it doesn't exist.
                    int appliedEntryIndex = urlString.indexOf(filterListEntryStructPointer->appliedEntryList[i]);

                    // Check if the entry was found.
                    if (appliedEntryIndex >= 0)  // The entry is contained in the URL string.
                    {
                        // Get the number of characters to remove from the front of the URL string.
                        int charactersToRemove = appliedEntryIndex + filterListEntryStructPointer->appliedEntryList[i].size();

                        // Remove the entry from the front of the URL string copy.
                        urlString.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 still matches.
                if (urlMatches)
                {
                    // Set the matched URL type.
                    requestStructPointer->matchedUrlType = requestUrlType;

                    // Check the domain status.
                    return checkDomain(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
                }
            }
        }
        else if (filterListEntryStructPointer->finalMatch)  // This is a final match.
        {
            // Get the entry locations.
            int penultimateEntryNumber = (filterListEntryStructPointer->sizeOfAppliedEntryList - 1);
            int ultimateEntryIndex = penultimateEntryNumber;

            // Create a URL matches flag.
            bool urlMatches = true;

            // Check all the entries except the last one.
            for (int i = 0; i < penultimateEntryNumber; ++i)
            {
                // Get the applied entry index, which will be `-1` if it doesn't exist.
                int appliedEntryIndex = urlString.indexOf(filterListEntryStructPointer->appliedEntryList[i]);

                // Check if the entry was found.
                if (appliedEntryIndex >= 0)  // The entry is contained in the URL string.
                {
                    // Get the number of characters to remove from the front of the URL string.
                    int charactersToRemove = appliedEntryIndex + filterListEntryStructPointer->appliedEntryList[i].size();

                    // Remove the entry from the front of the URL string copy.
                    urlString.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)
            {
                // Check if the URL string ends with the last applied entry.
                if (urlString.endsWith(filterListEntryStructPointer->appliedEntryList[ultimateEntryIndex]))
                {
                    // Set the matched URL type.
                    requestStructPointer->matchedUrlType = requestUrlType;

                    // Check the domain status.
                    return checkDomain(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
                }
            }
        }
        else  // There is no initial or final matching.
        {
            // Create a URL matches flag.
            bool urlMatches = true;

            // Check the URL.
            for (int i = 0; i < filterListEntryStructPointer->sizeOfAppliedEntryList; ++i)
            {
                // Get the applied entry index, which will be `-1` if it doesn't exist.
                int appliedEntryIndex = urlString.indexOf(filterListEntryStructPointer->appliedEntryList[i]);

                // Check if the entry was found.
                if (appliedEntryIndex >= 0)  // The entry is contained in the URL string.
                {
                    // Get the number of characters to remove from the front of the URL string.
                    int charactersToRemove = appliedEntryIndex + filterListEntryStructPointer->appliedEntryList[i].size();

                    // Remove the entry from the front of the URL string copy.
                    urlString.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 still matches.
            if (urlMatches)
            {
                // Set the matched URL type.
                requestStructPointer->matchedUrlType = requestUrlType;

                // Check the domain status.
                return checkDomain(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
            }
        }
    }

    // The applied entry doesn't match.  Return `true` to continue processing the URL request.
    return true;
}

bool FilterListHelper::checkRegularExpression(QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer, const QString &filterListTitle, const int sublistInt,
                                              FilterListEntryStruct *filterListEntryStructPointer) const
{
    // Create an applied entry regular expression.
    QRegularExpression appliedEntryRegularExpression(filterListEntryStructPointer->appliedEntryList[0]);

    // Check if the regular expression matches the applied entry.
    if (requestStructPointer->urlString.contains(appliedEntryRegularExpression))
    {
        // Check the domain status.
        return checkDomain(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
    }

    // If the regular expression doesn't match, return `true` to continue processing the URL request.
    return true;
}

bool FilterListHelper::checkDomain(QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer, const QString &filterListTitle, const int sublistInt,
                                   FilterListEntryStruct *filterListEntryStructPointer) const
{
    // Check domain status.
    if (filterListEntryStructPointer->domain == FilterOptionDisposition::Null)  // Ignore domain status.
    {
        // Check the third-party status.
        return checkThirdParty(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
    }
    else if (filterListEntryStructPointer->domain == FilterOptionDisposition::Apply)  // Block requests from listed domains.
    {
        // Check each domain.
        for (QString blockedDomain : filterListEntryStructPointer->domainList)
        {
            // Check if the request came from a blocked domain.
            if (requestStructPointer->firstPartyHost.endsWith(blockedDomain))
            {
                // Check the third-party status.
                return checkThirdParty(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
            }
        }
    }
    else if (filterListEntryStructPointer->domain == FilterOptionDisposition::Override)  // Block domains that are not overridden.
    {
        // Create a block domain flag.
        bool blockDomain = true;

        // Check each overridden domain.
        for (QString overriddenDomain : filterListEntryStructPointer->domainList)
        {
            // Check if the request came from an overridden domain.
            if (requestStructPointer->firstPartyHost.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, filterListTitle, sublistInt, filterListEntryStructPointer);
        }
    }

    // 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 QString &filterListTitle, const int sublistInt,
                                       FilterListEntryStruct *filterListEntryStructPointer) const
{
    // Check third-party status.
    if (filterListEntryStructPointer->thirdParty == FilterOptionDisposition::Null)  // Ignore third-party status.
    {
        // Check if request options are applied.
        if (filterListEntryStructPointer->hasRequestOptions)  // Request options are applied.
        {
            // Check the request options.
            return checkRequestOptions(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
        }
        else  // Request options are not applied.
        {
            // Process the request.
            return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
        }
    }
    else if ((filterListEntryStructPointer->thirdParty == FilterOptionDisposition::Apply) && requestStructPointer->isThirdPartyRequest)  // Block third-party request.
    {
        // Check if request options are applied.
        if (filterListEntryStructPointer->hasRequestOptions)  // Request options are applied.
        {
            // Check the request options.
            return checkRequestOptions(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
        }
        else  // Request options are not applied.
        {
            // Process the request.
            return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
        }
    }
    else if ((filterListEntryStructPointer->thirdParty == FilterOptionDisposition::Override) && !requestStructPointer->isThirdPartyRequest)  // Block first-party requests.
    {
        // Check if request options are applied.
        if (filterListEntryStructPointer->hasRequestOptions)  // Request options are applied.
        {
            // Check the request options.
            return checkRequestOptions(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
        }
        else  // Request options are not applied.
        {
            // Process the request.
            return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);
        }
    }

    // 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,
                                           FilterListEntryStruct *filterListEntryStructPointer) const
{
    // Process font requests.
    if ((filterListEntryStructPointer->font == FilterOptionDisposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeFontResource))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // Process image requests.
    if ((filterListEntryStructPointer->image == FilterOptionDisposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeImage))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // Process main frame requests.
    if ((filterListEntryStructPointer->mainFrame == FilterOptionDisposition::Apply) && ((requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeMainFrame) ||
                                                                              (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeNavigationPreloadMainFrame)))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // Process media requests.
    if ((filterListEntryStructPointer->media == FilterOptionDisposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeMedia))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // Process object requests.
    if ((filterListEntryStructPointer->object == FilterOptionDisposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeObject))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // Process other requests.
    if ((filterListEntryStructPointer->other == FilterOptionDisposition::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)))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // Process ping requests
    if ((filterListEntryStructPointer->ping == FilterOptionDisposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypePing))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // Process script requests.
    if ((filterListEntryStructPointer->script == FilterOptionDisposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeScript))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // Process style sheet requests.
    if ((filterListEntryStructPointer->styleSheet == FilterOptionDisposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeStylesheet))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // Process sub resource requests.
    if ((filterListEntryStructPointer->subFrame == FilterOptionDisposition::Apply) && ((requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeSubFrame) ||
                                                                             (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeNavigationPreloadSubFrame)))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // Process web socket requests.
    if ((filterListEntryStructPointer->webSocket == FilterOptionDisposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeWebSocket))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // Process XML HTTP requests.
    if ((filterListEntryStructPointer->xmlHttpRequest == FilterOptionDisposition::Apply) && (requestStructPointer->resourceTypeInt == QWebEngineUrlRequestInfo::ResourceTypeXhr))
        return processRequest(urlRequestInfo, requestStructPointer, filterListTitle, sublistInt, filterListEntryStructPointer);

    // The request options specified don't match this request.  Return `true` to continue processing the URL request.
    return true;
}

bool FilterListHelper::processRequest(QWebEngineUrlRequestInfo &urlRequestInfo, RequestStruct *requestStructPointer, const QString &filterListTitle, const int sublistInt,
                                    FilterListEntryStruct *filterListEntryStructPointer) const
{
    // Set the request disposition.
    if (sublistInt <= 2)
        requestStructPointer->dispositionInt = ALLOWED;
    else
        requestStructPointer->dispositionInt = BLOCKED;

    // Populate the request struct.
    requestStructPointer->filterListTitle = filterListTitle;
    requestStructPointer->sublistInt = sublistInt;
    requestStructPointer->filterListEntryStruct = *filterListEntryStructPointer;

    // Block the request if specified.
    if (requestStructPointer->dispositionInt == BLOCKED)
        urlRequestInfo.block(true);

    // Log the disposition.
    //qDebug().noquote().nospace() << "Request processed:  " << 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.
    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::getRequestOptionDispositionString(const FilterOptionDisposition filterOptionDisposition) const
{
    // Return the translated filter option disposition string.
    switch (filterOptionDisposition)
    {
        case FilterOptionDisposition::Apply: return FILTER_OPTION_APPLY;
        case FilterOptionDisposition::Override: return FILTER_OPTION_OVERRIDE;
        default: return FILTER_OPTION_NULL;
    }
}

QString FilterListHelper::getResourceTypeString(int resourceTypeInt) const
{
    // Return the translated resource type string (in order of their values <https://doc.qt.io/qt-6/qwebengineurlrequestinfo.html#ResourceType-enum>).
    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;
        case QWebEngineUrlRequestInfo::ResourceTypeWebSocket: return RESOURCE_TYPE_WEB_SOCKET;
        case QWebEngineUrlRequestInfo::ResourceTypeJson: return RESOURCE_TYPE_JSON;
        default: return RESOURCE_TYPE_UNKNOWN;
    }
}

QString FilterListHelper::getSublistName(int sublistInt) const
{
    // Return the name of the requested sublist.
    switch (sublistInt)
    {
        case MAIN_ALLOWLIST: return MAIN_ALLOWLIST_STRING;
        case INITIAL_DOMAIN_ALLOWLIST: return INITIAL_DOMAIN_ALLOWLIST_STRING;
        case REGULAR_EXPRESSION_ALLOWLIST: return REGULAR_EXPRESSION_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.
    }
}

FilterListStruct* FilterListHelper::populateFilterList(const QString &filterListFileName) const
{
    // Get the filter list file.
    QFile filterListFile(filterListFileName);

    // Open the filter list file.
    bool fileOpenSuccessful = filterListFile.open(QIODevice::ReadOnly);

    // Exit here if the file open was not successful.
    if (!fileOpenSuccessful)
        return new FilterListStruct;

    // 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 the temporary filter list entry string.
    QString filterListEntryString;

    // Parse each line of the filter list.
    while (filterListTextStream.readLineInto(&filterListEntryString)) {
        // Create a filter list entry struct.
        FilterListEntryStruct *filterListEntryStructPointer = new FilterListEntryStruct;

        // Store the original entry.
        filterListEntryStructPointer->originalEntryString = filterListEntryString;

        // Parse the entry.
        if (filterListEntryString.isEmpty())  // Ignore empty lines.
        {
            // Do nothing.

            // Log the dropping of the line.
            //qDebug().noquote().nospace() << filterListEntryString << "  NOT added from  " << filterListFileName << "  (empty line).";
        }
        else if (filterListEntryString.startsWith(QLatin1Char('[')))  // The line starts with `[`, which is the file format.
        {
            // Do nothing.

            // Log the dropping of the line.
            //qDebug().noquote().nospace() << filterListEntryString << "  NOT added from  " << filterListFileName << "  (file format).";
        }
        else if (filterListEntryString.contains(QLatin1String("##")) ||
                 filterListEntryString.contains(QLatin1String("#?#")) ||
                 filterListEntryString.contains(QLatin1String("#@#")) ||
                 filterListEntryString.contains(QLatin1String("#$#")))  // The line contains unimplemented content filtering.
        {
            // Do nothing.

            // Log the dropping of the line.
            //qDebug().noquote().nospace() << filterListEntryString << "  NOT added from  " << filterListFileName << "  (content filtering).";
        }
        else if (filterListEntryString.startsWith(QLatin1Char('!')))  // The line starts with `!`, which are comments.
        {
            if (filterListEntryString.startsWith(QLatin1String("! Title: ")))  // The line contains the title.
            {
                // Add the title to the filter list struct.
                filterListStructPointer->title = filterListEntryString.remove(0, 9);

                // Log the addition of the filter list title.
                //qDebug().noquote().nospace() << "Filter list title:  " << filterListEntryString << "  ADDED from  " << filterListFileName << ".";
            }
            else if (filterListEntryString.startsWith(QLatin1String("! Version: ")))  // The line contains the version.
            {
                // Add the version to the filter list struct.
                filterListStructPointer->version = filterListEntryString.remove(0, 11);

                // Log the addition of the filter list version.
                //qDebug().noquote().nospace() << "Filter list version:  " << filterListEntryString << "  ADDED from  " << filterListFileName << ".";
            }
            else  // The comment is not important
            {
                // Else do nothing.

                // Log the dropping of the line.
                //qDebug().noquote().nospace() << filterListEntryString << "  NOT added from  " << filterListFileName << "  (comment).";
            }
        }
        else  // Process the entry.
        {
            // Get the index of the last dollar sign.
            int indexOfLastDollarSign = filterListEntryString.lastIndexOf(QLatin1Char('$'));

            // Process the filter options if they exist.
            if (indexOfLastDollarSign > -1)
            {
                // Store the original filter options.
                filterListEntryStructPointer->originalFilterOptionsString = filterListEntryString.section(QLatin1Char('$'), -1);

                // Store the entry without the filter options as the filter list string.
                filterListEntryString.truncate(indexOfLastDollarSign);

                // Split the filter options.
                QStringList originalFilterOptionsList = filterListEntryStructPointer->originalFilterOptionsString.split(QLatin1Char(','));

                // Populate the applied filter options list.
                for (QString filterOptionString : originalFilterOptionsList)
                {
                    // Only add filter options that are handled by Privacy Browser.  <https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters>
                    // `csp` specifies a Content Security Policy, which isn't handled by Privacy Browser.
                    // `method` appears to specify the request method, but that isn't an official filter option, and Privacy Browser has chosen to ignore it.
                    // `redirect` is probably a misspelling of the `rewrite` option.
                    // `rewrite` would be difficult to implement, so it isn't used by Privacy Browser.
                    // `1p` is not an official filter option.
                    // `elemhide` isn't implemented by Privacy Browser.
                    // `genericblock` isn't implemented by Privacy Browser.
                    // `generichide` isn't implemented by Privacy Browser.
                    // `match-case` isn't currently implemented by Privacy Browser, but it could be if that became important.
                    // `xhr` is probably a misspelling of `xmlhttprequest`.
                    if (!(filterOptionString.startsWith(QLatin1String("csp=")) ||
                          filterOptionString.startsWith(QLatin1String("method=")) ||
                          filterOptionString.startsWith(QLatin1String("redirect=")) ||
                          filterOptionString.startsWith(QLatin1String("rewrite=")) ||
                          (filterOptionString == (QLatin1String("1p"))) ||
                          (filterOptionString == (QLatin1String("elemhide"))) ||
                          (filterOptionString == (QLatin1String("genericblock"))) ||
                          (filterOptionString == (QLatin1String("generichide"))) ||
                          (filterOptionString == (QLatin1String("match-case"))) ||
                          (filterOptionString == (QLatin1String("xhr")))))
                        // Add the filter option to the applied filter options list.
                        filterListEntryStructPointer->appliedFilterOptionsList.append(filterOptionString);
                }

                // Initialize an override struct.
                FilterOptionsOverrideStruct filterOptionsOverrideStruct;

                // Populate the filter options entries.
                for (QString filterOptionString : filterListEntryStructPointer->appliedFilterOptionsList)
                {
                    // Parse the filter options.
                    if (filterOptionString.startsWith(QLatin1String("domain=")))  // Domain.
                    {
                        // Remove `domain=` from the filter option.
                        filterOptionString.remove(0, 7);

                        // Set the disposition according to the domain type.
                        if (filterOptionString.contains(QLatin1Char('~')))  // Override domains.
                        {
                            // Remove the tilde from the beginning of each domain.
                            filterOptionString.remove(QLatin1Char('~'));

                            // Populate the domain filter disposition.
                            filterListEntryStructPointer->domain = FilterOptionDisposition::Override;
                        }
                        else  // Standard domains.
                        {
                            // Populate the domain filter disposition.
                            filterListEntryStructPointer->domain = FilterOptionDisposition::Apply;
                        }

                        // Store the domain list.
                        filterListEntryStructPointer->domainList = filterOptionString.split(QLatin1Char('|'));
                    }
                    else if (filterOptionString == QLatin1String("third-party"))  // Third-party.
                    {
                        // Populate the third-party filter disposition.
                        filterListEntryStructPointer->thirdParty = FilterOptionDisposition::Apply;
                    }
                    else if (filterOptionString == QLatin1String("~third-party"))  // Third-party override.
                    {
                        // Populate the third-party filter disposition.
                        filterListEntryStructPointer->thirdParty = FilterOptionDisposition::Override;
                    }
                    else if ((filterOptionString == QLatin1String("document")) || (filterOptionString == QLatin1String("popup")))  // Document (and popup).
                    {
                        // Populate the main frame disposition.
                        filterListEntryStructPointer->mainFrame = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if (filterOptionString == QLatin1String("font"))  // Font.
                    {
                        // Populate the font disposition.
                        filterListEntryStructPointer->font = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if (filterOptionString == QLatin1String("image"))  // Image.
                    {
                        // Populate the image disposition.
                        filterListEntryStructPointer->image = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if (filterOptionString == QLatin1String("media"))  // Media.
                    {
                        // Populate the media disposition.
                        filterListEntryStructPointer->media = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if (filterOptionString == QLatin1String("object"))  // Object.
                    {
                        // Populate the object disposition.
                        filterListEntryStructPointer->object = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if ((filterOptionString == QLatin1String("other")) || (filterOptionString == QLatin1String("webrtc")))  // Other.
                    {
                        // Populate the other disposition.
                        filterListEntryStructPointer->other = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if (filterOptionString == QLatin1String("ping"))  // Ping.
                    {
                        // Populate the ping disposition.
                        filterListEntryStructPointer->ping = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if (filterOptionString == QLatin1String("script"))  // Script.
                    {
                        // Populate the script disposition.
                        filterListEntryStructPointer->script = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if (filterOptionString == QLatin1String("stylesheet"))  // Style sheet.
                    {
                        // Populate the script disposition.
                        filterListEntryStructPointer->styleSheet = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if (filterOptionString == QLatin1String("subdocument"))  // Sub document.
                    {
                        // Populate the sub resource disposition.
                        filterListEntryStructPointer->subFrame = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if (filterOptionString == QLatin1String("websocket"))  // Web socket.
                    {
                        // Populate the web socket disposition.
                        filterListEntryStructPointer->webSocket = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if (filterOptionString == QLatin1String("xmlhttprequest"))  // XML HTTP request.
                    {
                        //Populate the XML HTTP request disposition.
                        filterListEntryStructPointer->xmlHttpRequest = FilterOptionDisposition::Apply;

                        // Set the has request options flag.
                        filterListEntryStructPointer->hasRequestOptions = true;
                    }
                    else if (filterOptionString == QLatin1String("~document"))  // Document override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.mainFrame = true;
                    }
                    else if (filterOptionString == QLatin1String("~font"))  // Font override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.font = true;
                    }
                    else if (filterOptionString == QLatin1String("~image"))  // Image override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.image = true;
                    }
                    else if (filterOptionString == QLatin1String("~media"))  // Media override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.media = true;
                    }
                    else if (filterOptionString == QLatin1String("~object"))  // Object override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.object = true;
                    }
                    else if ((filterOptionString == QLatin1String("~other")) || (filterOptionString == QLatin1String("~webrtc")))  // Other override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.other = true;
                    }
                    else if (filterOptionString == QLatin1String("~ping"))  // Ping override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.ping = true;
                    }
                    else if (filterOptionString == QLatin1String("~script"))  // Script override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.script = true;
                    }
                    else if (filterOptionString == QLatin1String("~stylesheet"))  // Style sheet override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.styleSheet = true;
                    }
                    else if (filterOptionString == QLatin1String("~subdocument"))  // Sub document override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.subFrame = true;
                    }
                    else if (filterOptionString == QLatin1String("~websocket"))  // Web socket override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.webSocket = true;
                    }
                    else if (filterOptionString == QLatin1String("~xmlhttprequest"))  // XML HTTP request override.
                    {
                        // Populate the override struct.
                        filterOptionsOverrideStruct.hasOverride = true;
                        filterOptionsOverrideStruct.xmlHttpRequest = true;
                    }
                }

                // Apply the overrides.
                if (filterOptionsOverrideStruct.hasOverride)
                {
                    // Set the has request options flag.
                    filterListEntryStructPointer->hasRequestOptions = true;

                    // Font.
                    if (filterOptionsOverrideStruct.font)
                        filterListEntryStructPointer->font = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->font = FilterOptionDisposition::Apply;

                    // Image.
                    if (filterOptionsOverrideStruct.image)
                        filterListEntryStructPointer->image = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->image = FilterOptionDisposition::Apply;

                    // Main Frame (document).
                    if (filterOptionsOverrideStruct.mainFrame)
                        filterListEntryStructPointer->mainFrame = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->mainFrame = FilterOptionDisposition::Apply;

                    // Media.
                    if (filterOptionsOverrideStruct.media)
                        filterListEntryStructPointer->media = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->media = FilterOptionDisposition::Apply;

                    // Object.
                    if (filterOptionsOverrideStruct.object)
                        filterListEntryStructPointer->object = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->object = FilterOptionDisposition::Apply;

                    // Other.
                    if (filterOptionsOverrideStruct.other)
                        filterListEntryStructPointer->other = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->other = FilterOptionDisposition::Apply;

                    // Ping.
                    if (filterOptionsOverrideStruct.ping)
                        filterListEntryStructPointer->ping = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->ping = FilterOptionDisposition::Apply;

                    //  Script.
                    if (filterOptionsOverrideStruct.script)
                        filterListEntryStructPointer->script = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->script = FilterOptionDisposition::Apply;

                    // Style Sheet.
                    if (filterOptionsOverrideStruct.styleSheet)
                        filterListEntryStructPointer->styleSheet = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->styleSheet = FilterOptionDisposition::Apply;

                    // Sub Resource.
                    if (filterOptionsOverrideStruct.subFrame)
                        filterListEntryStructPointer->subFrame = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->subFrame = FilterOptionDisposition::Apply;

                    // Web Socket.
                    if (filterOptionsOverrideStruct.webSocket)
                        filterListEntryStructPointer->webSocket = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->webSocket = FilterOptionDisposition::Apply;

                    // XML HTTP Request.
                    if (filterOptionsOverrideStruct.xmlHttpRequest)
                        filterListEntryStructPointer->xmlHttpRequest = FilterOptionDisposition::Override;
                    else
                        filterListEntryStructPointer->xmlHttpRequest = FilterOptionDisposition::Apply;
                }
            }  // Finish processing filter options.

            // Process the base entry.
            if ((filterListEntryStructPointer->originalFilterOptionsString.length() > 0) &&
                !filterListEntryStructPointer->hasRequestOptions &&
                (filterListEntryStructPointer->domain == FilterOptionDisposition::Null) &&
                (filterListEntryStructPointer->thirdParty == FilterOptionDisposition::Null))
                // There were filter options, but they have all been removed because they don't apply to Privacy Browser.
            {
                // Ignore these entries as they will have unintended consequences.

                // Log the dropping of the entry.
                //qDebug().noquote().nospace() << "Unsupported filter options:  " << filterListEntryStructPointer->originalEntryString << "  NOT added from  " << filterListFileName << ".";
            }
            else if (filterListEntryString.isEmpty() && !filterListEntryStructPointer->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() << "Dropped because nothing left is applied:  " << filterListEntryStructPointer->originalEntryString << "  NOT added from  " << filterListFileName << ".";
            }
            else if (filterListEntryString.startsWith(QLatin1String("@@")))  // Process an allow list entry.
            {
                // Remove the initial `@@`.
                filterListEntryString.remove(0, 2);

                if (filterListEntryString.startsWith(QLatin1String("||")))  // Process an initial domain allow list entry.
                {
                    // Remove the initial `||`.
                    filterListEntryString.remove(0, 2);

                    // Set the initial match flag.
                    filterListEntryStructPointer->initialMatch = true;

                    // Prepare the filter list string.
                    prepareFilterListString(filterListEntryString, filterListEntryStructPointer);

                    // Add the entry struct to the initial domain allow list.
                    filterListStructPointer->initialDomainAllowListPointer->push_front(filterListEntryStructPointer);

                    // Log the addition to the filter list.
                    //qDebug().noquote().nospace() << filterListEntryStructPointer->originalEntry << " added to Initial Domain Allow List from " << filterListFileName << ".";
                }
                else if (filterListEntryString.contains(QLatin1String("\\")))  // Process a regular expression allow list entry.
                {
                    // Add the regular expression to the applied entry list.
                    filterListEntryStructPointer->appliedEntryList.append(filterListEntryString);

                    // Add the entry struct to the regular expression allow list.
                    filterListStructPointer->regularExpressionAllowListPointer->push_front(filterListEntryStructPointer);

                    // Log the addition to the filter list.
                    //qDebug().noquote().nospace() << filterListEntryStructPointer->originalEntry << " added to Regular Expression Allow List from " << filterListFileName << ".";
                }
                else  // Process a main allow list entry.
                {
                    // Prepare the filter list string.
                    prepareFilterListString(filterListEntryString, filterListEntryStructPointer);

                    // Add the entry struct to the main allow list.
                    filterListStructPointer->mainAllowListPointer->push_front(filterListEntryStructPointer);

                    // Log the addition to the filter list.
                    //qDebug().noquote().nospace() << filterListEntryStructPointer->originalEntry << " added to Main Allow List from " << filterListFileName << ".";
                }
            }
            else if (filterListEntryString.startsWith(QLatin1String("||")))  // Process an initial domain block list entry.
            {
                // Remove the initial `||`.
                filterListEntryString.remove(0, 2);

                // Set the initial match flag.
                filterListEntryStructPointer->initialMatch = true;

                // Prepare the filter list string.
                prepareFilterListString(filterListEntryString, filterListEntryStructPointer);

                // Add the entry struct to the initial domain block list.
                filterListStructPointer->initialDomainBlockListPointer->push_front(filterListEntryStructPointer);

                // Log the addition to the filter list.
                //qDebug().noquote().nospace() << filterListEntryStructPointer->originalEntryString << " added to Initial Domain Block List from " << filterListFileName << ".";
            }
            else if (filterListEntryString.contains(QLatin1String("\\")))  // Process a regular expression block list entry.
            {
                // Add the regular expression to the applied entry list.
                filterListEntryStructPointer->appliedEntryList.append(filterListEntryString);

                // Add the entry struct to the regular expression block list.
                filterListStructPointer->regularExpressionBlockListPointer->push_front(filterListEntryStructPointer);

                // Log the addition to the filter list.
                //qDebug().noquote().nospace() << filterListEntryStructPointer->originalEntryString << " added to Regular Expression Block List from " << filterListFileName << ".";
            }
            else  // Process a main block list entry.
            {
                // Prepare the filter list string.
                prepareFilterListString(filterListEntryString, filterListEntryStructPointer);

                // Add the entry struct to the main block list.
                filterListStructPointer->mainBlockListPointer->push_front(filterListEntryStructPointer);

                // Log the addition to the filter list.
                //qDebug().noquote().nospace() << filterListEntryStructPointer->originalEntry << " added to Main Block List from " << filterListFileName << ".";
            }
        }
    }

    // Close the filter list file.
    filterListFile.close();

    // Return the filter list struct pointer.
    return filterListStructPointer;
}

void FilterListHelper::prepareFilterListString(QString &filterListEntryString, FilterListEntryStruct *filterListEntryStructPointer) const
{
    // Check if this is an initial match.
    if (filterListEntryString.startsWith(QLatin1Char('|')))
    {
        // Strip the initial `|`.
        filterListEntryString.remove(0, 1);

        // Set the initial match flag.
        filterListEntryStructPointer->initialMatch = true;
    }

    // Check if this is a final match.
    if (filterListEntryString.endsWith(QLatin1Char('|')))
    {
        // Strip the final `|`.
        filterListEntryString.chop(1);

        // Set the final match flag.
        filterListEntryStructPointer->finalMatch = true;
    }

    // Remove the initial asterisk if it exists.
    if (filterListEntryString.startsWith(QLatin1Char('*')))
        filterListEntryString.remove(0, 1);

    // Remove the final asterisk if it exists.
    if (filterListEntryString.endsWith(QLatin1Char('*')))
        filterListEntryString.chop(1);

    // Split the filter list entry string and set it as the applied entry list.
    filterListEntryStructPointer->appliedEntryList = filterListEntryString.split(QLatin1Char('*'));

    // Store the size of the applied entry list.
    filterListEntryStructPointer->sizeOfAppliedEntryList = filterListEntryStructPointer->appliedEntryList.size();

    // Determine if this is a single applied entry (including an empty entry, which, amazingly, are calculated as having a size of 1).
    filterListEntryStructPointer->singleAppliedEntry = (filterListEntryStructPointer->sizeOfAppliedEntryList == 1);
}
