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 &currentRule, 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 &currentRule, 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