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 }