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