Falkon Develop
Cross-platform Qt-based web browser
adblockrule.cpp
Go to the documentation of this file.
1/* ============================================================
2* Falkon - Qt web browser
3* Copyright (C) 2010-2017 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* ============================================================ */
47#include "adblockrule.h"
48#include "adblocksubscription.h"
49#include "qztools.h"
50
51#include <QUrl>
52#include <QString>
53#include <QWebEnginePage>
54#include <QWebEngineUrlRequestInfo>
55
56/* TODO Qt6 Replace with PUBLIC API */
57#include <QtCore/private/qurl_p.h>
58#include <QtNetwork/private/qtldurl_p.h>
59
60static QString getTopLevelDomain(const QUrl &url)
61{
62 // QUrl::topLevelDomain() was removed in Qt6.
63 // The following is copied from the old "qTopLevelDomain" code in Qt6::Network.
64 // It was removed in this commit: https://github.com/qt/qtbase/commit/50b30976837be0969efdccced68cfb584d99981a
65 const QString domainLower = url.host().toLower();
66 QVector<QStringView> sections = QStringView{domainLower}.split(QLatin1Char('.'), Qt::SkipEmptyParts);
67 if (sections.isEmpty())
68 return QString();
69
70 QString level, tld;
71 for (int j = sections.count() - 1; j >= 0; --j) {
72 level.prepend(QLatin1Char('.') + sections.at(j));
73 if (qIsEffectiveTLD(QStringView{level}.right(level.size() - 1)))
74 tld = level;
75 }
76
77 //return qt_ACE_do(tld, ToAceOnly, AllowLeadingDot, {});
78 // TODO QT6 - QUrl::toAce() uses ForbidLeadingDot, while the old QUrl::topLevelDomain() used AllowLeadingDot. Does this matter?
79 return QString(QString::fromUtf8(QUrl::toAce(tld)));
80}
81
82static QString toSecondLevelDomain(const QUrl &url)
83{
84 const QString topLevelDomain = getTopLevelDomain(url);
85 const QString urlHost = url.host();
86
87 if (topLevelDomain.isEmpty() || urlHost.isEmpty()) {
88 return {};
89 }
90
91 QString domain = urlHost.left(urlHost.size() - topLevelDomain.size());
92
93 if (domain.count(QL1C('.')) == 0) {
94 return urlHost;
95 }
96
97 while (domain.count(QL1C('.')) != 0) {
98 domain = domain.mid(domain.indexOf(QL1C('.')) + 1);
99 }
100
101 return domain + topLevelDomain;
102}
103
104AdBlockRule::AdBlockRule(const QString &filter, AdBlockSubscription* subscription)
105 : m_subscription(subscription)
106 , m_type(StringContainsMatchRule)
107 , m_caseSensitivity(Qt::CaseInsensitive)
108 , m_isEnabled(true)
109 , m_isException(false)
110 , m_isInternalDisabled(false)
111 , m_regExp(nullptr)
112{
114}
115
117{
118 delete m_regExp;
119}
120
122{
123 auto* rule = new AdBlockRule();
124 rule->m_subscription = m_subscription;
125 rule->m_type = m_type;
126 rule->m_options = m_options;
127 rule->m_exceptions = m_exceptions;
128 rule->m_filter = m_filter;
129 rule->m_matchString = m_matchString;
130 rule->m_caseSensitivity = m_caseSensitivity;
131 rule->m_isEnabled = m_isEnabled;
132 rule->m_isException = m_isException;
133 rule->m_isInternalDisabled = m_isInternalDisabled;
134 rule->m_allowedDomains = m_allowedDomains;
135 rule->m_blockedDomains = m_blockedDomains;
136
137 if (m_regExp) {
138 rule->m_regExp = new RegExp;
139 rule->m_regExp->regExp = m_regExp->regExp;
140 rule->m_regExp->matchers = m_regExp->matchers;
141 }
142
143 return rule;
144}
145
147{
148 return m_subscription;
149}
150
152{
153 m_subscription = subscription;
154}
155
156QString AdBlockRule::filter() const
157{
158 return m_filter;
159}
160
161void AdBlockRule::setFilter(const QString &filter)
162{
163 m_filter = filter;
164 parseFilter();
165}
166
168{
169 return m_type == CssRule;
170}
171
173{
174 return m_matchString;
175}
176
178{
179 return m_type == ExtendedCssRule || m_type == SnippetRule || m_isInternalDisabled;
180}
181
183{
184 return hasOption(DocumentOption);
185}
186
188{
189 return hasOption(ElementHideOption);
190}
191
193{
194 return hasOption(GenericHideOption);
195}
196
198{
199 return hasOption(DomainRestrictedOption);
200}
201
203{
204 return m_isException;
205}
206
208{
209 return m_filter.startsWith(QL1C('!'));
210}
211
213{
214 return m_isEnabled;
215}
216
217void AdBlockRule::setEnabled(bool enabled)
218{
219 m_isEnabled = enabled;
220}
221
223{
224 return m_regExp != nullptr;
225}
226
228{
229 return m_isInternalDisabled;
230}
231
232bool AdBlockRule::urlMatch(const QUrl &url) const
233{
234 if (!hasOption(DocumentOption) && !hasOption(ElementHideOption) && !hasOption(GenericHideOption) && !hasOption(GenericBlockOption)) {
235 return false;
236 }
237
238 const QString encodedUrl = QString::fromUtf8(url.toEncoded());
239 const QString domain = url.host();
240
241 return stringMatch(domain, encodedUrl);
242}
243
244bool AdBlockRule::networkMatch(const QWebEngineUrlRequestInfo &request, const QString &domain, const QString &encodedUrl) const
245{
246 if (m_type == CssRule || !m_isEnabled || m_isInternalDisabled) {
247 return false;
248 }
249
250 bool matched = stringMatch(domain, encodedUrl);
251
252 if (matched) {
253 // Check domain restrictions
254 if (hasOption(DomainRestrictedOption) && !matchDomain(request.firstPartyUrl().host())) {
255 return false;
256 }
257
258 // Check third-party restriction
259 if (hasOption(ThirdPartyOption) && !matchThirdParty(request)) {
260 return false;
261 }
262
263 // Check type restrictions
264 if (((m_exceptions | m_options) & TypeOptions) && !matchType(request))
265 return false;
266 }
267
268 return matched;
269}
270
271bool AdBlockRule::matchDomain(const QString &domain) const
272{
273 if (!m_isEnabled) {
274 return false;
275 }
276
277 if (!hasOption(DomainRestrictedOption)) {
278 return true;
279 }
280
281 if (m_blockedDomains.isEmpty()) {
282 for (const QString &d : std::as_const(m_allowedDomains)) {
283 if (isMatchingDomain(domain, d)) {
284 return true;
285 }
286 }
287 }
288 else if (m_allowedDomains.isEmpty()) {
289 for (const QString &d : std::as_const(m_blockedDomains)) {
290 if (isMatchingDomain(domain, d)) {
291 return false;
292 }
293 }
294 return true;
295 }
296 else {
297 for (const QString &d : std::as_const(m_blockedDomains)) {
298 if (isMatchingDomain(domain, d)) {
299 return false;
300 }
301 }
302
303 for (const QString &d : std::as_const(m_allowedDomains)) {
304 if (isMatchingDomain(domain, d)) {
305 return true;
306 }
307 }
308 }
309
310 return false;
311}
312
313bool AdBlockRule::matchThirdParty(const QWebEngineUrlRequestInfo &request) const
314{
315 // Third-party matching should be performed on second-level domains
316 const QString firstPartyHost = toSecondLevelDomain(request.firstPartyUrl());
317 const QString host = toSecondLevelDomain(request.requestUrl());
318
319 bool match = firstPartyHost != host;
320
321 return hasException(ThirdPartyOption) ? !match : match;
322}
323
324bool AdBlockRule::matchType(const QWebEngineUrlRequestInfo &request) const
325{
326 RuleOption type;
327 switch (request.resourceType()) {
328 case QWebEngineUrlRequestInfo::ResourceTypeMainFrame:
329 type = DocumentOption;
330 break;
331 case QWebEngineUrlRequestInfo::ResourceTypeSubFrame:
332 type = SubdocumentOption;
333 break;
334 case QWebEngineUrlRequestInfo::ResourceTypeStylesheet:
335 type = StyleSheetOption;
336 break;
337 case QWebEngineUrlRequestInfo::ResourceTypeScript:
338 type = ScriptOption;
339 break;
340 case QWebEngineUrlRequestInfo::ResourceTypeImage:
341 type = ImageOption;
342 break;
343 case QWebEngineUrlRequestInfo::ResourceTypeFontResource:
344 type = FontOption;
345 break;
346 case QWebEngineUrlRequestInfo::ResourceTypeObject:
347 type = ObjectOption;
348 break;
349 case QWebEngineUrlRequestInfo::ResourceTypeMedia:
350 type = MediaOption;
351 break;
352 case QWebEngineUrlRequestInfo::ResourceTypeXhr:
353 type = XMLHttpRequestOption;
354 break;
355 case QWebEngineUrlRequestInfo::ResourceTypePing:
356 type = PingOption;
357 break;
358 case QWebEngineUrlRequestInfo::ResourceTypePluginResource:
359 type = ObjectSubrequestOption;
360 break;
361 case QWebEngineUrlRequestInfo::ResourceTypeSubResource:
362 case QWebEngineUrlRequestInfo::ResourceTypeWorker:
363 case QWebEngineUrlRequestInfo::ResourceTypeSharedWorker:
364 case QWebEngineUrlRequestInfo::ResourceTypePrefetch:
365 case QWebEngineUrlRequestInfo::ResourceTypeFavicon:
366 case QWebEngineUrlRequestInfo::ResourceTypeServiceWorker:
367 case QWebEngineUrlRequestInfo::ResourceTypeCspReport:
368 case QWebEngineUrlRequestInfo::ResourceTypeNavigationPreloadMainFrame:
369 case QWebEngineUrlRequestInfo::ResourceTypeNavigationPreloadSubFrame:
370 case QWebEngineUrlRequestInfo::ResourceTypeUnknown:
371 default:
372 type = OtherOption;
373 break;
374 }
375 if (!m_exceptions)
376 return m_options.testFlag(type);
377 return !m_exceptions.testFlag(type);
378}
379
380void AdBlockRule::parseFilter()
381{
382 QString parsedLine = m_filter;
383
384 // Empty rule or just comment
385 if (m_filter.trimmed().isEmpty() || m_filter.startsWith(QL1C('!'))) {
386 // We want to differentiate rule disabled by user and rule disabled in subscription file
387 // m_isInternalDisabled is also used when rule is disabled due to all options not being supported
388 m_isEnabled = false;
389 m_isInternalDisabled = true;
390 m_type = Invalid;
391 return;
392 }
393
394 // Exception always starts with @@
395 if (parsedLine.startsWith(QL1S("@@"))) {
396 m_isException = true;
397 parsedLine.remove(0, 2);
398 }
399
400 // Extended CSS element hiding
401 if (parsedLine.contains(QL1S("#?#"))) {
402 m_type = ExtendedCssRule;
403 int pos = parsedLine.indexOf(QL1C('#'));
404 if (!parsedLine.startsWith(QL1S("#"))) {
405 QString domains = parsedLine.left(pos);
406 parseDomains(domains, QL1C(','));
407 }
408 m_matchString = parsedLine.mid(pos + 3);
409 // CSS rule cannot have more options -> stop parsing
410 return;
411 }
412
413 // Snippet rule
414 if (parsedLine.contains(QL1S("#$#"))) {
415 m_type = SnippetRule;
416 int pos = parsedLine.indexOf(QL1C('#'));
417 if (!parsedLine.startsWith(QL1S("#"))) {
418 QString domains = parsedLine.left(pos);
419 parseDomains(domains, QL1C(','));
420 }
421 m_matchString = parsedLine.mid(pos + 3);
422 return;
423 }
424
425 // CSS Element hiding rule
426 if (parsedLine.contains(QL1S("##")) || parsedLine.contains(QL1S("#@#"))) {
427 m_type = CssRule;
428 int pos = parsedLine.indexOf(QL1C('#'));
429
430 // Domain restricted rule
431 if (!parsedLine.startsWith(QL1S("#"))) {
432 QString domains = parsedLine.left(pos);
433 parseDomains(domains, QL1C(','));
434 }
435
436 m_isException = parsedLine.at(pos + 1) == QL1C('@');
437 m_matchString = parsedLine.mid(m_isException ? pos + 3 : pos + 2);
438
439 // CSS rule cannot have more options -> stop parsing
440 return;
441 }
442
443 // Parse all options following $ char
444 int optionsIndex = parsedLine.indexOf(QL1C('$'));
445 if (optionsIndex >= 0) {
446 const QStringList options = parsedLine.mid(optionsIndex + 1).split(QL1C(','), Qt::SkipEmptyParts);
447
448 int handledOptions = 0;
449 for (const QString &option : options) {
450 if (option.startsWith(QL1S("domain="))) {
451 parseDomains(option.mid(7), QL1C('|'));
452 ++handledOptions;
453 }
454 else if (option == QL1S("match-case")) {
455 m_caseSensitivity = Qt::CaseSensitive;
456 ++handledOptions;
457 }
458 else if (option.endsWith(QL1S("third-party"))) {
459 setOption(ThirdPartyOption);
460 setException(ThirdPartyOption, option.startsWith(QL1C('~')));
461 ++handledOptions;
462 }
463 else if (option.endsWith(QL1S("object"))) {
464 setOption(ObjectOption);
465 setException(ObjectOption, option.startsWith(QL1C('~')));
466 ++handledOptions;
467 }
468 else if (option.endsWith(QL1S("subdocument"))) {
469 setOption(SubdocumentOption);
470 setException(SubdocumentOption, option.startsWith(QL1C('~')));
471 ++handledOptions;
472 }
473 else if (option.endsWith(QL1S("xmlhttprequest"))) {
474 setOption(XMLHttpRequestOption);
475 setException(XMLHttpRequestOption, option.startsWith(QL1C('~')));
476 ++handledOptions;
477 }
478 else if (option.endsWith(QL1S("image"))) {
479 setOption(ImageOption);
480 setException(ImageOption, option.startsWith(QL1C('~')));
481 ++handledOptions;
482 }
483 else if (option.endsWith(QL1S("script"))) {
484 setOption(ScriptOption);
485 setException(ScriptOption, option.startsWith(QL1C('~')));
486 ++handledOptions;
487 }
488 else if (option.endsWith(QL1S("stylesheet"))) {
489 setOption(StyleSheetOption);
490 setException(StyleSheetOption, option.startsWith(QL1C('~')));
491 ++handledOptions;
492 }
493 else if (option.endsWith(QL1S("object-subrequest"))) {
494 setOption(ObjectSubrequestOption);
495 setException(ObjectSubrequestOption, option.startsWith(QL1C('~')));
496 ++handledOptions;
497 }
498 else if (option.endsWith(QL1S("ping"))) {
499 setOption(PingOption);
500 setException(PingOption, option.startsWith(QL1C('~')));
501 ++handledOptions;
502 }
503 else if (option.endsWith(QL1S("media"))) {
504 setOption(MediaOption);
505 setException(MediaOption, option.startsWith(QL1C('~')));
506 ++handledOptions;
507 }
508 else if (option.endsWith(QL1S("font"))) {
509 setOption(FontOption);
510 setException(FontOption, option.startsWith(QL1C('~')));
511 ++handledOptions;
512 }
513 else if (option.endsWith(QL1S("other"))) {
514 setOption(OtherOption);
515 setException(OtherOption, option.startsWith(QL1C('~')));
516 ++handledOptions;
517 }
518 else if (option == QL1S("collapse")) {
519 // Hiding placeholders of blocked elements is enabled by default
520 ++handledOptions;
521 }
522 else if (option == QL1S("popup")) {
523 // doesn't do anything yet
524 setOption(PopupOption);
525 ++handledOptions;
526 }
527 else if (option == QL1S("document") && m_isException) {
528 setOption(DocumentOption);
529 ++handledOptions;
530 }
531 else if (option == QL1S("elemhide") && m_isException) {
532 setOption(ElementHideOption);
533 ++handledOptions;
534 }
535 else if (option == QL1S("generichide") && m_isException) {
536 setOption(GenericHideOption);
537 ++handledOptions;
538 }
539 else if (option == QL1S("genericblock") && m_isException) {
540 // doesn't do anything yet
541 setOption(GenericBlockOption);
542// ++handledOptions;
543 }
544 }
545
546 // If we don't handle all options, it's safer to just disable this rule
547 if (handledOptions != options.count()) {
548 m_isInternalDisabled = true;
549 m_type = Invalid;
550 return;
551 }
552
553 parsedLine.truncate(optionsIndex);
554 }
555
556 // Rule is classic regexp
557 if (parsedLine.startsWith(QL1C('/')) && parsedLine.endsWith(QL1C('/'))) {
558 parsedLine.remove(0, 1);
559 parsedLine = parsedLine.left(parsedLine.size() - 1);
560
561 m_type = RegExpMatchRule;
562 m_regExp = new RegExp;
563 m_regExp->regExp = QRegularExpression(parsedLine, QRegularExpression::InvertedGreedinessOption);
564 if (m_caseSensitivity == Qt::CaseInsensitive) {
565 m_regExp->regExp.setPatternOptions(m_regExp->regExp.patternOptions() | QRegularExpression::CaseInsensitiveOption);
566 }
567 m_regExp->matchers = createStringMatchers(parseRegExpFilter(parsedLine));
568 return;
569 }
570
571 // Remove starting and ending wildcards (*)
572 if (parsedLine.startsWith(QL1C('*'))) {
573 parsedLine.remove(0, 1);
574 }
575
576 if (parsedLine.endsWith(QL1C('*'))) {
577 parsedLine = parsedLine.left(parsedLine.size() - 1);
578 }
579
580 // We can use fast string matching for domain here
581 if (filterIsOnlyDomain(parsedLine)) {
582 parsedLine.remove(0, 2);
583 parsedLine = parsedLine.left(parsedLine.size() - 1);
584
585 m_type = DomainMatchRule;
586 m_matchString = parsedLine;
587 return;
588 }
589
590 // If rule contains only | at end, we can also use string matching
591 if (filterIsOnlyEndsMatch(parsedLine)) {
592 parsedLine = parsedLine.left(parsedLine.size() - 1);
593
594 m_type = StringEndsMatchRule;
595 m_matchString = parsedLine;
596 return;
597 }
598
599 // If we still find a wildcard (*) or separator (^) or (|)
600 // we must modify parsedLine to comply with QRegularExpression
601 if (parsedLine.contains(QL1C('*')) ||
602 parsedLine.contains(QL1C('^')) ||
603 parsedLine.contains(QL1C('|'))
604 ) {
605 m_type = RegExpMatchRule;
606 m_regExp = new RegExp;
607 m_regExp->regExp = QRegularExpression(createRegExpFromFilter(parsedLine), QRegularExpression::InvertedGreedinessOption);
608 if (m_caseSensitivity == Qt::CaseInsensitive) {
609 m_regExp->regExp.setPatternOptions(m_regExp->regExp.patternOptions() | QRegularExpression::CaseInsensitiveOption);
610 }
611 m_regExp->matchers = createStringMatchers(parseRegExpFilter(parsedLine));
612 return;
613 }
614
615 // This rule matches all urls
616 if (parsedLine.isEmpty()) {
617 if (m_options == NoOption) {
618 qWarning() << "Disabling unrestricted rule that would block all requests" << m_filter;
619 m_isInternalDisabled = true;
620 m_type = Invalid;
621 return;
622 }
623 m_type = MatchAllUrlsRule;
624 return;
625 }
626
627 // We haven't found anything that needs use of regexp, yay!
628 m_type = StringContainsMatchRule;
629 m_matchString = parsedLine;
630}
631
632void AdBlockRule::parseDomains(const QString &domains, const QChar &separator)
633{
634 const QStringList domainsList = domains.split(separator, Qt::SkipEmptyParts);
635
636 for (const QString &domain : domainsList) {
637 if (domain.isEmpty()) {
638 continue;
639 }
640 if (domain.startsWith(QL1C('~'))) {
641 m_blockedDomains.append(domain.mid(1));
642 }
643 else {
644 m_allowedDomains.append(domain);
645 }
646 }
647
648 if (!m_blockedDomains.isEmpty() || !m_allowedDomains.isEmpty()) {
649 setOption(DomainRestrictedOption);
650 }
651}
652
653bool AdBlockRule::filterIsOnlyDomain(const QString &filter) const
654{
655 if (!filter.endsWith(QL1C('^')) || !filter.startsWith(QL1S("||")))
656 return false;
657
658 for (int i = 0; i < filter.size(); ++i) {
659 switch (filter.at(i).toLatin1()) {
660 case '/':
661 case ':':
662 case '?':
663 case '=':
664 case '&':
665 case '*':
666 return false;
667 default:
668 break;
669 }
670 }
671
672 return true;
673}
674
675bool AdBlockRule::filterIsOnlyEndsMatch(const QString &filter) const
676{
677 for (int i = 0; i < filter.size(); ++i) {
678 switch (filter.at(i).toLatin1()) {
679 case '^':
680 case '*':
681 return false;
682 case '|':
683 return i == filter.size() - 1;
684 default:
685 break;
686 }
687 }
688
689 return false;
690}
691
692static bool wordCharacter(const QChar &c)
693{
694 return c.isLetterOrNumber() || c.isMark() || c == QL1C('_');
695}
696
697QString AdBlockRule::createRegExpFromFilter(const QString &filter) const
698{
699 QString parsed;
700 parsed.reserve(filter.size());
701
702 bool hadWildcard = false; // Filter multiple wildcards
703
704 for (int i = 0; i < filter.size(); ++i) {
705 const QChar c = filter.at(i);
706 switch (c.toLatin1()) {
707 case '^':
708 parsed.append(QL1S("(?:[^\\w\\d\\-.%]|$)"));
709 break;
710
711 case '*':
712 if (!hadWildcard)
713 parsed.append(QL1S(".*"));
714 break;
715
716 case '|':
717 if (i == 0) {
718 if (filter.size() > 1 && filter.at(1) == QL1C('|')) {
719 parsed.append(QL1S("^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?"));
720 i++;
721 }
722 else {
723 parsed.append(QL1C('^'));
724 }
725 break;
726 }
727 else if (i == filter.size() - 1) {
728 parsed.append(QL1C('$'));
729 break;
730 }
731 // fallthrough
732
733 default:
734 if (!wordCharacter(c))
735 parsed.append(QL1C('\\') + c);
736 else
737 parsed.append(c);
738 }
739
740 hadWildcard = c == QL1C('*');
741 }
742
743 return parsed;
744}
745
746QList<QStringMatcher> AdBlockRule::createStringMatchers(const QStringList &filters) const
747{
748 QList<QStringMatcher> matchers;
749 matchers.reserve(filters.size());
750
751 for (const QString &filter : filters) {
752 matchers.append(QStringMatcher(filter, m_caseSensitivity));
753 }
754
755 return matchers;
756}
757
758bool AdBlockRule::stringMatch(const QString &domain, const QString &encodedUrl) const
759{
760 switch (m_type) {
761 case StringContainsMatchRule:
762 return encodedUrl.contains(m_matchString, m_caseSensitivity);
763
764 case DomainMatchRule:
765 return isMatchingDomain(domain, m_matchString);
766
767 case StringEndsMatchRule:
768 return encodedUrl.endsWith(m_matchString, m_caseSensitivity);
769
770 case RegExpMatchRule:
771 if (!isMatchingRegExpStrings(encodedUrl)) {
772 return false;
773 }
774 return m_regExp->regExp.match(encodedUrl).hasMatch();
775
776 case MatchAllUrlsRule:
777 return true;
778
779 default:
780 return false;
781 }
782}
783
784bool AdBlockRule::isMatchingDomain(const QString &domain, const QString &filter) const
785{
786 return QzTools::matchDomain(filter, domain);
787}
788
789bool AdBlockRule::isMatchingRegExpStrings(const QString &url) const
790{
791 Q_ASSERT(m_regExp);
792
793 const auto matchers = m_regExp->matchers;
794 for (const QStringMatcher &matcher : matchers) {
795 if (matcher.indexIn(url) == -1)
796 return false;
797 }
798
799 return true;
800}
801
802// Split regexp filter into strings that can be used with QString::contains
803// Don't use parts that contains only 1 char and duplicated parts
804QStringList AdBlockRule::parseRegExpFilter(const QString &filter) const
805{
806 QStringList list;
807 int startPos = -1;
808
809 for (int i = 0; i < filter.size(); ++i) {
810 const QChar c = filter.at(i);
811 // Meta characters in AdBlock rules are | * ^
812 if (c == QL1C('|') || c == QL1C('*') || c == QL1C('^')) {
813 const QString sub = filter.mid(startPos, i - startPos);
814 if (sub.size() > 1)
815 list.append(sub);
816 startPos = i + 1;
817 }
818 }
819
820 const QString sub = filter.mid(startPos);
821 if (sub.size() > 1)
822 list.append(sub);
823
824 list.removeDuplicates();
825
826 return list;
827}
828
829bool AdBlockRule::hasOption(const AdBlockRule::RuleOption &opt) const
830{
831 return (m_options & opt);
832}
833
834bool AdBlockRule::hasException(const AdBlockRule::RuleOption &opt) const
835{
836 return (m_exceptions & opt);
837}
838
839void AdBlockRule::setOption(const AdBlockRule::RuleOption &opt)
840{
841 m_options |= opt;
842}
843
844void AdBlockRule::setException(const AdBlockRule::RuleOption &opt, bool on)
845{
846 if (on) {
847 m_exceptions |= opt;
848 }
849}
QString filter() const
bool isDomainRestricted() const
bool isGenerichide() const
bool isMatchingRegExpStrings(const QString &url) const
AdBlockSubscription * subscription() const
bool isMatchingDomain(const QString &domain, const QString &filter) const
bool matchDomain(const QString &domain) const
bool isEnabled() const
QString cssSelector() const
bool isException() const
QStringList parseRegExpFilter(const QString &filter) const
AdBlockRule(const QString &filter=QString(), AdBlockSubscription *subscription=nullptr)
bool isInternalDisabled() const
bool isElemhide() const
bool isUnsupportedRule() const
bool stringMatch(const QString &domain, const QString &encodedUrl) const
bool networkMatch(const QWebEngineUrlRequestInfo &request, const QString &domain, const QString &encodedUrl) const
bool urlMatch(const QUrl &url) const
bool matchType(const QWebEngineUrlRequestInfo &request) const
AdBlockRule * copy() const
void setEnabled(bool enabled)
bool isCssRule() const
void setFilter(const QString &filter)
void setSubscription(AdBlockSubscription *subscription)
bool isSlow() const
bool matchThirdParty(const QWebEngineUrlRequestInfo &request) const
bool isComment() const
bool isDocument() const
static bool matchDomain(const QString &pattern, const QString &domain)
Definition: qztools.cpp:737
i
Definition: i18n.py:23
#define QL1S(x)
Definition: qzcommon.h:44
#define QL1C(x)
Definition: qzcommon.h:48