1 /* ============================================================
2 * QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader
3 * Copyright (C) 2011-2020 QuiteRSS Team <quiterssteam@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 <https://www.gnu.org/licenses/>.
17 * ============================================================ */
18 /* ============================================================
19 * QupZilla - WebKit based browser
20 * Copyright (C) 2010-2014 David Rosca <nowrep@gmail.com>
21 *
22 * This program is free software: you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License as published by
24 * the Free Software Foundation, either version 3 of the License, or
25 * (at your option) any later version.
26 *
27 * This program is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program. If not, see <https://www.gnu.org/licenses/>.
34 * ============================================================ */
35 #include "adblockmanager.h"
36 #include "adblockdialog.h"
37 #include "adblockmatcher.h"
38 #include "adblocksubscription.h"
39 #include "adblockblockednetworkreply.h"
40 #include "adblockicon.h"
41 #include "common.h"
42 #include "mainapplication.h"
43 #include "networkmanager.h"
44 #include "webpage.h"
45 #include "settings.h"
46
47 #include <QDateTime>
48 #include <QTextStream>
49 #include <QDir>
50 #include <QTimer>
51 #include <QDebug>
52
53 //#define ADBLOCK_DEBUG
54
55 #ifdef ADBLOCK_DEBUG
56 #include <QElapsedTimer>
57 #endif
58
59 AdBlockManager* AdBlockManager::s_adBlockManager = 0;
60
AdBlockManager(QObject * parent)61 AdBlockManager::AdBlockManager(QObject* parent)
62 : QObject(parent)
63 , m_loaded(false)
64 , m_enabled(true)
65 , m_useLimitedEasyList(true)
66 , m_matcher(new AdBlockMatcher(this))
67 {
68 load();
69 }
70
~AdBlockManager()71 AdBlockManager::~AdBlockManager()
72 {
73 qDeleteAll(m_subscriptions);
74 }
75
instance()76 AdBlockManager* AdBlockManager::instance()
77 {
78 if (!s_adBlockManager) {
79 s_adBlockManager = new AdBlockManager(mainApp->networkManager());
80 }
81
82 return s_adBlockManager;
83 }
84
setEnabled(bool enabled)85 void AdBlockManager::setEnabled(bool enabled)
86 {
87 if (m_enabled == enabled) {
88 return;
89 }
90
91 m_enabled = enabled;
92 emit enabledChanged(enabled);
93
94 Settings settings;
95 settings.beginGroup("AdBlock");
96 settings.setValue("enabled", m_enabled);
97 settings.endGroup();
98
99 load();
100 mainApp->reloadUserStyleBrowser();
101 }
102
subscriptions() const103 QList<AdBlockSubscription*> AdBlockManager::subscriptions() const
104 {
105 return m_subscriptions;
106 }
107
block(const QNetworkRequest & request)108 QNetworkReply* AdBlockManager::block(const QNetworkRequest &request)
109 {
110 #ifdef ADBLOCK_DEBUG
111 QElapsedTimer timer;
112 timer.start();
113 #endif
114 const QString urlString = request.url().toEncoded().toLower();
115 const QString urlDomain = request.url().host().toLower();
116 const QString urlScheme = request.url().scheme().toLower();
117
118 if (!isEnabled() || !canRunOnScheme(urlScheme))
119 return 0;
120
121 const AdBlockRule* blockedRule = m_matcher->match(request, urlDomain, urlString);
122
123 if (blockedRule) {
124 QVariant v = request.attribute((QNetworkRequest::Attribute)(QNetworkRequest::User + 100));
125 WebPage* webPage = static_cast<WebPage*>(v.value<void*>());
126 if (WebPage::isPointerSafeToUse(webPage)) {
127 if (!canBeBlocked(webPage->mainFrame()->url())) {
128 return 0;
129 }
130
131 webPage->addAdBlockRule(blockedRule, request.url());
132 }
133
134 AdBlockBlockedNetworkReply* reply = new AdBlockBlockedNetworkReply(blockedRule, this);
135 reply->setRequest(request);
136
137 #ifdef ADBLOCK_DEBUG
138 qDebug() << "BLOCKED: " << timer.elapsed() << blockedRule->filter() << request.url();
139 #endif
140
141 return reply;
142 }
143
144 #ifdef ADBLOCK_DEBUG
145 qDebug() << timer.elapsed() << request.url();
146 #endif
147
148 return 0;
149 }
150
disabledRules() const151 QStringList AdBlockManager::disabledRules() const
152 {
153 return m_disabledRules;
154 }
155
addDisabledRule(const QString & filter)156 void AdBlockManager::addDisabledRule(const QString &filter)
157 {
158 m_disabledRules.append(filter);
159 }
160
removeDisabledRule(const QString & filter)161 void AdBlockManager::removeDisabledRule(const QString &filter)
162 {
163 m_disabledRules.removeOne(filter);
164 }
165
addSubscription(const QString & title,const QString & url)166 AdBlockSubscription* AdBlockManager::addSubscription(const QString &title, const QString &url)
167 {
168 if (title.isEmpty() || url.isEmpty()) {
169 return 0;
170 }
171
172 QString fileName = Common::filterCharsFromFilename(title.toLower()) + ".txt";
173 QString filePath = Common::ensureUniqueFilename(mainApp->dataDir() + "/adblock/" + fileName);
174
175 QByteArray data = QString("Title: %1\nUrl: %2\n[Adblock Plus 1.1.1]").arg(title, url).toLatin1();
176
177 QFile file(filePath);
178 if (!file.open(QFile::WriteOnly | QFile::Truncate)) {
179 qWarning() << "AdBlockManager: Cannot write to file" << filePath;
180 return 0;
181 }
182
183 file.write(data);
184 file.close();
185
186 AdBlockSubscription* subscription = new AdBlockSubscription(title, this);
187 subscription->setUrl(QUrl(url));
188 subscription->setFilePath(filePath);
189 subscription->loadSubscription(m_disabledRules);
190
191 m_subscriptions.insert(m_subscriptions.count() - 1, subscription);
192
193 return subscription;
194 }
195
removeSubscription(AdBlockSubscription * subscription)196 bool AdBlockManager::removeSubscription(AdBlockSubscription* subscription)
197 {
198 if (!m_subscriptions.contains(subscription) || !subscription->canBeRemoved()) {
199 return false;
200 }
201
202 QFile(subscription->filePath()).remove();
203 m_subscriptions.removeOne(subscription);
204
205 delete subscription;
206 return true;
207 }
208
customList() const209 AdBlockCustomList* AdBlockManager::customList() const
210 {
211 foreach (AdBlockSubscription* subscription, m_subscriptions) {
212 AdBlockCustomList* list = qobject_cast<AdBlockCustomList*>(subscription);
213
214 if (list) {
215 return list;
216 }
217 }
218
219 return 0;
220 }
221
load()222 void AdBlockManager::load()
223 {
224 if (m_loaded) {
225 return;
226 }
227
228 #ifdef ADBLOCK_DEBUG
229 QElapsedTimer timer;
230 timer.start();
231 #endif
232
233 Settings settings;
234 settings.beginGroup("AdBlock");
235 m_enabled = settings.value("enabled", m_enabled).toBool();
236 m_useLimitedEasyList = settings.value("useLimitedEasyList", m_useLimitedEasyList).toBool();
237 m_disabledRules = settings.value("disabledRules", QStringList()).toStringList();
238 QDateTime lastUpdate = settings.value("lastUpdate", QDateTime()).toDateTime();
239 settings.endGroup();
240
241 if (!m_enabled) {
242 return;
243 }
244
245 QDir adblockDir(mainApp->dataDir() + "/adblock");
246 // Create if neccessary
247 if (!adblockDir.exists()) {
248 QDir(mainApp->dataDir()).mkdir("adblock");
249 }
250
251 foreach (const QString &fileName, adblockDir.entryList(QStringList("*.txt"), QDir::Files)) {
252 if (fileName == QLatin1String("customlist.txt")) {
253 continue;
254 }
255
256 const QString absolutePath = adblockDir.absoluteFilePath(fileName);
257 QFile file(absolutePath);
258 if (!file.open(QFile::ReadOnly)) {
259 continue;
260 }
261
262 QTextStream textStream(&file);
263 textStream.setCodec("UTF-8");
264 QString title = textStream.readLine(1024).remove(QLatin1String("Title: "));
265 QUrl url = QUrl(textStream.readLine(1024).remove(QLatin1String("Url: ")));
266
267 if (title.isEmpty() || !url.isValid()) {
268 qWarning() << "AdBlockManager: Invalid subscription file" << absolutePath;
269 continue;
270 }
271
272 AdBlockSubscription* subscription = new AdBlockSubscription(title, this);
273 subscription->setUrl(url);
274 subscription->setFilePath(absolutePath);
275
276 m_subscriptions.append(subscription);
277 }
278
279 // Prepend EasyList if subscriptions are empty
280 if (m_subscriptions.isEmpty()) {
281 AdBlockSubscription* easyList = new AdBlockSubscription(tr("EasyList"), this);
282 easyList->setUrl(QUrl(ADBLOCK_EASYLIST_URL));
283 easyList->setFilePath(mainApp->dataDir() + "/adblock/easylist.txt");
284 connect(easyList, SIGNAL(subscriptionUpdated()), mainApp, SLOT(reloadUserStyleBrowser()));
285
286 m_subscriptions.prepend(easyList);
287 }
288
289 // Append CustomList
290 AdBlockCustomList* customList = new AdBlockCustomList(this);
291 m_subscriptions.append(customList);
292
293 // Load all subscriptions
294 foreach (AdBlockSubscription* subscription, m_subscriptions) {
295 subscription->loadSubscription(m_disabledRules);
296
297 connect(subscription, SIGNAL(subscriptionUpdated()), mainApp, SLOT(reloadUserStyleBrowser()));
298 connect(subscription, SIGNAL(subscriptionChanged()), m_matcher, SLOT(update()));
299 }
300
301 if (lastUpdate.addDays(5) < QDateTime::currentDateTime()) {
302 QTimer::singleShot(1000 * 60, this, SLOT(updateAllSubscriptions()));
303 }
304
305 #ifdef ADBLOCK_DEBUG
306 qDebug() << "AdBlock loaded in" << timer.elapsed();
307 #endif
308
309 m_matcher->update();
310 m_loaded = true;
311 }
312
updateAllSubscriptions()313 void AdBlockManager::updateAllSubscriptions()
314 {
315 foreach (AdBlockSubscription* subscription, m_subscriptions) {
316 subscription->updateSubscription();
317 }
318
319 Settings settings;
320 settings.beginGroup("AdBlock");
321 settings.setValue("lastUpdate", QDateTime::currentDateTime());
322 settings.endGroup();
323 }
324
save()325 void AdBlockManager::save()
326 {
327 if (!m_loaded) {
328 return;
329 }
330
331 foreach (AdBlockSubscription* subscription, m_subscriptions) {
332 subscription->saveSubscription();
333 }
334
335 Settings settings;
336 settings.beginGroup("AdBlock");
337 settings.setValue("enabled", m_enabled);
338 settings.setValue("useLimitedEasyList", m_useLimitedEasyList);
339 settings.setValue("disabledRules", m_disabledRules);
340 settings.endGroup();
341 }
342
isEnabled() const343 bool AdBlockManager::isEnabled() const
344 {
345 return m_enabled;
346 }
347
canRunOnScheme(const QString & scheme) const348 bool AdBlockManager::canRunOnScheme(const QString &scheme) const
349 {
350 return !(scheme == QLatin1String("file") || scheme == QLatin1String("qrc")
351 || scheme == QLatin1String("qupzilla") || scheme == QLatin1String("data")
352 || scheme == QLatin1String("abp"));
353 }
354
useLimitedEasyList() const355 bool AdBlockManager::useLimitedEasyList() const
356 {
357 return m_useLimitedEasyList;
358 }
359
setUseLimitedEasyList(bool useLimited)360 void AdBlockManager::setUseLimitedEasyList(bool useLimited)
361 {
362 m_useLimitedEasyList = useLimited;
363
364 foreach (AdBlockSubscription* subscription, m_subscriptions) {
365 if (subscription->url() == QUrl(ADBLOCK_EASYLIST_URL)) {
366 subscription->updateSubscription();
367 }
368 }
369 }
370
canBeBlocked(const QUrl & url) const371 bool AdBlockManager::canBeBlocked(const QUrl &url) const
372 {
373 return !m_matcher->adBlockDisabledForUrl(url);
374 }
375
elementHidingRules() const376 QString AdBlockManager::elementHidingRules() const
377 {
378 return m_matcher->elementHidingRules();
379 }
380
elementHidingRulesForDomain(const QUrl & url) const381 QString AdBlockManager::elementHidingRulesForDomain(const QUrl &url) const
382 {
383 if (!isEnabled() || !canRunOnScheme(url.scheme()) || !canBeBlocked(url))
384 return QString();
385
386 // Acid3 doesn't like the way element hiding rules are embedded into page
387 if (url.host() == QLatin1String("acid3.acidtests.org"))
388 return QString();
389
390 return m_matcher->elementHidingRulesForDomain(url.host());
391 }
392
subscriptionByName(const QString & name) const393 AdBlockSubscription* AdBlockManager::subscriptionByName(const QString &name) const
394 {
395 foreach (AdBlockSubscription* subscription, m_subscriptions) {
396 if (subscription->title() == name) {
397 return subscription;
398 }
399 }
400
401 return 0;
402 }
403
showDialog()404 AdBlockDialog* AdBlockManager::showDialog()
405 {
406 if (!m_adBlockDialog) {
407 m_adBlockDialog = new AdBlockDialog(mainApp->mainWindow());
408 }
409
410 m_adBlockDialog.data()->show();
411
412 return m_adBlockDialog.data();
413 }
414
showRule()415 void AdBlockManager::showRule()
416 {
417 if (QAction* action = qobject_cast<QAction*>(sender())) {
418 const AdBlockRule* rule = static_cast<const AdBlockRule*>(action->data().value<void*>());
419
420 if (rule) {
421 showDialog()->showRule(rule);
422 }
423 }
424 }
425