diff --git a/src/lib/adblock/adblockaddsubscriptiondialog.cpp b/src/lib/adblock/adblockaddsubscriptiondialog.cpp index c701a1fd..68a6d597 100644 --- a/src/lib/adblock/adblockaddsubscriptiondialog.cpp +++ b/src/lib/adblock/adblockaddsubscriptiondialog.cpp @@ -1,93 +1,93 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2017 David Rosca * * 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 . * ============================================================ */ #include "adblockaddsubscriptiondialog.h" #include "ui_adblockaddsubscriptiondialog.h" #include "adblockmanager.h" AdBlockAddSubscriptionDialog::AdBlockAddSubscriptionDialog(QWidget* parent) : QDialog(parent) , ui(new Ui::AdBlockAddSubscriptionDialog) { ui->setupUi(this); m_knownSubscriptions << Subscription(QSL("EasyList (English)"), ADBLOCK_EASYLIST_URL) << Subscription(QSL("NoCoin List"), ADBLOCK_NOCOINLIST_URL) << Subscription(QSL("BSI Lista Polska (Polish)"), QSL("http://www.bsi.info.pl/filtrABP.txt")) << Subscription(QSL("Czech List (Czech)"), QSL("http://adblock.dajbych.net/adblock.txt")) << Subscription(QSL("dutchblock (Dutch)"), QSL("http://groenewoudt.net/dutchblock/list.txt")) << Subscription(QSL("EasyList (Spanish)"), QSL("https://easylist-downloads.adblockplus.org/easylistspanish.txt")) << Subscription(QSL("IsraelList (Hebrew)"), QSL("http://secure.fanboy.co.nz/israelilist/IsraelList.txt")) << Subscription(QSL("NLBlock (Dutch)"), QSL("http://www.verzijlbergh.com/adblock/nlblock.txt")) << Subscription(QSL("Peter Lowe's list (English)"), QSL("http://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblockplus&mimetype=plaintext")) << Subscription(QSL("PLgeneral (Polish)"), QSL("http://www.niecko.pl/adblock/adblock.txt")) << Subscription(QSL("Schacks Adblock Plus liste (Danish)"), QSL("http://adblock.schack.dk/block.txt")) << Subscription(QSL("Xfiles (Italian)"), QSL("http://mozilla.gfsolone.com/filtri.txt")) << Subscription(QSL("EasyPrivacy (English)"), QSL("http://easylist-downloads.adblockplus.org/easyprivacy.txt")) << Subscription(QSL("RU Adlist (Russian)"), QSL("https://easylist-downloads.adblockplus.org/advblock.txt")) << Subscription(QSL("ABPindo (Indonesian)"), QSL("https://raw.githubusercontent.com/heradhis/indonesianadblockrules/master/subscriptions/abpindo.txt")) << Subscription(QSL("Easylist China (Chinese)"), QSL("https://easylist-downloads.adblockplus.org/easylistchina.txt")) << Subscription(QSL("Anti-Adblock Killer"), QSL("https://raw.githubusercontent.com/reek/anti-adblock-killer/master/anti-adblock-killer-filters.txt")) << Subscription(tr("Other..."), QString()); - foreach (const Subscription &subscription, m_knownSubscriptions) { + for (const Subscription &subscription : qAsConst(m_knownSubscriptions)) { ui->comboBox->addItem(subscription.title); } connect(ui->comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(indexChanged(int))); indexChanged(0); } QString AdBlockAddSubscriptionDialog::title() const { return ui->title->text(); } QString AdBlockAddSubscriptionDialog::url() const { return ui->url->text(); } void AdBlockAddSubscriptionDialog::indexChanged(int index) { const Subscription subscription = m_knownSubscriptions.at(index); // "Other..." entry if (subscription.url.isEmpty()) { ui->title->clear(); ui->url->clear(); } else { int pos = subscription.title.indexOf(QLatin1Char('(')); QString title = subscription.title; if (pos > 0) { title = title.left(pos).trimmed(); } ui->title->setText(title); ui->title->setCursorPosition(0); ui->url->setText(subscription.url); ui->url->setCursorPosition(0); } } AdBlockAddSubscriptionDialog::~AdBlockAddSubscriptionDialog() { delete ui; } diff --git a/src/lib/adblock/adblockdialog.cpp b/src/lib/adblock/adblockdialog.cpp index 65d707d9..f7ca3cda 100644 --- a/src/lib/adblock/adblockdialog.cpp +++ b/src/lib/adblock/adblockdialog.cpp @@ -1,185 +1,186 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * 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 . * ============================================================ */ #include "adblockdialog.h" #include "adblockmanager.h" #include "adblocksubscription.h" #include "adblocktreewidget.h" #include "adblockaddsubscriptiondialog.h" #include "mainapplication.h" #include "qztools.h" #include #include #include #include AdBlockDialog::AdBlockDialog(QWidget* parent) : QDialog(parent) , m_manager(AdBlockManager::instance()) , m_currentTreeWidget(nullptr) , m_currentSubscription(nullptr) , m_loaded(false) { setAttribute(Qt::WA_DeleteOnClose); setupUi(this); QzTools::centerWidgetOnScreen(this); #ifdef Q_OS_MACOS tabWidget->setDocumentMode(false); #endif adblockCheckBox->setChecked(m_manager->isEnabled()); QMenu* menu = new QMenu(buttonOptions); m_actionAddRule = menu->addAction(tr("Add Rule"), this, &AdBlockDialog::addRule); m_actionRemoveRule = menu->addAction(tr("Remove Rule"), this, &AdBlockDialog::removeRule); menu->addSeparator(); m_actionAddSubscription = menu->addAction(tr("Add Subscription"), this, &AdBlockDialog::addSubscription); m_actionRemoveSubscription = menu->addAction(tr("Remove Subscription"), this, &AdBlockDialog::removeSubscription); menu->addAction(tr("Update Subscriptions"), m_manager, &AdBlockManager::updateAllSubscriptions); menu->addSeparator(); menu->addAction(tr("Learn about writing rules..."), this, &AdBlockDialog::learnAboutRules); buttonOptions->setMenu(menu); connect(menu, &QMenu::aboutToShow, this, &AdBlockDialog::aboutToShowMenu); connect(adblockCheckBox, &QAbstractButton::toggled, this, &AdBlockDialog::enableAdBlock); connect(search, &QLineEdit::textChanged, this, &AdBlockDialog::filterString); connect(tabWidget, &QTabWidget::currentChanged, this, &AdBlockDialog::currentChanged); connect(buttonBox, &QDialogButtonBox::accepted, this, &QWidget::close); load(); buttonBox->setFocus(); } void AdBlockDialog::showRule(const AdBlockRule* rule) const { AdBlockSubscription* subscription = rule->subscription(); if (!subscription) { return; } for (int i = 0; i < tabWidget->count(); ++i) { AdBlockTreeWidget* treeWidget = qobject_cast(tabWidget->widget(i)); if (subscription == treeWidget->subscription()) { treeWidget->showRule(rule); tabWidget->setCurrentIndex(i); break; } } } void AdBlockDialog::addRule() { m_currentTreeWidget->addRule(); } void AdBlockDialog::removeRule() { m_currentTreeWidget->removeRule(); } void AdBlockDialog::addSubscription() { AdBlockAddSubscriptionDialog dialog(this); if (dialog.exec() != QDialog::Accepted) { return; } QString title = dialog.title(); QString url = dialog.url(); if (AdBlockSubscription* subscription = m_manager->addSubscription(title, url)) { AdBlockTreeWidget* tree = new AdBlockTreeWidget(subscription, tabWidget); int index = tabWidget->insertTab(tabWidget->count() - 1, tree, subscription->title()); tabWidget->setCurrentIndex(index); } } void AdBlockDialog::removeSubscription() { if (m_manager->removeSubscription(m_currentSubscription)) { delete m_currentTreeWidget; } } void AdBlockDialog::currentChanged(int index) { if (index != -1) { m_currentTreeWidget = qobject_cast(tabWidget->widget(index)); m_currentSubscription = m_currentTreeWidget->subscription(); } } void AdBlockDialog::filterString(const QString &string) { if (m_currentTreeWidget && adblockCheckBox->isChecked()) { m_currentTreeWidget->filterString(string); } } void AdBlockDialog::enableAdBlock(bool state) { m_manager->setEnabled(state); if (state) { load(); } } void AdBlockDialog::aboutToShowMenu() { bool subscriptionEditable = m_currentSubscription && m_currentSubscription->canEditRules(); bool subscriptionRemovable = m_currentSubscription && m_currentSubscription->canBeRemoved(); m_actionAddRule->setEnabled(subscriptionEditable); m_actionRemoveRule->setEnabled(subscriptionEditable); m_actionRemoveSubscription->setEnabled(subscriptionRemovable); } void AdBlockDialog::learnAboutRules() { mApp->addNewTab(QUrl(QSL("http://adblockplus.org/en/filters"))); } void AdBlockDialog::loadSubscriptions() { for (int i = 0; i < tabWidget->count(); ++i) { AdBlockTreeWidget* treeWidget = qobject_cast(tabWidget->widget(i)); treeWidget->refresh(); } } void AdBlockDialog::load() { if (m_loaded || !adblockCheckBox->isChecked()) { return; } - foreach (AdBlockSubscription* subscription, m_manager->subscriptions()) { + const auto subscriptions = m_manager->subscriptions(); + for (AdBlockSubscription* subscription : subscriptions) { AdBlockTreeWidget* tree = new AdBlockTreeWidget(subscription, tabWidget); tabWidget->addTab(tree, subscription->title()); } m_loaded = true; QTimer::singleShot(50, this, &AdBlockDialog::loadSubscriptions); } diff --git a/src/lib/adblock/adblockmanager.cpp b/src/lib/adblock/adblockmanager.cpp index e299b4eb..7d6c003f 100644 --- a/src/lib/adblock/adblockmanager.cpp +++ b/src/lib/adblock/adblockmanager.cpp @@ -1,461 +1,462 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * 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 . * ============================================================ */ #include "adblockmanager.h" #include "adblockdialog.h" #include "adblockmatcher.h" #include "adblocksubscription.h" #include "adblockurlinterceptor.h" #include "datapaths.h" #include "mainapplication.h" #include "webpage.h" #include "qztools.h" #include "browserwindow.h" #include "settings.h" #include "networkmanager.h" #include #include #include #include #include #include #include #include #include //#define ADBLOCK_DEBUG #ifdef ADBLOCK_DEBUG #include #endif Q_GLOBAL_STATIC(AdBlockManager, qz_adblock_manager) AdBlockManager::AdBlockManager(QObject* parent) : QObject(parent) , m_loaded(false) , m_enabled(true) , m_matcher(new AdBlockMatcher(this)) , m_interceptor(new AdBlockUrlInterceptor(this)) { qRegisterMetaType(); load(); } AdBlockManager::~AdBlockManager() { qDeleteAll(m_subscriptions); } AdBlockManager* AdBlockManager::instance() { return qz_adblock_manager(); } void AdBlockManager::setEnabled(bool enabled) { if (m_enabled == enabled) { return; } m_enabled = enabled; emit enabledChanged(enabled); Settings settings; settings.beginGroup(QSL("AdBlock")); settings.setValue(QSL("enabled"), m_enabled); settings.endGroup(); load(); mApp->reloadUserStyleSheet(); QMutexLocker locker(&m_mutex); if (m_enabled) { m_matcher->update(); } else { m_matcher->clear(); } } QList AdBlockManager::subscriptions() const { return m_subscriptions; } bool AdBlockManager::block(QWebEngineUrlRequestInfo &request, QString &ruleFilter, QString &ruleSubscription) { QMutexLocker locker(&m_mutex); if (!isEnabled()) { return false; } #ifdef ADBLOCK_DEBUG QElapsedTimer timer; timer.start(); #endif const QString urlString = request.requestUrl().toEncoded().toLower(); const QString urlDomain = request.requestUrl().host().toLower(); const QString urlScheme = request.requestUrl().scheme().toLower(); if (!canRunOnScheme(urlScheme) || !canBeBlocked(request.firstPartyUrl())) { return false; } const AdBlockRule* blockedRule = m_matcher->match(request, urlDomain, urlString); if (blockedRule) { ruleFilter = blockedRule->filter(); ruleSubscription = blockedRule->subscription()->title(); #ifdef ADBLOCK_DEBUG qDebug() << "BLOCKED: " << timer.elapsed() << blockedRule->filter() << request.requestUrl(); #endif } #ifdef ADBLOCK_DEBUG qDebug() << timer.elapsed() << request.requestUrl(); #endif return blockedRule; } QVector AdBlockManager::blockedRequestsForUrl(const QUrl &url) const { return m_blockedRequests.value(url); } void AdBlockManager::clearBlockedRequestsForUrl(const QUrl &url) { if (m_blockedRequests.remove(url)) { emit blockedRequestsChanged(url); } } QStringList AdBlockManager::disabledRules() const { return m_disabledRules; } void AdBlockManager::addDisabledRule(const QString &filter) { m_disabledRules.append(filter); } void AdBlockManager::removeDisabledRule(const QString &filter) { m_disabledRules.removeOne(filter); } bool AdBlockManager::addSubscriptionFromUrl(const QUrl &url) { const QList > queryItems = QUrlQuery(url).queryItems(QUrl::FullyDecoded); QString subscriptionTitle; QString subscriptionUrl; for (int i = 0; i < queryItems.count(); ++i) { QPair pair = queryItems.at(i); if (pair.first.endsWith(QL1S("location"))) subscriptionUrl = pair.second; else if (pair.first.endsWith(QL1S("title"))) subscriptionTitle = pair.second; } if (subscriptionTitle.isEmpty() || subscriptionUrl.isEmpty()) return false; const QString message = AdBlockManager::tr("Do you want to add %1 subscription?").arg(subscriptionTitle); QMessageBox::StandardButton result = QMessageBox::question(nullptr, AdBlockManager::tr("AdBlock Subscription"), message, QMessageBox::Yes | QMessageBox::No); if (result == QMessageBox::Yes) { AdBlockManager::instance()->addSubscription(subscriptionTitle, subscriptionUrl); AdBlockManager::instance()->showDialog(); } return true; } AdBlockSubscription* AdBlockManager::addSubscription(const QString &title, const QString &url) { if (title.isEmpty() || url.isEmpty()) { return nullptr; } QString fileName = QzTools::filterCharsFromFilename(title.toLower()) + QSL(".txt"); QString filePath = QzTools::ensureUniqueFilename(DataPaths::currentProfilePath() + QSL("/adblock/") + fileName); QByteArray data = QSL("Title: %1\nUrl: %2\n[Adblock Plus 1.1.1]").arg(title, url).toLatin1(); QSaveFile file(filePath); if (!file.open(QFile::WriteOnly)) { qWarning() << "AdBlockManager: Cannot write to file" << filePath; return nullptr; } file.write(data); file.commit(); AdBlockSubscription* subscription = new AdBlockSubscription(title, this); subscription->setUrl(QUrl(url)); subscription->setFilePath(filePath); subscription->loadSubscription(m_disabledRules); m_subscriptions.insert(m_subscriptions.count() - 1, subscription); connect(subscription, &AdBlockSubscription::subscriptionUpdated, mApp, &MainApplication::reloadUserStyleSheet); connect(subscription, &AdBlockSubscription::subscriptionChanged, this, &AdBlockManager::updateMatcher); return subscription; } bool AdBlockManager::removeSubscription(AdBlockSubscription* subscription) { QMutexLocker locker(&m_mutex); if (!m_subscriptions.contains(subscription) || !subscription->canBeRemoved()) { return false; } QFile(subscription->filePath()).remove(); m_subscriptions.removeOne(subscription); m_matcher->update(); delete subscription; return true; } AdBlockCustomList* AdBlockManager::customList() const { - foreach (AdBlockSubscription* subscription, m_subscriptions) { + for (AdBlockSubscription* subscription : qAsConst(m_subscriptions)) { AdBlockCustomList* list = qobject_cast(subscription); if (list) { return list; } } return nullptr; } void AdBlockManager::load() { QMutexLocker locker(&m_mutex); if (m_loaded) { return; } #ifdef ADBLOCK_DEBUG QElapsedTimer timer; timer.start(); #endif Settings settings; settings.beginGroup(QSL("AdBlock")); m_enabled = settings.value(QSL("enabled"), m_enabled).toBool(); m_disabledRules = settings.value(QSL("disabledRules"), QStringList()).toStringList(); QDateTime lastUpdate = settings.value(QSL("lastUpdate"), QDateTime()).toDateTime(); settings.endGroup(); if (!m_enabled) { return; } QDir adblockDir(DataPaths::currentProfilePath() + QSL("/adblock")); // Create if necessary if (!adblockDir.exists()) { QDir(DataPaths::currentProfilePath()).mkdir(QSL("adblock")); } - foreach (const QString &fileName, adblockDir.entryList(QStringList(QSL("*.txt")), QDir::Files)) { + const auto fileNames = adblockDir.entryList(QStringList(QSL("*.txt")), QDir::Files); + for (const QString &fileName : fileNames) { if (fileName == QLatin1String("customlist.txt")) { continue; } const QString absolutePath = adblockDir.absoluteFilePath(fileName); QFile file(absolutePath); if (!file.open(QFile::ReadOnly)) { continue; } QTextStream textStream(&file); textStream.setCodec("UTF-8"); QString title = textStream.readLine(1024).remove(QLatin1String("Title: ")); QUrl url = QUrl(textStream.readLine(1024).remove(QLatin1String("Url: "))); if (title.isEmpty() || !url.isValid()) { qWarning() << "AdBlockManager: Invalid subscription file" << absolutePath; continue; } AdBlockSubscription* subscription = new AdBlockSubscription(title, this); subscription->setUrl(url); subscription->setFilePath(absolutePath); m_subscriptions.append(subscription); } // Add EasyList + NoCoinList if subscriptions are empty if (m_subscriptions.isEmpty()) { AdBlockSubscription *easyList = new AdBlockSubscription(tr("EasyList"), this); easyList->setUrl(QUrl(ADBLOCK_EASYLIST_URL)); easyList->setFilePath(DataPaths::currentProfilePath() + QLatin1String("/adblock/easylist.txt")); m_subscriptions.append(easyList); AdBlockSubscription *noCoinList = new AdBlockSubscription(tr("NoCoin List"), this); noCoinList->setUrl(QUrl(ADBLOCK_NOCOINLIST_URL)); noCoinList->setFilePath(DataPaths::currentProfilePath() + QLatin1String("/adblock/nocoinlist.txt")); m_subscriptions.append(noCoinList); } // Append CustomList AdBlockCustomList* customList = new AdBlockCustomList(this); m_subscriptions.append(customList); // Load all subscriptions - foreach (AdBlockSubscription* subscription, m_subscriptions) { + for (AdBlockSubscription* subscription : qAsConst(m_subscriptions)) { subscription->loadSubscription(m_disabledRules); connect(subscription, &AdBlockSubscription::subscriptionUpdated, mApp, &MainApplication::reloadUserStyleSheet); connect(subscription, &AdBlockSubscription::subscriptionChanged, this, &AdBlockManager::updateMatcher); } if (lastUpdate.addDays(5) < QDateTime::currentDateTime()) { QTimer::singleShot(1000 * 60, this, &AdBlockManager::updateAllSubscriptions); } #ifdef ADBLOCK_DEBUG qDebug() << "AdBlock loaded in" << timer.elapsed(); #endif m_matcher->update(); m_loaded = true; connect(m_interceptor, &AdBlockUrlInterceptor::requestBlocked, this, [this](const AdBlockedRequest &request) { m_blockedRequests[request.firstPartyUrl].append(request); emit blockedRequestsChanged(request.firstPartyUrl); }); mApp->networkManager()->installUrlInterceptor(m_interceptor); } void AdBlockManager::updateMatcher() { QMutexLocker locker(&m_mutex); mApp->networkManager()->removeUrlInterceptor(m_interceptor); m_matcher->update(); mApp->networkManager()->installUrlInterceptor(m_interceptor); } void AdBlockManager::updateAllSubscriptions() { - foreach (AdBlockSubscription* subscription, m_subscriptions) { + for (AdBlockSubscription* subscription : qAsConst(m_subscriptions)) { subscription->updateSubscription(); } Settings settings; settings.beginGroup(QSL("AdBlock")); settings.setValue(QSL("lastUpdate"), QDateTime::currentDateTime()); settings.endGroup(); } void AdBlockManager::save() { if (!m_loaded) { return; } - foreach (AdBlockSubscription* subscription, m_subscriptions) { + for (AdBlockSubscription* subscription : qAsConst(m_subscriptions)) { subscription->saveSubscription(); } Settings settings; settings.beginGroup(QSL("AdBlock")); settings.setValue(QSL("enabled"), m_enabled); settings.setValue(QSL("disabledRules"), m_disabledRules); settings.endGroup(); } bool AdBlockManager::isEnabled() const { return m_enabled; } bool AdBlockManager::canRunOnScheme(const QString &scheme) const { return !(scheme == QL1S("file") || scheme == QL1S("qrc") || scheme == QL1S("view-source") || scheme == QL1S("falkon") || scheme == QL1S("data") || scheme == QL1S("abp")); } bool AdBlockManager::canBeBlocked(const QUrl &url) const { return !m_matcher->adBlockDisabledForUrl(url); } QString AdBlockManager::elementHidingRules(const QUrl &url) const { if (!isEnabled() || !canRunOnScheme(url.scheme()) || !canBeBlocked(url)) return QString(); return m_matcher->elementHidingRules(); } QString AdBlockManager::elementHidingRulesForDomain(const QUrl &url) const { if (!isEnabled() || !canRunOnScheme(url.scheme()) || !canBeBlocked(url)) return QString(); return m_matcher->elementHidingRulesForDomain(url.host()); } AdBlockSubscription* AdBlockManager::subscriptionByName(const QString &name) const { - foreach (AdBlockSubscription* subscription, m_subscriptions) { + for (AdBlockSubscription* subscription : qAsConst(m_subscriptions)) { if (subscription->title() == name) { return subscription; } } return nullptr; } AdBlockDialog *AdBlockManager::showDialog(QWidget *parent) { if (!m_adBlockDialog) { m_adBlockDialog = new AdBlockDialog(parent ? parent : mApp->getWindow()); } m_adBlockDialog.data()->show(); m_adBlockDialog.data()->raise(); m_adBlockDialog.data()->activateWindow(); return m_adBlockDialog.data(); } void AdBlockManager::showRule() { if (QAction* action = qobject_cast(sender())) { const AdBlockRule* rule = static_cast(action->data().value()); if (rule) { showDialog()->showRule(rule); } } } diff --git a/src/lib/adblock/adblockmatcher.cpp b/src/lib/adblock/adblockmatcher.cpp index 90927538..62954951 100644 --- a/src/lib/adblock/adblockmatcher.cpp +++ b/src/lib/adblock/adblockmatcher.cpp @@ -1,219 +1,221 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2014-2017 David Rosca * * 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 . * ============================================================ */ #include "adblockmatcher.h" #include "adblockmanager.h" #include "adblockrule.h" #include "adblocksubscription.h" AdBlockMatcher::AdBlockMatcher(AdBlockManager* manager) : QObject(manager) , m_manager(manager) { } AdBlockMatcher::~AdBlockMatcher() { clear(); } const AdBlockRule* AdBlockMatcher::match(const QWebEngineUrlRequestInfo &request, const QString &urlDomain, const QString &urlString) const { // Exception rules if (m_networkExceptionTree.find(request, urlDomain, urlString)) return 0; int count = m_networkExceptionRules.count(); for (int i = 0; i < count; ++i) { const AdBlockRule* rule = m_networkExceptionRules.at(i); if (rule->networkMatch(request, urlDomain, urlString)) return 0; } // Block rules if (const AdBlockRule* rule = m_networkBlockTree.find(request, urlDomain, urlString)) return rule; count = m_networkBlockRules.count(); for (int i = 0; i < count; ++i) { const AdBlockRule* rule = m_networkBlockRules.at(i); if (rule->networkMatch(request, urlDomain, urlString)) return rule; } return 0; } bool AdBlockMatcher::adBlockDisabledForUrl(const QUrl &url) const { int count = m_documentRules.count(); for (int i = 0; i < count; ++i) if (m_documentRules.at(i)->urlMatch(url)) return true; return false; } bool AdBlockMatcher::elemHideDisabledForUrl(const QUrl &url) const { if (adBlockDisabledForUrl(url)) return true; int count = m_elemhideRules.count(); for (int i = 0; i < count; ++i) if (m_elemhideRules.at(i)->urlMatch(url)) return true; return false; } QString AdBlockMatcher::elementHidingRules() const { return m_elementHidingRules; } QString AdBlockMatcher::elementHidingRulesForDomain(const QString &domain) const { QString rules; int addedRulesCount = 0; int count = m_domainRestrictedCssRules.count(); for (int i = 0; i < count; ++i) { const AdBlockRule* rule = m_domainRestrictedCssRules.at(i); if (!rule->matchDomain(domain)) continue; if (Q_UNLIKELY(addedRulesCount == 1000)) { rules.append(rule->cssSelector()); rules.append(QL1S("{display:none !important;}\n")); addedRulesCount = 0; } else { rules.append(rule->cssSelector() + QLatin1Char(',')); addedRulesCount++; } } if (addedRulesCount != 0) { rules = rules.left(rules.size() - 1); rules.append(QL1S("{display:none !important;}\n")); } return rules; } void AdBlockMatcher::update() { clear(); QHash cssRulesHash; QVector exceptionCssRules; - foreach (AdBlockSubscription* subscription, m_manager->subscriptions()) { - foreach (const AdBlockRule* rule, subscription->allRules()) { + const auto subscriptions = m_manager->subscriptions(); + for (AdBlockSubscription* subscription : subscriptions) { + const auto rules = subscription->allRules(); + for (const AdBlockRule* rule : rules) { // Don't add internally disabled rules to cache if (rule->isInternalDisabled()) continue; if (rule->isCssRule()) { // We will add only enabled css rules to cache, because there is no enabled/disabled // check on match. They are directly embedded to pages. if (!rule->isEnabled()) continue; if (rule->isException()) exceptionCssRules.append(rule); else cssRulesHash.insert(rule->cssSelector(), rule); } else if (rule->isDocument()) { m_documentRules.append(rule); } else if (rule->isElemhide()) { m_elemhideRules.append(rule); } else if (rule->isException()) { if (!m_networkExceptionTree.add(rule)) m_networkExceptionRules.append(rule); } else { if (!m_networkBlockTree.add(rule)) m_networkBlockRules.append(rule); } } } - foreach (const AdBlockRule* rule, exceptionCssRules) { + for (const AdBlockRule* rule : qAsConst(exceptionCssRules)) { const AdBlockRule* originalRule = cssRulesHash.value(rule->cssSelector()); // If we don't have this selector, the exception does nothing if (!originalRule) continue; AdBlockRule* copiedRule = originalRule->copy(); copiedRule->m_options |= AdBlockRule::DomainRestrictedOption; copiedRule->m_blockedDomains.append(rule->m_allowedDomains); cssRulesHash[rule->cssSelector()] = copiedRule; m_createdRules.append(copiedRule); } // Apparently, excessive amount of selectors for one CSS rule is not what WebKit likes. // (In my testings, 4931 is the number that makes it crash) // So let's split it by 1000 selectors... int hidingRulesCount = 0; QHashIterator it(cssRulesHash); while (it.hasNext()) { it.next(); const AdBlockRule* rule = it.value(); if (rule->isDomainRestricted()) { m_domainRestrictedCssRules.append(rule); } else if (Q_UNLIKELY(hidingRulesCount == 1000)) { m_elementHidingRules.append(rule->cssSelector()); m_elementHidingRules.append(QL1S("{display:none !important;} ")); hidingRulesCount = 0; } else { m_elementHidingRules.append(rule->cssSelector() + QLatin1Char(',')); hidingRulesCount++; } } if (hidingRulesCount != 0) { m_elementHidingRules = m_elementHidingRules.left(m_elementHidingRules.size() - 1); m_elementHidingRules.append(QL1S("{display:none !important;} ")); } } void AdBlockMatcher::clear() { m_networkExceptionTree.clear(); m_networkExceptionRules.clear(); m_networkBlockTree.clear(); m_networkBlockRules.clear(); m_domainRestrictedCssRules.clear(); m_elementHidingRules.clear(); m_documentRules.clear(); m_elemhideRules.clear(); qDeleteAll(m_createdRules); m_createdRules.clear(); } diff --git a/src/lib/adblock/adblockrule.cpp b/src/lib/adblock/adblockrule.cpp index 5251b5ea..15041509 100644 --- a/src/lib/adblock/adblockrule.cpp +++ b/src/lib/adblock/adblockrule.cpp @@ -1,848 +1,849 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2017 David Rosca * * 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 . * ============================================================ */ /** * Copyright (c) 2009, Zsombor Gegesy * Copyright (c) 2009, Benjamin C. Meyer * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Benjamin Meyer nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "adblockrule.h" #include "adblocksubscription.h" #include "qztools.h" #include #include #include #include #include static QString toSecondLevelDomain(const QUrl &url) { const QString topLevelDomain = url.topLevelDomain(); const QString urlHost = url.host(); if (topLevelDomain.isEmpty() || urlHost.isEmpty()) { return QString(); } QString domain = urlHost.left(urlHost.size() - topLevelDomain.size()); if (domain.count(QL1C('.')) == 0) { return urlHost; } while (domain.count(QL1C('.')) != 0) { domain = domain.mid(domain.indexOf(QL1C('.')) + 1); } return domain + topLevelDomain; } AdBlockRule::AdBlockRule(const QString &filter, AdBlockSubscription* subscription) : m_subscription(subscription) , m_type(StringContainsMatchRule) , m_caseSensitivity(Qt::CaseInsensitive) , m_isEnabled(true) , m_isException(false) , m_isInternalDisabled(false) , m_regExp(nullptr) { setFilter(filter); } AdBlockRule::~AdBlockRule() { delete m_regExp; } AdBlockRule* AdBlockRule::copy() const { AdBlockRule* rule = new AdBlockRule(); rule->m_subscription = m_subscription; rule->m_type = m_type; rule->m_options = m_options; rule->m_exceptions = m_exceptions; rule->m_filter = m_filter; rule->m_matchString = m_matchString; rule->m_caseSensitivity = m_caseSensitivity; rule->m_isEnabled = m_isEnabled; rule->m_isException = m_isException; rule->m_isInternalDisabled = m_isInternalDisabled; rule->m_allowedDomains = m_allowedDomains; rule->m_blockedDomains = m_blockedDomains; if (m_regExp) { rule->m_regExp = new RegExp; rule->m_regExp->regExp = m_regExp->regExp; rule->m_regExp->matchers = m_regExp->matchers; } return rule; } AdBlockSubscription* AdBlockRule::subscription() const { return m_subscription; } void AdBlockRule::setSubscription(AdBlockSubscription* subscription) { m_subscription = subscription; } QString AdBlockRule::filter() const { return m_filter; } void AdBlockRule::setFilter(const QString &filter) { m_filter = filter; parseFilter(); } bool AdBlockRule::isCssRule() const { return m_type == CssRule; } QString AdBlockRule::cssSelector() const { return m_matchString; } bool AdBlockRule::isDocument() const { return hasOption(DocumentOption); } bool AdBlockRule::isElemhide() const { return hasOption(ElementHideOption); } bool AdBlockRule::isDomainRestricted() const { return hasOption(DomainRestrictedOption); } bool AdBlockRule::isException() const { return m_isException; } bool AdBlockRule::isComment() const { return m_filter.startsWith(QL1C('!')); } bool AdBlockRule::isEnabled() const { return m_isEnabled; } void AdBlockRule::setEnabled(bool enabled) { m_isEnabled = enabled; } bool AdBlockRule::isSlow() const { return m_regExp != nullptr; } bool AdBlockRule::isInternalDisabled() const { return m_isInternalDisabled; } bool AdBlockRule::urlMatch(const QUrl &url) const { if (!hasOption(DocumentOption) && !hasOption(ElementHideOption)) { return false; } const QString encodedUrl = url.toEncoded(); const QString domain = url.host(); return stringMatch(domain, encodedUrl); } bool AdBlockRule::networkMatch(const QWebEngineUrlRequestInfo &request, const QString &domain, const QString &encodedUrl) const { if (m_type == CssRule || !m_isEnabled || m_isInternalDisabled) { return false; } bool matched = stringMatch(domain, encodedUrl); if (matched) { // Check domain restrictions if (hasOption(DomainRestrictedOption) && !matchDomain(request.firstPartyUrl().host())) { return false; } // Check third-party restriction if (hasOption(ThirdPartyOption) && !matchThirdParty(request)) { return false; } // Check object restrictions if (hasOption(ObjectOption) && !matchObject(request)) { return false; } // Check subdocument restriction if (hasOption(SubdocumentOption) && !matchSubdocument(request)) { return false; } // Check xmlhttprequest restriction if (hasOption(XMLHttpRequestOption) && !matchXmlHttpRequest(request)) { return false; } // Check image restriction if (hasOption(ImageOption) && !matchImage(request)) { return false; } // Check script restriction if (hasOption(ScriptOption) && !matchScript(request)) { return false; } // Check stylesheet restriction if (hasOption(StyleSheetOption) && !matchStyleSheet(request)) { return false; } // Check object-subrequest restriction if (hasOption(ObjectSubrequestOption) && !matchObjectSubrequest(request)) { return false; } // Check ping restriction if (hasOption(PingOption) && !matchPing(request)) { return false; } // Check media restriction if (hasOption(MediaOption) && !matchMedia(request)) { return false; } // Check font restriction if (hasOption(FontOption) && !matchFont(request)) { return false; } } return matched; } bool AdBlockRule::matchDomain(const QString &domain) const { if (!m_isEnabled) { return false; } if (!hasOption(DomainRestrictedOption)) { return true; } if (m_blockedDomains.isEmpty()) { - foreach (const QString &d, m_allowedDomains) { + for (const QString &d : qAsConst(m_allowedDomains)) { if (isMatchingDomain(domain, d)) { return true; } } } else if (m_allowedDomains.isEmpty()) { - foreach (const QString &d, m_blockedDomains) { + for (const QString &d : qAsConst(m_blockedDomains)) { if (isMatchingDomain(domain, d)) { return false; } } return true; } else { - foreach (const QString &d, m_blockedDomains) { + for (const QString &d : qAsConst(m_blockedDomains)) { if (isMatchingDomain(domain, d)) { return false; } } - foreach (const QString &d, m_allowedDomains) { + for (const QString &d : qAsConst(m_allowedDomains)) { if (isMatchingDomain(domain, d)) { return true; } } } return false; } bool AdBlockRule::matchThirdParty(const QWebEngineUrlRequestInfo &request) const { // Third-party matching should be performed on second-level domains const QString firstPartyHost = toSecondLevelDomain(request.firstPartyUrl()); const QString host = toSecondLevelDomain(request.requestUrl()); bool match = firstPartyHost != host; return hasException(ThirdPartyOption) ? !match : match; } bool AdBlockRule::matchObject(const QWebEngineUrlRequestInfo &request) const { bool match = request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeObject; return hasException(ObjectOption) ? !match : match; } bool AdBlockRule::matchSubdocument(const QWebEngineUrlRequestInfo &request) const { bool match = request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeSubFrame; return hasException(SubdocumentOption) ? !match : match; } bool AdBlockRule::matchXmlHttpRequest(const QWebEngineUrlRequestInfo &request) const { bool match = request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeXhr; return hasException(XMLHttpRequestOption) ? !match : match; } bool AdBlockRule::matchImage(const QWebEngineUrlRequestInfo &request) const { bool match = request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeImage; return hasException(ImageOption) ? !match : match; } bool AdBlockRule::matchScript(const QWebEngineUrlRequestInfo &request) const { bool match = request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeScript; return hasException(ScriptOption) ? !match : match; } bool AdBlockRule::matchStyleSheet(const QWebEngineUrlRequestInfo &request) const { bool match = request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeStylesheet; return hasException(StyleSheetOption) ? !match : match; } bool AdBlockRule::matchObjectSubrequest(const QWebEngineUrlRequestInfo &request) const { bool match = request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypePluginResource; return hasException(ObjectSubrequestOption) ? !match : match; } bool AdBlockRule::matchPing(const QWebEngineUrlRequestInfo &request) const { bool match = request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypePing; return hasException(PingOption) ? !match : match; } bool AdBlockRule::matchMedia(const QWebEngineUrlRequestInfo &request) const { bool match = request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeMedia; return hasException(MediaOption) ? !match : match; } bool AdBlockRule::matchFont(const QWebEngineUrlRequestInfo &request) const { bool match = request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeFontResource; return hasException(FontOption) ? !match : match; } bool AdBlockRule::matchOther(const QWebEngineUrlRequestInfo &request) const { bool match = request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeFontResource || request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeSubResource || request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeWorker || request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeSharedWorker || request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypePrefetch || request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeFavicon || request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeServiceWorker || request.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeUnknown; return hasException(MediaOption) ? !match : match; } void AdBlockRule::parseFilter() { QString parsedLine = m_filter; // Empty rule or just comment if (m_filter.trimmed().isEmpty() || m_filter.startsWith(QL1C('!'))) { // We want to differentiate rule disabled by user and rule disabled in subscription file // m_isInternalDisabled is also used when rule is disabled due to all options not being supported m_isEnabled = false; m_isInternalDisabled = true; m_type = Invalid; return; } // CSS Element hiding rule if (parsedLine.contains(QL1S("##")) || parsedLine.contains(QL1S("#@#"))) { m_type = CssRule; int pos = parsedLine.indexOf(QL1C('#')); // Domain restricted rule if (!parsedLine.startsWith(QL1S("##"))) { QString domains = parsedLine.left(pos); parseDomains(domains, QL1C(',')); } m_isException = parsedLine.at(pos + 1) == QL1C('@'); m_matchString = parsedLine.mid(m_isException ? pos + 3 : pos + 2); // CSS rule cannot have more options -> stop parsing return; } // Exception always starts with @@ if (parsedLine.startsWith(QL1S("@@"))) { m_isException = true; parsedLine.remove(0, 2); } // Parse all options following $ char int optionsIndex = parsedLine.indexOf(QL1C('$')); if (optionsIndex >= 0) { const QStringList options = parsedLine.mid(optionsIndex + 1).split(QL1C(','), QString::SkipEmptyParts); int handledOptions = 0; - foreach (const QString &option, options) { + for (const QString &option : options) { if (option.startsWith(QL1S("domain="))) { parseDomains(option.mid(7), QL1C('|')); ++handledOptions; } else if (option == QL1S("match-case")) { m_caseSensitivity = Qt::CaseSensitive; ++handledOptions; } else if (option.endsWith(QL1S("third-party"))) { setOption(ThirdPartyOption); setException(ThirdPartyOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option.endsWith(QL1S("object"))) { setOption(ObjectOption); setException(ObjectOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option.endsWith(QL1S("subdocument"))) { setOption(SubdocumentOption); setException(SubdocumentOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option.endsWith(QL1S("xmlhttprequest"))) { setOption(XMLHttpRequestOption); setException(XMLHttpRequestOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option.endsWith(QL1S("image"))) { setOption(ImageOption); setException(ImageOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option.endsWith(QL1S("script"))) { setOption(ScriptOption); setException(ScriptOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option.endsWith(QL1S("stylesheet"))) { setOption(StyleSheetOption); setException(StyleSheetOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option.endsWith(QL1S("object-subrequest"))) { setOption(ObjectSubrequestOption); setException(ObjectSubrequestOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option.endsWith(QL1S("ping"))) { setOption(PingOption); setException(PingOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option.endsWith(QL1S("media"))) { setOption(MediaOption); setException(MediaOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option.endsWith(QL1S("font"))) { setOption(FontOption); setException(FontOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option.endsWith(QL1S("other"))) { setOption(OtherOption); setException(OtherOption, option.startsWith(QL1C('~'))); ++handledOptions; } else if (option == QL1S("document") && m_isException) { setOption(DocumentOption); ++handledOptions; } else if (option == QL1S("elemhide") && m_isException) { setOption(ElementHideOption); ++handledOptions; } else if (option == QL1S("collapse")) { // Hiding placeholders of blocked elements is enabled by default ++handledOptions; } } // If we don't handle all options, it's safer to just disable this rule if (handledOptions != options.count()) { m_isInternalDisabled = true; m_type = Invalid; return; } parsedLine.truncate(optionsIndex); } // Rule is classic regexp if (parsedLine.startsWith(QL1C('/')) && parsedLine.endsWith(QL1C('/'))) { parsedLine.remove(0, 1); parsedLine = parsedLine.left(parsedLine.size() - 1); m_type = RegExpMatchRule; m_regExp = new RegExp; m_regExp->regExp = QRegularExpression(parsedLine, QRegularExpression::InvertedGreedinessOption); if (m_caseSensitivity == Qt::CaseInsensitive) { m_regExp->regExp.setPatternOptions(m_regExp->regExp.patternOptions() | QRegularExpression::CaseInsensitiveOption); } m_regExp->matchers = createStringMatchers(parseRegExpFilter(parsedLine)); return; } // Remove starting and ending wildcards (*) if (parsedLine.startsWith(QL1C('*'))) { parsedLine.remove(0, 1); } if (parsedLine.endsWith(QL1C('*'))) { parsedLine = parsedLine.left(parsedLine.size() - 1); } // We can use fast string matching for domain here if (filterIsOnlyDomain(parsedLine)) { parsedLine.remove(0, 2); parsedLine = parsedLine.left(parsedLine.size() - 1); m_type = DomainMatchRule; m_matchString = parsedLine; return; } // If rule contains only | at end, we can also use string matching if (filterIsOnlyEndsMatch(parsedLine)) { parsedLine = parsedLine.left(parsedLine.size() - 1); m_type = StringEndsMatchRule; m_matchString = parsedLine; return; } // If we still find a wildcard (*) or separator (^) or (|) // we must modify parsedLine to comply with QRegularExpression if (parsedLine.contains(QL1C('*')) || parsedLine.contains(QL1C('^')) || parsedLine.contains(QL1C('|')) ) { m_type = RegExpMatchRule; m_regExp = new RegExp; m_regExp->regExp = QRegularExpression(createRegExpFromFilter(parsedLine), QRegularExpression::InvertedGreedinessOption); if (m_caseSensitivity == Qt::CaseInsensitive) { m_regExp->regExp.setPatternOptions(m_regExp->regExp.patternOptions() | QRegularExpression::CaseInsensitiveOption); } m_regExp->matchers = createStringMatchers(parseRegExpFilter(parsedLine)); return; } // This rule matches all urls if (parsedLine.isEmpty()) { if (m_options == NoOption) { qWarning() << "Disabling unrestricted rule that would block all requests" << m_filter; m_isInternalDisabled = true; m_type = Invalid; return; } m_type = MatchAllUrlsRule; return; } // We haven't found anything that needs use of regexp, yay! m_type = StringContainsMatchRule; m_matchString = parsedLine; } void AdBlockRule::parseDomains(const QString &domains, const QChar &separator) { - QStringList domainsList = domains.split(separator, QString::SkipEmptyParts); + const QStringList domainsList = domains.split(separator, QString::SkipEmptyParts); - foreach (const QString domain, domainsList) { + for (const QString domain : domainsList) { if (domain.isEmpty()) { continue; } if (domain.startsWith(QL1C('~'))) { m_blockedDomains.append(domain.mid(1)); } else { m_allowedDomains.append(domain); } } if (!m_blockedDomains.isEmpty() || !m_allowedDomains.isEmpty()) { setOption(DomainRestrictedOption); } } bool AdBlockRule::filterIsOnlyDomain(const QString &filter) const { if (!filter.endsWith(QL1C('^')) || !filter.startsWith(QL1S("||"))) return false; for (int i = 0; i < filter.size(); ++i) { switch (filter.at(i).toLatin1()) { case '/': case ':': case '?': case '=': case '&': case '*': return false; default: break; } } return true; } bool AdBlockRule::filterIsOnlyEndsMatch(const QString &filter) const { for (int i = 0; i < filter.size(); ++i) { switch (filter.at(i).toLatin1()) { case '^': case '*': return false; case '|': return i == filter.size() - 1; default: break; } } return false; } static bool wordCharacter(const QChar &c) { return c.isLetterOrNumber() || c.isMark() || c == QL1C('_'); } QString AdBlockRule::createRegExpFromFilter(const QString &filter) const { QString parsed; parsed.reserve(filter.size()); bool hadWildcard = false; // Filter multiple wildcards for (int i = 0; i < filter.size(); ++i) { const QChar c = filter.at(i); switch (c.toLatin1()) { case '^': parsed.append(QL1S("(?:[^\\w\\d\\-.%]|$)")); break; case '*': if (!hadWildcard) parsed.append(QL1S(".*")); break; case '|': if (i == 0) { if (filter.size() > 1 && filter.at(1) == QL1C('|')) { parsed.append(QL1S("^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?")); i++; } else { parsed.append(QL1C('^')); } break; } else if (i == filter.size() - 1) { parsed.append(QL1C('$')); break; } // fallthrough default: if (!wordCharacter(c)) parsed.append(QL1C('\\') + c); else parsed.append(c); } hadWildcard = c == QL1C('*'); } return parsed; } QList AdBlockRule::createStringMatchers(const QStringList &filters) const { QList matchers; matchers.reserve(filters.size()); - foreach (const QString &filter, filters) { + for (const QString &filter : filters) { matchers.append(QStringMatcher(filter, m_caseSensitivity)); } return matchers; } bool AdBlockRule::stringMatch(const QString &domain, const QString &encodedUrl) const { switch (m_type) { case StringContainsMatchRule: return encodedUrl.contains(m_matchString, m_caseSensitivity); case DomainMatchRule: return isMatchingDomain(domain, m_matchString); case StringEndsMatchRule: return encodedUrl.endsWith(m_matchString, m_caseSensitivity); case RegExpMatchRule: if (!isMatchingRegExpStrings(encodedUrl)) { return false; } return m_regExp->regExp.match(encodedUrl).hasMatch(); case MatchAllUrlsRule: return true; default: return false; } } bool AdBlockRule::isMatchingDomain(const QString &domain, const QString &filter) const { return QzTools::matchDomain(filter, domain); } bool AdBlockRule::isMatchingRegExpStrings(const QString &url) const { Q_ASSERT(m_regExp); - foreach (const QStringMatcher &matcher, m_regExp->matchers) { + const auto matchers = m_regExp->matchers; + for (const QStringMatcher &matcher : matchers) { if (matcher.indexIn(url) == -1) return false; } return true; } // Split regexp filter into strings that can be used with QString::contains // Don't use parts that contains only 1 char and duplicated parts QStringList AdBlockRule::parseRegExpFilter(const QString &filter) const { QStringList list; int startPos = -1; for (int i = 0; i < filter.size(); ++i) { const QChar c = filter.at(i); // Meta characters in AdBlock rules are | * ^ if (c == QL1C('|') || c == QL1C('*') || c == QL1C('^')) { const QString sub = filter.mid(startPos, i - startPos); if (sub.size() > 1) list.append(sub); startPos = i + 1; } } const QString sub = filter.mid(startPos); if (sub.size() > 1) list.append(sub); list.removeDuplicates(); return list; } bool AdBlockRule::hasOption(const AdBlockRule::RuleOption &opt) const { return (m_options & opt); } bool AdBlockRule::hasException(const AdBlockRule::RuleOption &opt) const { return (m_exceptions & opt); } void AdBlockRule::setOption(const AdBlockRule::RuleOption &opt) { m_options |= opt; } void AdBlockRule::setException(const AdBlockRule::RuleOption &opt, bool on) { if (on) { m_exceptions |= opt; } } diff --git a/src/lib/adblock/adblocksubscription.cpp b/src/lib/adblock/adblocksubscription.cpp index e0f405fa..99d06610 100644 --- a/src/lib/adblock/adblocksubscription.cpp +++ b/src/lib/adblock/adblocksubscription.cpp @@ -1,429 +1,429 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * 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 . * ============================================================ */ /** * Copyright (c) 2009, Benjamin C. Meyer * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Benjamin Meyer nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "adblocksubscription.h" #include "adblockmanager.h" #include "adblocksearchtree.h" #include "mainapplication.h" #include "networkmanager.h" #include "datapaths.h" #include "qztools.h" #include #include #include #include AdBlockSubscription::AdBlockSubscription(const QString &title, QObject* parent) : QObject(parent) , m_reply(nullptr) , m_title(title) , m_updated(false) { } QString AdBlockSubscription::title() const { return m_title; } QString AdBlockSubscription::filePath() const { return m_filePath; } void AdBlockSubscription::setFilePath(const QString &path) { m_filePath = path; } QUrl AdBlockSubscription::url() const { return m_url; } void AdBlockSubscription::setUrl(const QUrl &url) { m_url = url; } void AdBlockSubscription::loadSubscription(const QStringList &disabledRules) { QFile file(m_filePath); if (!file.exists()) { QTimer::singleShot(0, this, &AdBlockSubscription::updateSubscription); return; } if (!file.open(QFile::ReadOnly)) { qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "Unable to open adblock file for reading" << m_filePath; QTimer::singleShot(0, this, &AdBlockSubscription::updateSubscription); return; } QTextStream textStream(&file); textStream.setCodec("UTF-8"); // Header is on 3rd line textStream.readLine(1024); textStream.readLine(1024); QString header = textStream.readLine(1024); if (!header.startsWith(QLatin1String("[Adblock")) || m_title.isEmpty()) { qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "invalid format of adblock file" << m_filePath; QTimer::singleShot(0, this, &AdBlockSubscription::updateSubscription); return; } qDeleteAll(m_rules); m_rules.clear(); while (!textStream.atEnd()) { const QString line = textStream.readLine().trimmed(); if (line.isEmpty()) { continue; } AdBlockRule *rule = new AdBlockRule(line, this); if (disabledRules.contains(rule->filter())) { rule->setEnabled(false); } m_rules.append(rule); } // Initial update if (m_rules.isEmpty() && !m_updated) { QTimer::singleShot(0, this, &AdBlockSubscription::updateSubscription); } } void AdBlockSubscription::saveSubscription() { } void AdBlockSubscription::updateSubscription() { if (m_reply || !m_url.isValid()) { return; } m_reply = mApp->networkManager()->get(QNetworkRequest(m_url)); connect(m_reply, &QNetworkReply::finished, this, &AdBlockSubscription::subscriptionDownloaded); } void AdBlockSubscription::subscriptionDownloaded() { if (m_reply != qobject_cast(sender())) { return; } bool error = false; const QByteArray response = QString::fromUtf8(m_reply->readAll()).toUtf8(); if (m_reply->error() != QNetworkReply::NoError || !response.startsWith(QByteArray("[Adblock")) || !saveDownloadedData(response) ) { error = true; } m_reply->deleteLater(); m_reply = 0; if (error) { emit subscriptionError(tr("Cannot load subscription!")); return; } loadSubscription(AdBlockManager::instance()->disabledRules()); emit subscriptionUpdated(); emit subscriptionChanged(); } bool AdBlockSubscription::saveDownloadedData(const QByteArray &data) { QSaveFile file(m_filePath); if (!file.open(QFile::WriteOnly)) { qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "Unable to open adblock file for writing:" << m_filePath; return false; } // Write subscription header file.write(QSL("Title: %1\nUrl: %2\n").arg(title(), url().toString()).toUtf8()); file.write(data); file.commit(); return true; } const AdBlockRule* AdBlockSubscription::rule(int offset) const { if (!QzTools::containsIndex(m_rules, offset)) { return nullptr; } return m_rules[offset]; } QVector AdBlockSubscription::allRules() const { return m_rules; } const AdBlockRule* AdBlockSubscription::enableRule(int offset) { if (!QzTools::containsIndex(m_rules, offset)) { return nullptr; } AdBlockRule* rule = m_rules[offset]; rule->setEnabled(true); AdBlockManager::instance()->removeDisabledRule(rule->filter()); emit subscriptionChanged(); if (rule->isCssRule()) mApp->reloadUserStyleSheet(); return rule; } const AdBlockRule* AdBlockSubscription::disableRule(int offset) { if (!QzTools::containsIndex(m_rules, offset)) { return nullptr; } AdBlockRule* rule = m_rules[offset]; rule->setEnabled(false); AdBlockManager::instance()->addDisabledRule(rule->filter()); emit subscriptionChanged(); if (rule->isCssRule()) mApp->reloadUserStyleSheet(); return rule; } bool AdBlockSubscription::canEditRules() const { return false; } bool AdBlockSubscription::canBeRemoved() const { return true; } int AdBlockSubscription::addRule(AdBlockRule* rule) { Q_UNUSED(rule) return -1; } bool AdBlockSubscription::removeRule(int offset) { Q_UNUSED(offset) return false; } const AdBlockRule* AdBlockSubscription::replaceRule(AdBlockRule* rule, int offset) { Q_UNUSED(rule) Q_UNUSED(offset) return nullptr; } AdBlockSubscription::~AdBlockSubscription() { qDeleteAll(m_rules); } // AdBlockCustomList AdBlockCustomList::AdBlockCustomList(QObject* parent) : AdBlockSubscription(tr("Custom Rules"), parent) { setFilePath(DataPaths::currentProfilePath() + QLatin1String("/adblock/customlist.txt")); } void AdBlockCustomList::loadSubscription(const QStringList &disabledRules) { // DuckDuckGo ad whitelist rules // They cannot be removed, but can be disabled. // Please consider not disabling them. Thanks! const QString ddg1 = QSL("@@||duckduckgo.com^$document"); const QString ddg2 = QSL("duckduckgo.com#@#.has-ad"); const QString rules = QzTools::readAllFileContents(filePath()); QFile file(filePath()); if (!file.exists()) { saveSubscription(); } if (file.open(QFile::WriteOnly | QFile::Append)) { QTextStream stream(&file); stream.setCodec("UTF-8"); if (!rules.contains(ddg1 + QL1S("\n"))) stream << ddg1 << endl; if (!rules.contains(QL1S("\n") + ddg2)) stream << ddg2 << endl; } file.close(); AdBlockSubscription::loadSubscription(disabledRules); } void AdBlockCustomList::saveSubscription() { QFile file(filePath()); if (!file.open(QFile::ReadWrite | QFile::Truncate)) { qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "Unable to open adblock file for writing:" << filePath(); return; } QTextStream textStream(&file); textStream.setCodec("UTF-8"); textStream << "Title: " << title() << endl; textStream << "Url: " << url().toString() << endl; textStream << "[Adblock Plus 1.1.1]" << endl; - foreach (const AdBlockRule* rule, m_rules) { + for (const AdBlockRule* rule : qAsConst(m_rules)) { textStream << rule->filter() << endl; } file.close(); } bool AdBlockCustomList::canEditRules() const { return true; } bool AdBlockCustomList::canBeRemoved() const { return false; } bool AdBlockCustomList::containsFilter(const QString &filter) const { - foreach (const AdBlockRule* rule, m_rules) { + for (const AdBlockRule* rule : qAsConst(m_rules)) { if (rule->filter() == filter) { return true; } } return false; } bool AdBlockCustomList::removeFilter(const QString &filter) { for (int i = 0; i < m_rules.count(); ++i) { const AdBlockRule* rule = m_rules.at(i); if (rule->filter() == filter) { return removeRule(i); } } return false; } int AdBlockCustomList::addRule(AdBlockRule* rule) { m_rules.append(rule); emit subscriptionChanged(); if (rule->isCssRule()) mApp->reloadUserStyleSheet(); return m_rules.count() - 1; } bool AdBlockCustomList::removeRule(int offset) { if (!QzTools::containsIndex(m_rules, offset)) { return false; } AdBlockRule* rule = m_rules.at(offset); const QString filter = rule->filter(); m_rules.remove(offset); emit subscriptionChanged(); if (rule->isCssRule()) mApp->reloadUserStyleSheet(); AdBlockManager::instance()->removeDisabledRule(filter); delete rule; return true; } const AdBlockRule* AdBlockCustomList::replaceRule(AdBlockRule* rule, int offset) { if (!QzTools::containsIndex(m_rules, offset)) { return 0; } AdBlockRule* oldRule = m_rules.at(offset); m_rules[offset] = rule; emit subscriptionChanged(); if (rule->isCssRule() || oldRule->isCssRule()) mApp->reloadUserStyleSheet(); delete oldRule; return m_rules[offset]; } diff --git a/src/lib/adblock/adblocktreewidget.cpp b/src/lib/adblock/adblocktreewidget.cpp index 91866fc1..af70e3fb 100644 --- a/src/lib/adblock/adblocktreewidget.cpp +++ b/src/lib/adblock/adblocktreewidget.cpp @@ -1,268 +1,268 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2017 David Rosca * * 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 . * ============================================================ */ #include "adblocktreewidget.h" #include "adblocksubscription.h" #include #include #include #include #include AdBlockTreeWidget::AdBlockTreeWidget(AdBlockSubscription* subscription, QWidget* parent) : TreeWidget(parent) , m_subscription(subscription) , m_topItem(0) , m_itemChangingBlock(false) { setContextMenuPolicy(Qt::CustomContextMenu); setDefaultItemShowMode(TreeWidget::ItemsExpanded); setHeaderHidden(true); setAlternatingRowColors(true); setLayoutDirection(Qt::LeftToRight); connect(this, &QWidget::customContextMenuRequested, this, &AdBlockTreeWidget::contextMenuRequested); connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(itemChanged(QTreeWidgetItem*))); connect(m_subscription, &AdBlockSubscription::subscriptionUpdated, this, &AdBlockTreeWidget::subscriptionUpdated); connect(m_subscription, &AdBlockSubscription::subscriptionError, this, &AdBlockTreeWidget::subscriptionError); } AdBlockSubscription* AdBlockTreeWidget::subscription() const { return m_subscription; } void AdBlockTreeWidget::showRule(const AdBlockRule* rule) { if (!m_topItem && rule) { m_ruleToBeSelected = rule->filter(); } else if (!m_ruleToBeSelected.isEmpty()) { QList items = findItems(m_ruleToBeSelected, Qt::MatchRecursive); if (!items.isEmpty()) { QTreeWidgetItem* item = items.at(0); setCurrentItem(item); scrollToItem(item, QAbstractItemView::PositionAtCenter); } m_ruleToBeSelected.clear(); } } void AdBlockTreeWidget::contextMenuRequested(const QPoint &pos) { if (!m_subscription->canEditRules()) { return; } QTreeWidgetItem* item = itemAt(pos); if (!item) { return; } QMenu menu; menu.addAction(tr("Add Rule"), this, &AdBlockTreeWidget::addRule); menu.addSeparator(); QAction* deleteAction = menu.addAction(tr("Remove Rule"), this, &AdBlockTreeWidget::removeRule); if (!item->parent()) { deleteAction->setDisabled(true); } menu.exec(viewport()->mapToGlobal(pos)); } void AdBlockTreeWidget::itemChanged(QTreeWidgetItem* item) { if (!item || m_itemChangingBlock) { return; } m_itemChangingBlock = true; int offset = item->data(0, Qt::UserRole + 10).toInt(); const AdBlockRule* oldRule = m_subscription->rule(offset); if (item->checkState(0) == Qt::Unchecked && oldRule->isEnabled()) { // Disable rule const AdBlockRule* rule = m_subscription->disableRule(offset); adjustItemFeatures(item, rule); } else if (item->checkState(0) == Qt::Checked && !oldRule->isEnabled()) { // Enable rule const AdBlockRule* rule = m_subscription->enableRule(offset); adjustItemFeatures(item, rule); } else if (m_subscription->canEditRules()) { // Custom rule has been changed AdBlockRule* newRule = new AdBlockRule(item->text(0), m_subscription); const AdBlockRule* rule = m_subscription->replaceRule(newRule, offset); adjustItemFeatures(item, rule); } m_itemChangingBlock = false; } void AdBlockTreeWidget::copyFilter() { QTreeWidgetItem* item = currentItem(); if (!item) { return; } QApplication::clipboard()->setText(item->text(0)); } void AdBlockTreeWidget::addRule() { if (!m_subscription->canEditRules()) { return; } QString newRule = QInputDialog::getText(this, tr("Add Custom Rule"), tr("Please write your rule here:")); if (newRule.isEmpty()) { return; } AdBlockRule* rule = new AdBlockRule(newRule, m_subscription); int offset = m_subscription->addRule(rule); QTreeWidgetItem* item = new QTreeWidgetItem(); item->setText(0, newRule); item->setData(0, Qt::UserRole + 10, offset); item->setFlags(item->flags() | Qt::ItemIsEditable); m_itemChangingBlock = true; m_topItem->addChild(item); m_itemChangingBlock = false; adjustItemFeatures(item, rule); } void AdBlockTreeWidget::removeRule() { QTreeWidgetItem* item = currentItem(); if (!item || !m_subscription->canEditRules() || item == m_topItem) { return; } int offset = item->data(0, Qt::UserRole + 10).toInt(); m_subscription->removeRule(offset); deleteItem(item); } void AdBlockTreeWidget::subscriptionUpdated() { refresh(); m_itemChangingBlock = true; m_topItem->setText(0, tr("%1 (recently updated)").arg(m_subscription->title())); m_itemChangingBlock = false; } void AdBlockTreeWidget::subscriptionError(const QString &message) { refresh(); m_itemChangingBlock = true; m_topItem->setText(0, tr("%1 (Error: %2)").arg(m_subscription->title(), message)); m_itemChangingBlock = false; } void AdBlockTreeWidget::adjustItemFeatures(QTreeWidgetItem* item, const AdBlockRule* rule) { if (!rule->isEnabled()) { item->setForeground(0, QColor(Qt::gray)); if (!rule->isComment()) { QFont f = font(); f.setItalic(true); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(0, Qt::Unchecked); item->setFont(0, f); } return; } item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(0, Qt::Checked); item->setForeground(0, palette().foreground()); item->setFont(0, font()); if (rule->isException()) { item->setForeground(0, QColor(Qt::darkGreen)); item->setFont(0, QFont()); } else if (rule->isCssRule()) { item->setForeground(0, QColor(Qt::darkBlue)); item->setFont(0, QFont()); } } void AdBlockTreeWidget::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_C && event->modifiers() & Qt::ControlModifier) { copyFilter(); } if (event->key() == Qt::Key_Delete) { removeRule(); } TreeWidget::keyPressEvent(event); } void AdBlockTreeWidget::refresh() { m_itemChangingBlock = true; clear(); QFont boldFont; boldFont.setBold(true); m_topItem = new QTreeWidgetItem(this); m_topItem->setText(0, m_subscription->title()); m_topItem->setFont(0, boldFont); m_topItem->setExpanded(true); addTopLevelItem(m_topItem); const QVector &allRules = m_subscription->allRules(); int index = 0; - foreach (const AdBlockRule* rule, allRules) { + for (const AdBlockRule* rule : allRules) { QTreeWidgetItem* item = new QTreeWidgetItem(m_topItem); item->setText(0, rule->filter()); item->setData(0, Qt::UserRole + 10, index); if (m_subscription->canEditRules()) { item->setFlags(item->flags() | Qt::ItemIsEditable); } adjustItemFeatures(item, rule); ++index; } showRule(0); m_itemChangingBlock = false; }