Falkon Develop
Cross-platform Qt-based web browser
adblockmanager.cpp
Go to the documentation of this file.
1/* ============================================================
2* Falkon - Qt web browser
3* Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
4*
5* This program is free software: you can redistribute it and/or modify
6* it under the terms of the GNU General Public License as published by
7* the Free Software Foundation, either version 3 of the License, or
8* (at your option) any later version.
9*
10* This program is distributed in the hope that it will be useful,
11* but WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13* GNU General Public License for more details.
14*
15* You should have received a copy of the GNU General Public License
16* along with this program. If not, see <http://www.gnu.org/licenses/>.
17* ============================================================ */
18#include "adblockmanager.h"
19#include "adblockdialog.h"
20#include "adblockmatcher.h"
21#include "adblocksubscription.h"
23#include "datapaths.h"
24#include "mainapplication.h"
25#include "webpage.h"
26#include "qztools.h"
27#include "browserwindow.h"
28#include "settings.h"
29#include "networkmanager.h"
30
31#include <QAction>
32#include <QDateTime>
33#include <QTextStream>
34#include <QDir>
35#include <QTimer>
36#include <QMessageBox>
37#include <QUrlQuery>
38#include <QMutexLocker>
39#include <QSaveFile>
40
41//#define ADBLOCK_DEBUG
42
43#ifdef ADBLOCK_DEBUG
44#include <QElapsedTimer>
45#endif
46
47Q_GLOBAL_STATIC(AdBlockManager, qz_adblock_manager)
48
50 : QObject(parent)
51 , m_loaded(false)
52 , m_enabled(true)
53 , m_matcher(new AdBlockMatcher(this))
54 , m_interceptor(new AdBlockUrlInterceptor(this))
55{
56 qRegisterMetaType<AdBlockedRequest>();
57
58 load();
59}
60
62{
63 qDeleteAll(m_subscriptions);
64}
65
67{
68 return qz_adblock_manager();
69}
70
72{
73 if (m_enabled == enabled) {
74 return;
75 }
76
77 m_enabled = enabled;
78 Q_EMIT enabledChanged(enabled);
79
80 Settings settings;
81 settings.beginGroup(QSL("AdBlock"));
82 settings.setValue(QSL("enabled"), m_enabled);
83 settings.endGroup();
84
85 load();
86 mApp->reloadUserStyleSheet();
87
88 QMutexLocker locker(&m_mutex);
89
90 if (m_enabled) {
91 m_matcher->update();
92 } else {
93 m_matcher->clear();
94 }
95}
96
97QList<AdBlockSubscription*> AdBlockManager::subscriptions() const
98{
99 return m_subscriptions;
100}
101
102bool AdBlockManager::block(QWebEngineUrlRequestInfo &request, QString &ruleFilter, QString &ruleSubscription)
103{
104 QMutexLocker locker(&m_mutex);
105
106 if (!isEnabled()) {
107 return false;
108 }
109
110#ifdef ADBLOCK_DEBUG
111 QElapsedTimer timer;
112 timer.start();
113#endif
114 const QString urlString = QString::fromUtf8(request.requestUrl().toEncoded().toLower());
115 const QString urlDomain = request.requestUrl().host().toLower();
116 const QString urlScheme = request.requestUrl().scheme().toLower();
117
118 if (!canRunOnScheme(urlScheme) || !canBeBlocked(request.firstPartyUrl())) {
119 return false;
120 }
121
122 const AdBlockRule* blockedRule = m_matcher->match(request, urlDomain, urlString);
123
124 if (blockedRule) {
125 ruleFilter = blockedRule->filter();
126 ruleSubscription = blockedRule->subscription()->title();
127#ifdef ADBLOCK_DEBUG
128 qDebug() << "BLOCKED: " << timer.elapsed() << blockedRule->filter() << request.requestUrl();
129#endif
130 }
131
132#ifdef ADBLOCK_DEBUG
133 qDebug() << timer.elapsed() << request.requestUrl();
134#endif
135
136 return blockedRule;
137}
138
139QVector<AdBlockedRequest> AdBlockManager::blockedRequestsForUrl(const QUrl &url) const
140{
141 return m_blockedRequests.value(url);
142}
143
145{
146 if (m_blockedRequests.remove(url)) {
147 Q_EMIT blockedRequestsChanged(url);
148 }
149}
150
152{
153 return m_disabledRules;
154}
155
156void AdBlockManager::addDisabledRule(const QString &filter)
157{
158 m_disabledRules.append(filter);
159}
160
161void AdBlockManager::removeDisabledRule(const QString &filter)
162{
163 m_disabledRules.removeOne(filter);
164}
165
167{
168 const QList<QPair<QString, QString> > queryItems = QUrlQuery(url).queryItems(QUrl::FullyDecoded);
169
170 QString subscriptionTitle;
171 QString subscriptionUrl;
172
173 for (int i = 0; i < queryItems.count(); ++i) {
174 QPair<QString, QString> pair = queryItems.at(i);
175 if (pair.first.endsWith(QL1S("location")))
176 subscriptionUrl = pair.second;
177 else if (pair.first.endsWith(QL1S("title")))
178 subscriptionTitle = pair.second;
179 }
180
181 if (subscriptionTitle.isEmpty() || subscriptionUrl.isEmpty())
182 return false;
183
184 const QString message = AdBlockManager::tr("Do you want to add <b>%1</b> subscription?").arg(subscriptionTitle);
185
186 QMessageBox::StandardButton result = QMessageBox::question(nullptr, AdBlockManager::tr("AdBlock Subscription"), message, QMessageBox::Yes | QMessageBox::No);
187 if (result == QMessageBox::Yes) {
188 AdBlockManager::instance()->addSubscription(subscriptionTitle, subscriptionUrl);
190 }
191
192 return true;
193}
194
195AdBlockSubscription* AdBlockManager::addSubscription(const QString &title, const QString &url)
196{
197 if (title.isEmpty() || url.isEmpty()) {
198 return nullptr;
199 }
200
201 QString fileName = QzTools::filterCharsFromFilename(title.toLower()) + QSL(".txt");
202 QString filePath = QzTools::ensureUniqueFilename(DataPaths::currentProfilePath() + QSL("/adblock/") + fileName);
203
204 QByteArray data = QSL("Title: %1\nUrl: %2\n[Adblock Plus 1.1.1]").arg(title, url).toLatin1();
205
206 QSaveFile file(filePath);
207 if (!file.open(QFile::WriteOnly)) {
208 qWarning() << "AdBlockManager: Cannot write to file" << filePath;
209 return nullptr;
210 }
211 file.write(data);
212 file.commit();
213
214 auto* subscription = new AdBlockSubscription(title, this);
215 subscription->setUrl(QUrl(url));
216 subscription->setFilePath(filePath);
217 subscription->loadSubscription(m_disabledRules);
218
219 m_subscriptions.insert(m_subscriptions.count() - 1, subscription);
222
223 return subscription;
224}
225
227{
228 QMutexLocker locker(&m_mutex);
229
230 if (!m_subscriptions.contains(subscription) || !subscription->canBeRemoved()) {
231 return false;
232 }
233
234 QFile(subscription->filePath()).remove();
235 m_subscriptions.removeOne(subscription);
236
237 m_matcher->update();
238 delete subscription;
239
240 return true;
241}
242
244{
245 for (AdBlockSubscription* subscription : std::as_const(m_subscriptions)) {
246 auto* list = qobject_cast<AdBlockCustomList*>(subscription);
247
248 if (list) {
249 return list;
250 }
251 }
252
253 return nullptr;
254}
255
257{
258 QMutexLocker locker(&m_mutex);
259
260 if (m_loaded) {
261 return;
262 }
263
264#ifdef ADBLOCK_DEBUG
265 QElapsedTimer timer;
266 timer.start();
267#endif
268
269 Settings settings;
270 settings.beginGroup(QSL("AdBlock"));
271 m_enabled = settings.value(QSL("enabled"), m_enabled).toBool();
272 m_disabledRules = settings.value(QSL("disabledRules"), QStringList()).toStringList();
273 QDateTime lastUpdate = settings.value(QSL("lastUpdate"), QDateTime()).toDateTime();
274 settings.endGroup();
275
276 if (!m_enabled) {
277 return;
278 }
279
280 QDir adblockDir(DataPaths::currentProfilePath() + QSL("/adblock"));
281 // Create if necessary
282 if (!adblockDir.exists()) {
283 QDir(DataPaths::currentProfilePath()).mkdir(QSL("adblock"));
284 }
285
286 const auto fileNames = adblockDir.entryList(QStringList(QSL("*.txt")), QDir::Files);
287 for (const QString &fileName : fileNames) {
288 if (fileName == QLatin1String("customlist.txt")) {
289 continue;
290 }
291
292 const QString absolutePath = adblockDir.absoluteFilePath(fileName);
293 QFile file(absolutePath);
294 if (!file.open(QFile::ReadOnly)) {
295 continue;
296 }
297
298 QTextStream textStream(&file);
299 textStream.setEncoding(QStringConverter::Utf8);
300 QString title = textStream.readLine(1024).remove(QLatin1String("Title: "));
301 QUrl url = QUrl(textStream.readLine(1024).remove(QLatin1String("Url: ")));
302
303 if (title.isEmpty() || !url.isValid()) {
304 qWarning() << "AdBlockManager: Invalid subscription file" << absolutePath;
305 continue;
306 }
307
308 auto* subscription = new AdBlockSubscription(title, this);
309 subscription->setUrl(url);
310 subscription->setFilePath(absolutePath);
311
312 m_subscriptions.append(subscription);
313 }
314
315 // Add EasyList + NoCoinList if subscriptions are empty
316 if (m_subscriptions.isEmpty()) {
317 auto *easyList = new AdBlockSubscription(tr("EasyList"), this);
318 easyList->setUrl(QUrl(ADBLOCK_EASYLIST_URL));
319 easyList->setFilePath(DataPaths::currentProfilePath() + QLatin1String("/adblock/easylist.txt"));
320 m_subscriptions.append(easyList);
321
322 auto *noCoinList = new AdBlockSubscription(tr("NoCoin List"), this);
323 noCoinList->setUrl(QUrl(ADBLOCK_NOCOINLIST_URL));
324 noCoinList->setFilePath(DataPaths::currentProfilePath() + QLatin1String("/adblock/nocoinlist.txt"));
325 m_subscriptions.append(noCoinList);
326 }
327
328 // Append CustomList
329 auto* customList = new AdBlockCustomList(this);
330 m_subscriptions.append(customList);
331
332 // Load all subscriptions
333 for (AdBlockSubscription* subscription : std::as_const(m_subscriptions)) {
334 subscription->loadSubscription(m_disabledRules);
335
338 }
339
340 if (lastUpdate.addDays(5) < QDateTime::currentDateTime()) {
341 QTimer::singleShot(1000 * 60, this, &AdBlockManager::updateAllSubscriptions);
342 }
343
344#ifdef ADBLOCK_DEBUG
345 qDebug() << "AdBlock loaded in" << timer.elapsed();
346#endif
347
348 m_matcher->update();
349 m_loaded = true;
350
351 connect(m_interceptor, &AdBlockUrlInterceptor::requestBlocked, this, [this](const AdBlockedRequest &request) {
352 m_blockedRequests[request.firstPartyUrl].append(request);
354 });
355
356 mApp->networkManager()->installUrlInterceptor(m_interceptor);
357}
358
360{
361 QMutexLocker locker(&m_mutex);
362
363 mApp->networkManager()->removeUrlInterceptor(m_interceptor);
364 m_matcher->update();
365 mApp->networkManager()->installUrlInterceptor(m_interceptor);
366}
367
369{
370 for (AdBlockSubscription* subscription : std::as_const(m_subscriptions)) {
371 subscription->updateSubscription();
372 }
373
374 Settings settings;
375 settings.beginGroup(QSL("AdBlock"));
376 settings.setValue(QSL("lastUpdate"), QDateTime::currentDateTime());
377 settings.endGroup();
378}
379
381{
382 if (!m_loaded) {
383 return;
384 }
385
386 for (AdBlockSubscription* subscription : std::as_const(m_subscriptions)) {
387 subscription->saveSubscription();
388 }
389
390 Settings settings;
391 settings.beginGroup(QSL("AdBlock"));
392 settings.setValue(QSL("enabled"), m_enabled);
393 settings.setValue(QSL("disabledRules"), m_disabledRules);
394 settings.endGroup();
395}
396
398{
399 return m_enabled;
400}
401
402bool AdBlockManager::canRunOnScheme(const QString &scheme) const
403{
404 return !(scheme == QL1S("file") || scheme == QL1S("qrc") || scheme == QL1S("view-source")
405 || scheme == QL1S("falkon") || scheme == QL1S("data") || scheme == QL1S("abp"));
406}
407
408bool AdBlockManager::canBeBlocked(const QUrl &url) const
409{
410 return !m_matcher->adBlockDisabledForUrl(url);
411}
412
413QString AdBlockManager::elementHidingRules(const QUrl &url) const
414{
415 if (!isEnabled() || !canRunOnScheme(url.scheme()) || m_matcher->genericElemHideDisabledForUrl(url))
416 return {};
417
418 return m_matcher->elementHidingRules();
419}
420
422{
423 if (!isEnabled() || !canRunOnScheme(url.scheme()) || m_matcher->elemHideDisabledForUrl(url))
424 return {};
425
426 return m_matcher->elementHidingRulesForDomain(url.host());
427}
428
430{
431 for (AdBlockSubscription* subscription : std::as_const(m_subscriptions)) {
432 if (subscription->title() == name) {
433 return subscription;
434 }
435 }
436
437 return nullptr;
438}
439
441{
442 if (!m_adBlockDialog) {
443 m_adBlockDialog = new AdBlockDialog(parent ? parent : mApp->getWindow());
444 }
445
446 m_adBlockDialog.data()->show();
447 m_adBlockDialog.data()->raise();
448 m_adBlockDialog.data()->activateWindow();
449
450 return m_adBlockDialog.data();
451}
452
454{
455 if (auto* action = qobject_cast<QAction*>(sender())) {
456 const AdBlockRule* rule = static_cast<const AdBlockRule*>(action->data().value<void*>());
457
458 if (rule) {
459 showDialog()->showRule(rule);
460 }
461 }
462}
#define ADBLOCK_NOCOINLIST_URL
#define ADBLOCK_EASYLIST_URL
void showRule(const AdBlockRule *rule) const
void removeDisabledRule(const QString &filter)
void setEnabled(bool enabled)
bool canRunOnScheme(const QString &scheme) const
void enabledChanged(bool enabled)
AdBlockSubscription * addSubscription(const QString &title, const QString &url)
bool block(QWebEngineUrlRequestInfo &request, QString &ruleFilter, QString &ruleSubscription)
void blockedRequestsChanged(const QUrl &url)
QStringList disabledRules() const
bool addSubscriptionFromUrl(const QUrl &url)
AdBlockCustomList * customList() const
AdBlockSubscription * subscriptionByName(const QString &name) const
QVector< AdBlockedRequest > blockedRequestsForUrl(const QUrl &url) const
static AdBlockManager * instance()
QString elementHidingRules(const QUrl &url) const
void clearBlockedRequestsForUrl(const QUrl &url)
bool removeSubscription(AdBlockSubscription *subscription)
AdBlockDialog * showDialog(QWidget *parent=nullptr)
void addDisabledRule(const QString &filter)
QList< AdBlockSubscription * > subscriptions() const
void updateAllSubscriptions()
bool canBeBlocked(const QUrl &url) const
QString elementHidingRulesForDomain(const QUrl &url) const
bool isEnabled() const
bool adBlockDisabledForUrl(const QUrl &url) const
const AdBlockRule * match(const QWebEngineUrlRequestInfo &request, const QString &urlDomain, const QString &urlString) const
QString elementHidingRules() const
QString elementHidingRulesForDomain(const QString &domain) const
bool elemHideDisabledForUrl(const QUrl &url) const
bool genericElemHideDisabledForUrl(const QUrl &url) const
QString filter() const
AdBlockSubscription * subscription() const
virtual bool canBeRemoved() const
void requestBlocked(const AdBlockedRequest &request)
static QString currentProfilePath()
Definition: datapaths.cpp:95
static QString ensureUniqueFilename(const QString &name, const QString &appendFormat=QSL("(%1)"))
Definition: qztools.cpp:257
static QString filterCharsFromFilename(const QString &name)
Definition: qztools.cpp:309
void beginGroup(const QString &prefix)
Definition: settings.cpp:79
void endGroup()
Definition: settings.cpp:84
QVariant value(const QString &key, const QVariant &defaultValue=QVariant())
Definition: settings.cpp:74
void setValue(const QString &key, const QVariant &defaultValue=QVariant())
Definition: settings.cpp:69
#define mApp
i
Definition: i18n.py:23
#define QL1S(x)
Definition: qzcommon.h:44
#define QSL(x)
Definition: qzcommon.h:40