1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2010 - 2014 David Rosca <nowrep@gmail.com>
4 * Copyright (C) 2014 - 2017 Jan Bajer aka bajasoft <jbajer@gmail.com>
5 * Copyright (C) 2015 - 2019 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 **************************************************************************/
21
22 #include "AdblockContentFiltersProfile.h"
23 #include "Console.h"
24 #include "Job.h"
25 #include "SessionsManager.h"
26
27 #include <QtConcurrent/QtConcurrentRun>
28 #include <QtCore/QCoreApplication>
29 #include <QtCore/QDir>
30 #include <QtCore/QSaveFile>
31 #include <QtCore/QTextStream>
32
33 namespace Otter
34 {
35
36 QVector<QChar> AdblockContentFiltersProfile::m_separators({QLatin1Char('_'), QLatin1Char('-'), QLatin1Char('.'), QLatin1Char('%')});
37 QHash<QString, AdblockContentFiltersProfile::RuleOption> AdblockContentFiltersProfile::m_options({{QLatin1String("third-party"), ThirdPartyOption}, {QLatin1String("stylesheet"), StyleSheetOption}, {QLatin1String("image"), ImageOption}, {QLatin1String("script"), ScriptOption}, {QLatin1String("object"), ObjectOption}, {QLatin1String("object-subrequest"), ObjectSubRequestOption}, {QLatin1String("object_subrequest"), ObjectSubRequestOption}, {QLatin1String("subdocument"), SubDocumentOption}, {QLatin1String("xmlhttprequest"), XmlHttpRequestOption}, {QLatin1String("websocket"), WebSocketOption}, {QLatin1String("popup"), PopupOption}, {QLatin1String("elemhide"), ElementHideOption}, {QLatin1String("generichide"), GenericHideOption}});
38 QHash<NetworkManager::ResourceType, AdblockContentFiltersProfile::RuleOption> AdblockContentFiltersProfile::m_resourceTypes({{NetworkManager::ImageType, ImageOption}, {NetworkManager::ScriptType, ScriptOption}, {NetworkManager::StyleSheetType, StyleSheetOption}, {NetworkManager::ObjectType, ObjectOption}, {NetworkManager::XmlHttpRequestType, XmlHttpRequestOption}, {NetworkManager::SubFrameType, SubDocumentOption},{NetworkManager::PopupType, PopupOption}, {NetworkManager::ObjectSubrequestType, ObjectSubRequestOption}, {NetworkManager::WebSocketType, WebSocketOption}});
39
AdblockContentFiltersProfile(const QString & name,const QString & title,const QUrl & updateUrl,const QDateTime & lastUpdate,const QStringList & languages,int updateInterval,const ProfileCategory & category,const ProfileFlags & flags,QObject * parent)40 AdblockContentFiltersProfile::AdblockContentFiltersProfile(const QString &name, const QString &title, const QUrl &updateUrl, const QDateTime &lastUpdate, const QStringList &languages, int updateInterval, const ProfileCategory &category, const ProfileFlags &flags, QObject *parent) : ContentFiltersProfile(parent),
41 m_root(nullptr),
42 m_dataFetchJob(nullptr),
43 m_name(name),
44 m_title(title),
45 m_updateUrl(updateUrl),
46 m_lastUpdate(lastUpdate),
47 m_category(category),
48 m_error(NoError),
49 m_flags(flags),
50 m_updateInterval(updateInterval),
51 m_isEmpty(true),
52 m_wasLoaded(false)
53 {
54 if (languages.isEmpty())
55 {
56 m_languages = {QLocale::AnyLanguage};
57 }
58 else
59 {
60 for (int i = 0; i < languages.count(); ++i)
61 {
62 m_languages.append(QLocale(languages.at(i)).language());
63 }
64 }
65
66 loadHeader();
67 }
68
clear()69 void AdblockContentFiltersProfile::clear()
70 {
71 if (!m_wasLoaded)
72 {
73 return;
74 }
75
76 if (m_root)
77 {
78 QtConcurrent::run(this, &AdblockContentFiltersProfile::deleteNode, m_root);
79 }
80
81 m_cosmeticFiltersRules.clear();
82 m_cosmeticFiltersDomainExceptions.clear();
83 m_cosmeticFiltersDomainRules.clear();
84
85 m_wasLoaded = false;
86 }
87
loadHeader()88 void AdblockContentFiltersProfile::loadHeader()
89 {
90 const QString &path(getPath());
91
92 if (!QFile::exists(path))
93 {
94 return;
95 }
96
97 QFile file(path);
98
99 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
100 {
101 m_error = ReadError;
102
103 Console::addMessage(QCoreApplication::translate("main", "Failed to open content blocking profile file: %1").arg(file.errorString()), Console::OtherCategory, Console::ErrorLevel, file.fileName());
104
105 return;
106 }
107
108 QTextStream stream(&file);
109
110 while (!stream.atEnd())
111 {
112 QString line(stream.readLine().trimmed());
113
114 if (!line.startsWith(QLatin1Char('!')))
115 {
116 m_isEmpty = false;
117
118 break;
119 }
120
121 if (!m_flags.testFlag(HasCustomTitleFlag) && line.startsWith(QLatin1String("! Title: ")))
122 {
123 m_title = line.remove(QLatin1String("! Title: "));
124
125 continue;
126 }
127 }
128
129 file.close();
130
131 if (!m_dataFetchJob && m_updateInterval > 0 && (!m_lastUpdate.isValid() || m_lastUpdate.daysTo(QDateTime::currentDateTimeUtc()) > m_updateInterval))
132 {
133 update();
134 }
135 }
136
parseRuleLine(const QString & rule)137 void AdblockContentFiltersProfile::parseRuleLine(const QString &rule)
138 {
139 if (rule.indexOf(QLatin1Char('!')) == 0 || rule.isEmpty())
140 {
141 return;
142 }
143
144 if (rule.startsWith(QLatin1String("##")))
145 {
146 if (ContentFiltersManager::getCosmeticFiltersMode() == ContentFiltersManager::AllFilters)
147 {
148 m_cosmeticFiltersRules.append(rule.mid(2));
149 }
150
151 return;
152 }
153
154 if (rule.contains(QLatin1String("##")))
155 {
156 if (ContentFiltersManager::getCosmeticFiltersMode() != ContentFiltersManager::NoFilters)
157 {
158 parseStyleSheetRule(rule.split(QLatin1String("##")), m_cosmeticFiltersDomainRules);
159 }
160
161 return;
162 }
163
164 if (rule.contains(QLatin1String("#@#")))
165 {
166 if (ContentFiltersManager::getCosmeticFiltersMode() != ContentFiltersManager::NoFilters)
167 {
168 parseStyleSheetRule(rule.split(QLatin1String("#@#")), m_cosmeticFiltersDomainExceptions);
169 }
170
171 return;
172 }
173
174 const int optionsSeparator(rule.indexOf(QLatin1Char('$')));
175 const QStringList options((optionsSeparator >= 0) ? rule.mid(optionsSeparator + 1).split(QLatin1Char(','), QString::SkipEmptyParts) : QStringList());
176 QString line(rule);
177
178 if (optionsSeparator >= 0)
179 {
180 line = line.left(optionsSeparator);
181 }
182
183 if (line.endsWith(QLatin1Char('*')))
184 {
185 line = line.left(line.length() - 1);
186 }
187
188 if (line.startsWith(QLatin1Char('*')))
189 {
190 line = line.mid(1);
191 }
192
193 if (!ContentFiltersManager::areWildcardsEnabled() && line.contains(QLatin1Char('*')))
194 {
195 return;
196 }
197
198 QStringList allowedDomains;
199 QStringList blockedDomains;
200 RuleOptions ruleOptions;
201 RuleMatch ruleMatch(ContainsMatch);
202 const bool isException(line.startsWith(QLatin1String("@@")));
203
204 if (isException)
205 {
206 line = line.mid(2);
207 }
208
209 const bool needsDomainCheck(line.startsWith(QLatin1String("||")));
210
211 if (needsDomainCheck)
212 {
213 line = line.mid(2);
214 }
215
216 if (line.startsWith(QLatin1Char('|')))
217 {
218 ruleMatch = StartMatch;
219
220 line = line.mid(1);
221 }
222
223 if (line.endsWith(QLatin1Char('|')))
224 {
225 ruleMatch = ((ruleMatch == StartMatch) ? ExactMatch : EndMatch);
226
227 line = line.left(line.length() - 1);
228 }
229
230 for (int i = 0; i < options.count(); ++i)
231 {
232 const bool optionException(options.at(i).startsWith(QLatin1Char('~')));
233 const QString optionName(optionException ? options.at(i).mid(1) : options.at(i));
234
235 if (m_options.contains(optionName))
236 {
237 const RuleOption option(m_options.value(optionName));
238
239 if ((!isException || optionException) && (option == ElementHideOption || option == GenericHideOption))
240 {
241 continue;
242 }
243
244 if (!optionException)
245 {
246 ruleOptions |= option;
247 }
248 else if (option != WebSocketOption && option != PopupOption)
249 {
250 ruleOptions |= static_cast<RuleOption>(option * 2);
251 }
252 }
253 else if (optionName.startsWith(QLatin1String("domain")))
254 {
255 const QStringList parsedDomains(options.at(i).mid(options.at(i).indexOf(QLatin1Char('=')) + 1).split(QLatin1Char('|'), QString::SkipEmptyParts));
256
257 for (int j = 0; j < parsedDomains.count(); ++j)
258 {
259 if (parsedDomains.at(j).startsWith(QLatin1Char('~')))
260 {
261 allowedDomains.append(parsedDomains.at(j).mid(1));
262
263 continue;
264 }
265
266 blockedDomains.append(parsedDomains.at(j));
267 }
268 }
269 else
270 {
271 return;
272 }
273 }
274
275 addRule(new ContentBlockingRule(rule, blockedDomains, allowedDomains, ruleOptions, ruleMatch, isException, needsDomainCheck), line);
276 }
277
parseStyleSheetRule(const QStringList & line,QMultiHash<QString,QString> & list) const278 void AdblockContentFiltersProfile::parseStyleSheetRule(const QStringList &line, QMultiHash<QString, QString> &list) const
279 {
280 const QStringList domains(line.at(0).split(QLatin1Char(',')));
281
282 for (int i = 0; i < domains.count(); ++i)
283 {
284 list.insert(domains.at(i), line.at(1));
285 }
286 }
287
addRule(ContentBlockingRule * rule,const QString & ruleString) const288 void AdblockContentFiltersProfile::addRule(ContentBlockingRule *rule, const QString &ruleString) const
289 {
290 Node *node(m_root);
291
292 for (int i = 0; i < ruleString.length(); ++i)
293 {
294 const QChar value(ruleString.at(i));
295 bool childrenExists(false);
296
297 for (int j = 0; j < node->children.count(); ++j)
298 {
299 Node *nextNode(node->children.at(j));
300
301 if (nextNode->value == value)
302 {
303 node = nextNode;
304
305 childrenExists = true;
306
307 break;
308 }
309 }
310
311 if (!childrenExists)
312 {
313 Node *newNode(new Node());
314 newNode->value = value;
315
316 if (value == QLatin1Char('^'))
317 {
318 node->children.insert(0, newNode);
319 }
320 else
321 {
322 node->children.append(newNode);
323 }
324
325 node = newNode;
326 }
327 }
328
329 node->rules.append(rule);
330 }
331
deleteNode(Node * node) const332 void AdblockContentFiltersProfile::deleteNode(Node *node) const
333 {
334 for (int i = 0; i < node->children.count(); ++i)
335 {
336 deleteNode(node->children.at(i));
337 }
338
339 for (int i = 0; i < node->rules.count(); ++i)
340 {
341 delete node->rules.at(i);
342 }
343
344 delete node;
345 }
346
checkUrlSubstring(const Node * node,const QString & subString,QString currentRule,NetworkManager::ResourceType resourceType)347 ContentFiltersManager::CheckResult AdblockContentFiltersProfile::checkUrlSubstring(const Node *node, const QString &subString, QString currentRule, NetworkManager::ResourceType resourceType)
348 {
349 ContentFiltersManager::CheckResult result;
350 ContentFiltersManager::CheckResult currentResult;
351
352 for (int i = 0; i < subString.length(); ++i)
353 {
354 const QChar treeChar(subString.at(i));
355 bool childrenExists(false);
356
357 currentResult = evaluateRulesInNode(node, currentRule, resourceType);
358
359 if (currentResult.isBlocked)
360 {
361 result = currentResult;
362 }
363 else if (currentResult.isException)
364 {
365 return currentResult;
366 }
367
368 for (int j = 0; j < node->children.count(); ++j)
369 {
370 const Node *nextNode(node->children.at(j));
371
372 if (nextNode->value == QLatin1Char('*'))
373 {
374 const QString wildcardSubString(subString.mid(i));
375
376 for (int k = 0; k < wildcardSubString.length(); ++k)
377 {
378 currentResult = checkUrlSubstring(nextNode, wildcardSubString.right(wildcardSubString.length() - k), (currentRule + wildcardSubString.left(k)), resourceType);
379
380 if (currentResult.isBlocked)
381 {
382 result = currentResult;
383 }
384 else if (currentResult.isException)
385 {
386 return currentResult;
387 }
388 }
389 }
390
391 if (nextNode->value == QLatin1Char('^') && !treeChar.isDigit() && !treeChar.isLetter() && !m_separators.contains(treeChar))
392 {
393 currentResult = checkUrlSubstring(nextNode, subString.mid(i), currentRule, resourceType);
394
395 if (currentResult.isBlocked)
396 {
397 result = currentResult;
398 }
399 else if (currentResult.isException)
400 {
401 return currentResult;
402 }
403 }
404
405 if (nextNode->value == treeChar)
406 {
407 node = nextNode;
408
409 childrenExists = true;
410
411 break;
412 }
413 }
414
415 if (!childrenExists)
416 {
417 return result;
418 }
419
420 currentRule += treeChar;
421 }
422
423 currentResult = evaluateRulesInNode(node, currentRule, resourceType);
424
425 if (currentResult.isBlocked)
426 {
427 result = currentResult;
428 }
429 else if (currentResult.isException)
430 {
431 return currentResult;
432 }
433
434 for (int i = 0; i < node->children.count(); ++i)
435 {
436 if (node->children.at(i)->value == QLatin1Char('^'))
437 {
438 currentResult = evaluateRulesInNode(node, currentRule, resourceType);
439
440 if (currentResult.isBlocked)
441 {
442 result = currentResult;
443 }
444 else if (currentResult.isException)
445 {
446 return currentResult;
447 }
448 }
449 }
450
451 return result;
452 }
453
checkRuleMatch(const ContentBlockingRule * rule,const QString & currentRule,NetworkManager::ResourceType resourceType) const454 ContentFiltersManager::CheckResult AdblockContentFiltersProfile::checkRuleMatch(const ContentBlockingRule *rule, const QString ¤tRule, NetworkManager::ResourceType resourceType) const
455 {
456 switch (rule->ruleMatch)
457 {
458 case StartMatch:
459 if (!m_requestUrl.startsWith(currentRule))
460 {
461 return {};
462 }
463
464 break;
465 case EndMatch:
466 if (!m_requestUrl.endsWith(currentRule))
467 {
468 return {};
469 }
470
471 break;
472 case ExactMatch:
473 if (m_requestUrl != currentRule)
474 {
475 return {};
476 }
477
478 break;
479 default:
480 if (!m_requestUrl.contains(currentRule))
481 {
482 return {};
483 }
484
485 break;
486 }
487
488 const QStringList requestSubdomainList(ContentFiltersManager::createSubdomainList(m_requestHost));
489
490 if (rule->needsDomainCheck && !requestSubdomainList.contains(currentRule.left(currentRule.indexOf(m_domainExpression))))
491 {
492 return {};
493 }
494
495 const bool hasBlockedDomains(!rule->blockedDomains.isEmpty());
496 const bool hasAllowedDomains(!rule->allowedDomains.isEmpty());
497 bool isBlocked(true);
498
499 if (hasBlockedDomains)
500 {
501 isBlocked = resolveDomainExceptions(m_baseUrlHost, rule->blockedDomains);
502
503 if (!isBlocked)
504 {
505 return {};
506 }
507 }
508
509 isBlocked = (hasAllowedDomains ? !resolveDomainExceptions(m_baseUrlHost, rule->allowedDomains) : isBlocked);
510
511 if (rule->ruleOptions.testFlag(ThirdPartyExceptionOption) || rule->ruleOptions.testFlag(ThirdPartyOption))
512 {
513 if (m_baseUrlHost.isEmpty() || requestSubdomainList.contains(m_baseUrlHost))
514 {
515 isBlocked = rule->ruleOptions.testFlag(ThirdPartyExceptionOption);
516 }
517 else if (!hasBlockedDomains && !hasAllowedDomains)
518 {
519 isBlocked = rule->ruleOptions.testFlag(ThirdPartyOption);
520 }
521 }
522
523 if (rule->ruleOptions != NoOption)
524 {
525 QHash<NetworkManager::ResourceType, RuleOption>::const_iterator iterator;
526
527 for (iterator = m_resourceTypes.begin(); iterator != m_resourceTypes.end(); ++iterator)
528 {
529 const bool supportsException(iterator.value() != WebSocketOption && iterator.value() != PopupOption);
530
531 if (rule->ruleOptions.testFlag(iterator.value()) || (supportsException && rule->ruleOptions.testFlag(static_cast<RuleOption>(iterator.value() * 2))))
532 {
533 if (resourceType == iterator.key())
534 {
535 isBlocked = (isBlocked ? rule->ruleOptions.testFlag(iterator.value()) : isBlocked);
536 }
537 else if (supportsException)
538 {
539 isBlocked = (isBlocked ? rule->ruleOptions.testFlag(static_cast<RuleOption>(iterator.value() * 2)) : isBlocked);
540 }
541 else
542 {
543 isBlocked = false;
544 }
545 }
546 }
547 }
548 else if (resourceType == NetworkManager::PopupType)
549 {
550 isBlocked = false;
551 }
552
553 if (isBlocked)
554 {
555 ContentFiltersManager::CheckResult result;
556 result.rule = rule->rule;
557
558 if (rule->isException)
559 {
560 result.isBlocked = false;
561 result.isException = true;
562
563 if (rule->ruleOptions.testFlag(ElementHideOption))
564 {
565 result.comesticFiltersMode = ContentFiltersManager::NoFilters;
566 }
567 else if (rule->ruleOptions.testFlag(GenericHideOption))
568 {
569 result.comesticFiltersMode = ContentFiltersManager::DomainOnlyFilters;
570 }
571
572 return result;
573 }
574
575 result.isBlocked = true;
576
577 return result;
578 }
579
580 return {};
581 }
582
handleJobFinished(bool isSuccess)583 void AdblockContentFiltersProfile::handleJobFinished(bool isSuccess)
584 {
585 if (!m_dataFetchJob)
586 {
587 return;
588 }
589
590 QIODevice *device(m_dataFetchJob->getData());
591
592 m_dataFetchJob->deleteLater();
593 m_dataFetchJob = nullptr;
594
595 if (!isSuccess)
596 {
597 m_error = DownloadError;
598
599 Console::addMessage(QCoreApplication::translate("main", "Failed to update content blocking profile: %1").arg(device ? device->errorString() : QLatin1String("-")), Console::OtherCategory, Console::ErrorLevel, getPath());
600
601 return;
602 }
603
604 const QByteArray rawData(device->readAll());
605 QTextStream stream(rawData);
606 stream.setCodec("UTF-8");
607
608 QByteArray parsedData(stream.readLine().toUtf8());
609 QByteArray checksum;
610
611 while (!stream.atEnd())
612 {
613 QString line(stream.readLine());
614
615 if (!line.isEmpty())
616 {
617 if (checksum.isEmpty() && line.startsWith(QLatin1String("! Checksum:")))
618 {
619 checksum = line.remove(0, 11).trimmed().toUtf8();
620 }
621 else
622 {
623 parsedData.append(QLatin1Char('\n') + line);
624 }
625 }
626 }
627
628 if (!checksum.isEmpty() && QCryptographicHash::hash(parsedData, QCryptographicHash::Md5).toBase64().remove(22, 2) != checksum)
629 {
630 m_error = ChecksumError;
631
632 Console::addMessage(QCoreApplication::translate("main", "Failed to update content blocking profile: checksum mismatch"), Console::OtherCategory, Console::ErrorLevel, getPath());
633
634 return;
635 }
636
637 QDir().mkpath(SessionsManager::getWritableDataPath(QLatin1String("contentBlocking")));
638
639 QSaveFile file(SessionsManager::getWritableDataPath(QLatin1String("contentBlocking/%1.txt")).arg(m_name));
640
641 if (!file.open(QIODevice::WriteOnly))
642 {
643 m_error = DownloadError;
644
645 Console::addMessage(QCoreApplication::translate("main", "Failed to update content blocking profile: %1").arg(file.errorString()), Console::OtherCategory, Console::ErrorLevel, file.fileName());
646
647 return;
648 }
649
650 file.write(rawData);
651
652 m_lastUpdate = QDateTime::currentDateTimeUtc();
653
654 if (!file.commit())
655 {
656 Console::addMessage(QCoreApplication::translate("main", "Failed to update content blocking profile: %1").arg(file.errorString()), Console::OtherCategory, Console::ErrorLevel, file.fileName());
657 }
658
659 clear();
660 loadHeader();
661
662 if (m_wasLoaded)
663 {
664 loadRules();
665 }
666
667 emit profileModified(m_name);
668 }
669
setUpdateInterval(int interval)670 void AdblockContentFiltersProfile::setUpdateInterval(int interval)
671 {
672 if (interval != m_updateInterval)
673 {
674 m_updateInterval = interval;
675
676 emit profileModified(m_name);
677 }
678 }
679
setUpdateUrl(const QUrl & url)680 void AdblockContentFiltersProfile::setUpdateUrl(const QUrl &url)
681 {
682 if (url.isValid() && url != m_updateUrl)
683 {
684 m_updateUrl = url;
685 m_flags |= HasCustomUpdateUrlFlag;
686
687 emit profileModified(m_name);
688 }
689 }
690
setCategory(ProfileCategory category)691 void AdblockContentFiltersProfile::setCategory(ProfileCategory category)
692 {
693 if (category != m_category)
694 {
695 m_category = category;
696
697 emit profileModified(m_name);
698 }
699 }
700
setTitle(const QString & title)701 void AdblockContentFiltersProfile::setTitle(const QString &title)
702 {
703 if (title != m_title)
704 {
705 m_title = title;
706 m_flags |= HasCustomTitleFlag;
707
708 emit profileModified(m_name);
709 }
710 }
711
getName() const712 QString AdblockContentFiltersProfile::getName() const
713 {
714 return m_name;
715 }
716
getTitle() const717 QString AdblockContentFiltersProfile::getTitle() const
718 {
719 return (m_title.isEmpty() ? tr("(Unknown)") : m_title);
720 }
721
getPath() const722 QString AdblockContentFiltersProfile::getPath() const
723 {
724 return SessionsManager::getWritableDataPath(QLatin1String("contentBlocking/%1.txt")).arg(m_name);
725 }
726
getLastUpdate() const727 QDateTime AdblockContentFiltersProfile::getLastUpdate() const
728 {
729 return m_lastUpdate;
730 }
731
getUpdateUrl() const732 QUrl AdblockContentFiltersProfile::getUpdateUrl() const
733 {
734 return m_updateUrl;
735 }
736
evaluateRulesInNode(const Node * node,const QString & currentRule,NetworkManager::ResourceType resourceType) const737 ContentFiltersManager::CheckResult AdblockContentFiltersProfile::evaluateRulesInNode(const Node *node, const QString ¤tRule, NetworkManager::ResourceType resourceType) const
738 {
739 ContentFiltersManager::CheckResult result;
740
741 for (int i = 0; i < node->rules.count(); ++i)
742 {
743 if (node->rules.at(i))
744 {
745 ContentFiltersManager::CheckResult currentResult(checkRuleMatch(node->rules.at(i), currentRule, resourceType));
746
747 if (currentResult.isBlocked)
748 {
749 result = currentResult;
750 }
751 else if (currentResult.isException)
752 {
753 return currentResult;
754 }
755 }
756 }
757
758 return result;
759 }
760
checkUrl(const QUrl & baseUrl,const QUrl & requestUrl,NetworkManager::ResourceType resourceType)761 ContentFiltersManager::CheckResult AdblockContentFiltersProfile::checkUrl(const QUrl &baseUrl, const QUrl &requestUrl, NetworkManager::ResourceType resourceType)
762 {
763 ContentFiltersManager::CheckResult result;
764
765 if (!m_wasLoaded && !loadRules())
766 {
767 return result;
768 }
769
770 m_baseUrlHost = baseUrl.host();
771 m_requestUrl = requestUrl.url();
772 m_requestHost = requestUrl.host();
773
774 if (m_requestUrl.startsWith(QLatin1String("//")))
775 {
776 m_requestUrl = m_requestUrl.mid(2);
777 }
778
779 for (int i = 0; i < m_requestUrl.length(); ++i)
780 {
781 const ContentFiltersManager::CheckResult currentResult(checkUrlSubstring(m_root, m_requestUrl.right(m_requestUrl.length() - i), {}, resourceType));
782
783 if (currentResult.isBlocked)
784 {
785 result = currentResult;
786 }
787 else if (currentResult.isException)
788 {
789 return currentResult;
790 }
791 }
792
793 return result;
794 }
795
getCosmeticFilters(const QStringList & domains,bool isDomainOnly)796 ContentFiltersManager::CosmeticFiltersResult AdblockContentFiltersProfile::getCosmeticFilters(const QStringList &domains, bool isDomainOnly)
797 {
798 if (!m_wasLoaded)
799 {
800 loadRules();
801 }
802
803 ContentFiltersManager::CosmeticFiltersResult result;
804
805 if (!isDomainOnly)
806 {
807 result.rules = m_cosmeticFiltersRules;
808 }
809
810 for (int i = 0; i < domains.count(); ++i)
811 {
812 result.rules.append(m_cosmeticFiltersDomainRules.values(domains.at(i)));
813 result.exceptions.append(m_cosmeticFiltersDomainExceptions.values(domains.at(i)));
814 }
815
816 return result;
817 }
818
getLanguages() const819 QVector<QLocale::Language> AdblockContentFiltersProfile::getLanguages() const
820 {
821 return m_languages;
822 }
823
getCategory() const824 ContentFiltersProfile::ProfileCategory AdblockContentFiltersProfile::getCategory() const
825 {
826 return m_category;
827 }
828
getError() const829 ContentFiltersProfile::ProfileError AdblockContentFiltersProfile::getError() const
830 {
831 return m_error;
832 }
833
getFlags() const834 ContentFiltersProfile::ProfileFlags AdblockContentFiltersProfile::getFlags() const
835 {
836 return m_flags;
837 }
838
getUpdateInterval() const839 int AdblockContentFiltersProfile::getUpdateInterval() const
840 {
841 return m_updateInterval;
842 }
843
getUpdateProgress() const844 int AdblockContentFiltersProfile::getUpdateProgress() const
845 {
846 return (m_dataFetchJob ? m_dataFetchJob->getProgress() : -1);
847 }
848
loadRules()849 bool AdblockContentFiltersProfile::loadRules()
850 {
851 m_error = NoError;
852
853 if (m_isEmpty && !m_updateUrl.isEmpty())
854 {
855 update();
856
857 return false;
858 }
859
860 m_wasLoaded = true;
861
862 if (m_domainExpression.pattern().isEmpty())
863 {
864 m_domainExpression = QRegularExpression(QLatin1String("[:\?&/=]"));
865 m_domainExpression.optimize();
866 }
867
868 QFile file(getPath());
869 file.open(QIODevice::ReadOnly | QIODevice::Text);
870
871 QTextStream stream(&file);
872 stream.readLine(); // header
873
874 m_root = new Node();
875
876 while (!stream.atEnd())
877 {
878 parseRuleLine(stream.readLine());
879 }
880
881 file.close();
882
883 return true;
884 }
885
update()886 bool AdblockContentFiltersProfile::update()
887 {
888 if (m_dataFetchJob || thread() != QThread::currentThread())
889 {
890 return false;
891 }
892
893 if (!m_updateUrl.isValid())
894 {
895 const QString path(getPath());
896
897 m_error = DownloadError;
898
899 if (m_updateUrl.isEmpty())
900 {
901 Console::addMessage(QCoreApplication::translate("main", "Failed to update content blocking profile, update URL is empty"), Console::OtherCategory, Console::ErrorLevel, path);
902 }
903 else
904 {
905 Console::addMessage(QCoreApplication::translate("main", "Failed to update content blocking profile, update URL (%1) is invalid").arg(m_updateUrl.toString()), Console::OtherCategory, Console::ErrorLevel, path);
906 }
907
908 return false;
909 }
910
911 m_dataFetchJob = new DataFetchJob(m_updateUrl, this);
912
913 connect(m_dataFetchJob, &Job::jobFinished, this, &AdblockContentFiltersProfile::handleJobFinished);
914 connect(m_dataFetchJob, &Job::progressChanged, this, &AdblockContentFiltersProfile::updateProgressChanged);
915
916 m_dataFetchJob->start();
917
918 emit profileModified(m_name);
919
920 return true;
921 }
922
remove()923 bool AdblockContentFiltersProfile::remove()
924 {
925 const QString path(SessionsManager::getWritableDataPath(QLatin1String("contentBlocking/%1.txt")).arg(m_name));
926
927 if (m_dataFetchJob)
928 {
929 m_dataFetchJob->cancel();
930 m_dataFetchJob->deleteLater();
931 m_dataFetchJob = nullptr;
932 }
933
934 if (QFile::exists(path))
935 {
936 return QFile::remove(path);
937 }
938
939 return true;
940 }
941
resolveDomainExceptions(const QString & url,const QStringList & ruleList) const942 bool AdblockContentFiltersProfile::resolveDomainExceptions(const QString &url, const QStringList &ruleList) const
943 {
944 for (int i = 0; i < ruleList.count(); ++i)
945 {
946 if (url.contains(ruleList.at(i)))
947 {
948 return true;
949 }
950 }
951
952 return false;
953 }
954
isUpdating() const955 bool AdblockContentFiltersProfile::isUpdating() const
956 {
957 return (m_dataFetchJob != nullptr);
958 }
959
960 }
961