File indexing completed on 2024-05-12 04:57:49

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "adblockmanager.h"
0019 #include "adblockdialog.h"
0020 #include "adblockmatcher.h"
0021 #include "adblocksubscription.h"
0022 #include "adblockurlinterceptor.h"
0023 #include "datapaths.h"
0024 #include "mainapplication.h"
0025 #include "webpage.h"
0026 #include "qztools.h"
0027 #include "browserwindow.h"
0028 #include "settings.h"
0029 #include "networkmanager.h"
0030 
0031 #include <QAction>
0032 #include <QDateTime>
0033 #include <QTextStream>
0034 #include <QDir>
0035 #include <QTimer>
0036 #include <QMessageBox>
0037 #include <QUrlQuery>
0038 #include <QMutexLocker>
0039 #include <QSaveFile>
0040 
0041 //#define ADBLOCK_DEBUG
0042 
0043 #ifdef ADBLOCK_DEBUG
0044 #include <QElapsedTimer>
0045 #endif
0046 
0047 Q_GLOBAL_STATIC(AdBlockManager, qz_adblock_manager)
0048 
0049 AdBlockManager::AdBlockManager(QObject* parent)
0050     : QObject(parent)
0051     , m_loaded(false)
0052     , m_enabled(true)
0053     , m_matcher(new AdBlockMatcher(this))
0054     , m_interceptor(new AdBlockUrlInterceptor(this))
0055 {
0056     qRegisterMetaType<AdBlockedRequest>();
0057 
0058     load();
0059 }
0060 
0061 AdBlockManager::~AdBlockManager()
0062 {
0063     qDeleteAll(m_subscriptions);
0064 }
0065 
0066 AdBlockManager* AdBlockManager::instance()
0067 {
0068     return qz_adblock_manager();
0069 }
0070 
0071 void AdBlockManager::setEnabled(bool enabled)
0072 {
0073     if (m_enabled == enabled) {
0074         return;
0075     }
0076 
0077     m_enabled = enabled;
0078     Q_EMIT enabledChanged(enabled);
0079 
0080     Settings settings;
0081     settings.beginGroup(QSL("AdBlock"));
0082     settings.setValue(QSL("enabled"), m_enabled);
0083     settings.endGroup();
0084 
0085     load();
0086     mApp->reloadUserStyleSheet();
0087 
0088     QMutexLocker locker(&m_mutex);
0089 
0090     if (m_enabled) {
0091         m_matcher->update();
0092     } else {
0093         m_matcher->clear();
0094     }
0095 }
0096 
0097 QList<AdBlockSubscription*> AdBlockManager::subscriptions() const
0098 {
0099     return m_subscriptions;
0100 }
0101 
0102 bool AdBlockManager::block(QWebEngineUrlRequestInfo &request, QString &ruleFilter, QString &ruleSubscription)
0103 {
0104     QMutexLocker locker(&m_mutex);
0105 
0106     if (!isEnabled()) {
0107         return false;
0108     }
0109 
0110 #ifdef ADBLOCK_DEBUG
0111     QElapsedTimer timer;
0112     timer.start();
0113 #endif
0114     const QString urlString = QString::fromUtf8(request.requestUrl().toEncoded().toLower());
0115     const QString urlDomain = request.requestUrl().host().toLower();
0116     const QString urlScheme = request.requestUrl().scheme().toLower();
0117 
0118     if (!canRunOnScheme(urlScheme) || !canBeBlocked(request.firstPartyUrl())) {
0119         return false;
0120     }
0121 
0122     const AdBlockRule* blockedRule = m_matcher->match(request, urlDomain, urlString);
0123 
0124     if (blockedRule) {
0125         ruleFilter = blockedRule->filter();
0126         ruleSubscription = blockedRule->subscription()->title();
0127 #ifdef ADBLOCK_DEBUG
0128         qDebug() << "BLOCKED: " << timer.elapsed() << blockedRule->filter() << request.requestUrl();
0129 #endif
0130     }
0131 
0132 #ifdef ADBLOCK_DEBUG
0133     qDebug() << timer.elapsed() << request.requestUrl();
0134 #endif
0135 
0136     return blockedRule;
0137 }
0138 
0139 QVector<AdBlockedRequest> AdBlockManager::blockedRequestsForUrl(const QUrl &url) const
0140 {
0141     return m_blockedRequests.value(url);
0142 }
0143 
0144 void AdBlockManager::clearBlockedRequestsForUrl(const QUrl &url)
0145 {
0146     if (m_blockedRequests.remove(url)) {
0147         Q_EMIT blockedRequestsChanged(url);
0148     }
0149 }
0150 
0151 QStringList AdBlockManager::disabledRules() const
0152 {
0153     return m_disabledRules;
0154 }
0155 
0156 void AdBlockManager::addDisabledRule(const QString &filter)
0157 {
0158     m_disabledRules.append(filter);
0159 }
0160 
0161 void AdBlockManager::removeDisabledRule(const QString &filter)
0162 {
0163     m_disabledRules.removeOne(filter);
0164 }
0165 
0166 bool AdBlockManager::addSubscriptionFromUrl(const QUrl &url)
0167 {
0168     const QList<QPair<QString, QString> > queryItems = QUrlQuery(url).queryItems(QUrl::FullyDecoded);
0169 
0170     QString subscriptionTitle;
0171     QString subscriptionUrl;
0172 
0173     for (int i = 0; i < queryItems.count(); ++i) {
0174         QPair<QString, QString> pair = queryItems.at(i);
0175         if (pair.first.endsWith(QL1S("location")))
0176             subscriptionUrl = pair.second;
0177         else if (pair.first.endsWith(QL1S("title")))
0178             subscriptionTitle = pair.second;
0179     }
0180 
0181     if (subscriptionTitle.isEmpty() || subscriptionUrl.isEmpty())
0182         return false;
0183 
0184     const QString message = AdBlockManager::tr("Do you want to add <b>%1</b> subscription?").arg(subscriptionTitle);
0185 
0186     QMessageBox::StandardButton result = QMessageBox::question(nullptr, AdBlockManager::tr("AdBlock Subscription"), message, QMessageBox::Yes | QMessageBox::No);
0187     if (result == QMessageBox::Yes) {
0188         AdBlockManager::instance()->addSubscription(subscriptionTitle, subscriptionUrl);
0189         AdBlockManager::instance()->showDialog();
0190     }
0191 
0192     return true;
0193 }
0194 
0195 AdBlockSubscription* AdBlockManager::addSubscription(const QString &title, const QString &url)
0196 {
0197     if (title.isEmpty() || url.isEmpty()) {
0198         return nullptr;
0199     }
0200 
0201     QString fileName = QzTools::filterCharsFromFilename(title.toLower()) + QSL(".txt");
0202     QString filePath = QzTools::ensureUniqueFilename(DataPaths::currentProfilePath() + QSL("/adblock/") + fileName);
0203 
0204     QByteArray data = QSL("Title: %1\nUrl: %2\n[Adblock Plus 1.1.1]").arg(title, url).toLatin1();
0205 
0206     QSaveFile file(filePath);
0207     if (!file.open(QFile::WriteOnly)) {
0208         qWarning() << "AdBlockManager: Cannot write to file" << filePath;
0209         return nullptr;
0210     }
0211     file.write(data);
0212     file.commit();
0213 
0214     auto* subscription = new AdBlockSubscription(title, this);
0215     subscription->setUrl(QUrl(url));
0216     subscription->setFilePath(filePath);
0217     subscription->loadSubscription(m_disabledRules);
0218 
0219     m_subscriptions.insert(m_subscriptions.count() - 1, subscription);
0220     connect(subscription, &AdBlockSubscription::subscriptionUpdated, mApp, &MainApplication::reloadUserStyleSheet);
0221     connect(subscription, &AdBlockSubscription::subscriptionChanged, this, &AdBlockManager::updateMatcher);
0222 
0223     return subscription;
0224 }
0225 
0226 bool AdBlockManager::removeSubscription(AdBlockSubscription* subscription)
0227 {
0228     QMutexLocker locker(&m_mutex);
0229 
0230     if (!m_subscriptions.contains(subscription) || !subscription->canBeRemoved()) {
0231         return false;
0232     }
0233 
0234     QFile(subscription->filePath()).remove();
0235     m_subscriptions.removeOne(subscription);
0236 
0237     m_matcher->update();
0238     delete subscription;
0239 
0240     return true;
0241 }
0242 
0243 AdBlockCustomList* AdBlockManager::customList() const
0244 {
0245     for (AdBlockSubscription* subscription : std::as_const(m_subscriptions)) {
0246         auto* list = qobject_cast<AdBlockCustomList*>(subscription);
0247 
0248         if (list) {
0249             return list;
0250         }
0251     }
0252 
0253     return nullptr;
0254 }
0255 
0256 void AdBlockManager::load()
0257 {
0258     QMutexLocker locker(&m_mutex);
0259 
0260     if (m_loaded) {
0261         return;
0262     }
0263 
0264 #ifdef ADBLOCK_DEBUG
0265     QElapsedTimer timer;
0266     timer.start();
0267 #endif
0268 
0269     Settings settings;
0270     settings.beginGroup(QSL("AdBlock"));
0271     m_enabled = settings.value(QSL("enabled"), m_enabled).toBool();
0272     m_disabledRules = settings.value(QSL("disabledRules"), QStringList()).toStringList();
0273     QDateTime lastUpdate = settings.value(QSL("lastUpdate"), QDateTime()).toDateTime();
0274     settings.endGroup();
0275 
0276     if (!m_enabled) {
0277         return;
0278     }
0279 
0280     QDir adblockDir(DataPaths::currentProfilePath() + QSL("/adblock"));
0281     // Create if necessary
0282     if (!adblockDir.exists()) {
0283         QDir(DataPaths::currentProfilePath()).mkdir(QSL("adblock"));
0284     }
0285 
0286     const auto fileNames = adblockDir.entryList(QStringList(QSL("*.txt")), QDir::Files);
0287     for (const QString &fileName : fileNames) {
0288         if (fileName == QLatin1String("customlist.txt")) {
0289             continue;
0290         }
0291 
0292         const QString absolutePath = adblockDir.absoluteFilePath(fileName);
0293         QFile file(absolutePath);
0294         if (!file.open(QFile::ReadOnly)) {
0295             continue;
0296         }
0297 
0298         QTextStream textStream(&file);
0299         textStream.setEncoding(QStringConverter::Utf8);
0300         QString title = textStream.readLine(1024).remove(QLatin1String("Title: "));
0301         QUrl url = QUrl(textStream.readLine(1024).remove(QLatin1String("Url: ")));
0302 
0303         if (title.isEmpty() || !url.isValid()) {
0304             qWarning() << "AdBlockManager: Invalid subscription file" << absolutePath;
0305             continue;
0306         }
0307 
0308         auto* subscription = new AdBlockSubscription(title, this);
0309         subscription->setUrl(url);
0310         subscription->setFilePath(absolutePath);
0311 
0312         m_subscriptions.append(subscription);
0313     }
0314 
0315     // Add EasyList + NoCoinList if subscriptions are empty
0316     if (m_subscriptions.isEmpty()) {
0317         auto *easyList = new AdBlockSubscription(tr("EasyList"), this);
0318         easyList->setUrl(QUrl(ADBLOCK_EASYLIST_URL));
0319         easyList->setFilePath(DataPaths::currentProfilePath() + QLatin1String("/adblock/easylist.txt"));
0320         m_subscriptions.append(easyList);
0321 
0322         auto *noCoinList = new AdBlockSubscription(tr("NoCoin List"), this);
0323         noCoinList->setUrl(QUrl(ADBLOCK_NOCOINLIST_URL));
0324         noCoinList->setFilePath(DataPaths::currentProfilePath() + QLatin1String("/adblock/nocoinlist.txt"));
0325         m_subscriptions.append(noCoinList);
0326     }
0327 
0328     // Append CustomList
0329     auto* customList = new AdBlockCustomList(this);
0330     m_subscriptions.append(customList);
0331 
0332     // Load all subscriptions
0333     for (AdBlockSubscription* subscription : std::as_const(m_subscriptions)) {
0334         subscription->loadSubscription(m_disabledRules);
0335 
0336         connect(subscription, &AdBlockSubscription::subscriptionUpdated, mApp, &MainApplication::reloadUserStyleSheet);
0337         connect(subscription, &AdBlockSubscription::subscriptionChanged, this, &AdBlockManager::updateMatcher);
0338     }
0339 
0340     if (lastUpdate.addDays(5) < QDateTime::currentDateTime()) {
0341         QTimer::singleShot(1000 * 60, this, &AdBlockManager::updateAllSubscriptions);
0342     }
0343 
0344 #ifdef ADBLOCK_DEBUG
0345     qDebug() << "AdBlock loaded in" << timer.elapsed();
0346 #endif
0347 
0348     m_matcher->update();
0349     m_loaded = true;
0350 
0351     connect(m_interceptor, &AdBlockUrlInterceptor::requestBlocked, this, [this](const AdBlockedRequest &request) {
0352         m_blockedRequests[request.firstPartyUrl].append(request);
0353         Q_EMIT blockedRequestsChanged(request.firstPartyUrl);
0354     });
0355 
0356     mApp->networkManager()->installUrlInterceptor(m_interceptor);
0357 }
0358 
0359 void AdBlockManager::updateMatcher()
0360 {
0361     QMutexLocker locker(&m_mutex);
0362 
0363     mApp->networkManager()->removeUrlInterceptor(m_interceptor);
0364     m_matcher->update();
0365     mApp->networkManager()->installUrlInterceptor(m_interceptor);
0366 }
0367 
0368 void AdBlockManager::updateAllSubscriptions()
0369 {
0370     for (AdBlockSubscription* subscription : std::as_const(m_subscriptions)) {
0371         subscription->updateSubscription();
0372     }
0373 
0374     Settings settings;
0375     settings.beginGroup(QSL("AdBlock"));
0376     settings.setValue(QSL("lastUpdate"), QDateTime::currentDateTime());
0377     settings.endGroup();
0378 }
0379 
0380 void AdBlockManager::save()
0381 {
0382     if (!m_loaded) {
0383         return;
0384     }
0385 
0386     for (AdBlockSubscription* subscription : std::as_const(m_subscriptions)) {
0387         subscription->saveSubscription();
0388     }
0389 
0390     Settings settings;
0391     settings.beginGroup(QSL("AdBlock"));
0392     settings.setValue(QSL("enabled"), m_enabled);
0393     settings.setValue(QSL("disabledRules"), m_disabledRules);
0394     settings.endGroup();
0395 }
0396 
0397 bool AdBlockManager::isEnabled() const
0398 {
0399     return m_enabled;
0400 }
0401 
0402 bool AdBlockManager::canRunOnScheme(const QString &scheme) const
0403 {
0404     return !(scheme == QL1S("file") || scheme == QL1S("qrc") || scheme == QL1S("view-source")
0405              || scheme == QL1S("falkon") || scheme == QL1S("data") || scheme == QL1S("abp"));
0406 }
0407 
0408 bool AdBlockManager::canBeBlocked(const QUrl &url) const
0409 {
0410     return !m_matcher->adBlockDisabledForUrl(url);
0411 }
0412 
0413 QString AdBlockManager::elementHidingRules(const QUrl &url) const
0414 {
0415     if (!isEnabled() || !canRunOnScheme(url.scheme()) || m_matcher->genericElemHideDisabledForUrl(url))
0416         return {};
0417 
0418     return m_matcher->elementHidingRules();
0419 }
0420 
0421 QString AdBlockManager::elementHidingRulesForDomain(const QUrl &url) const
0422 {
0423     if (!isEnabled() || !canRunOnScheme(url.scheme()) || m_matcher->elemHideDisabledForUrl(url))
0424         return {};
0425 
0426     return m_matcher->elementHidingRulesForDomain(url.host());
0427 }
0428 
0429 AdBlockSubscription* AdBlockManager::subscriptionByName(const QString &name) const
0430 {
0431     for (AdBlockSubscription* subscription : std::as_const(m_subscriptions)) {
0432         if (subscription->title() == name) {
0433             return subscription;
0434         }
0435     }
0436 
0437     return nullptr;
0438 }
0439 
0440 AdBlockDialog *AdBlockManager::showDialog(QWidget *parent)
0441 {
0442     if (!m_adBlockDialog) {
0443         m_adBlockDialog = new AdBlockDialog(parent ? parent : mApp->getWindow());
0444     }
0445 
0446     m_adBlockDialog.data()->show();
0447     m_adBlockDialog.data()->raise();
0448     m_adBlockDialog.data()->activateWindow();
0449 
0450     return m_adBlockDialog.data();
0451 }
0452 
0453 void AdBlockManager::showRule()
0454 {
0455     if (auto* action = qobject_cast<QAction*>(sender())) {
0456         const AdBlockRule* rule = static_cast<const AdBlockRule*>(action->data().value<void*>());
0457 
0458         if (rule) {
0459             showDialog()->showRule(rule);
0460         }
0461     }
0462 }