/* * This file is part of smolbote. It's copyrighted by the contributors recorded * in the version control history of the file, available from its original * location: https://library.iserlohn-fortress.net/aqua/smolbote.git * * SPDX-License-Identifier: GPL-3.0 */ #ifndef SMOLBOTE_ADBLOCK_RULE_H #define SMOLBOTE_ADBLOCK_RULE_H #include "options.h" #include #include #include #include namespace AdblockPlus { class Rule { public: virtual ~Rule() = default; /** * requestUrl: requested URL * initiatorUrl: URL of document that initiated navigation * firstPartyUrl: URL of the page that issued the request */ virtual bool hasMatch(const QStringRef &requestUrl, const QStringRef &initiatorUrl, const QStringRef &firstPartyUrl, QWebEngineUrlRequestInfo::ResourceType resourceType = QWebEngineUrlRequestInfo::ResourceTypeMainFrame) const = 0; bool shouldRedirect() const { return options.redirect; } bool shouldBlock() const { return !options.exception; } protected: Rule(const Options &opt) : options(opt) { } const Options options; }; // The separator character can be anything but // a letter, a digit, or one of the following: _, -, ., %. // The end of the address is also accepted as a separator. inline bool isSeparator(const QChar &c) { return !c.isLetter() && !c.isDigit() && c != '_' && c != '-' && c != '.' && c != '%'; } class MatcherRule : public Rule { public: enum MatchPosition { DomainMatch, UrlStartsWith, UrlEndsWith, UrlContains, UrlEquals }; MatcherRule(const QString &pattern, const Options &opt, const MatchPosition pos = UrlContains) : Rule(opt) , position(pos) , matcher(pattern, opt.matchcase ? Qt::CaseSensitive : Qt::CaseInsensitive) , patternLength(pattern.length()) { } explicit MatcherRule(const MatcherRule &) = delete; MatcherRule &operator=(const MatcherRule &) = delete; explicit MatcherRule(MatcherRule &&) = delete; MatcherRule &operator=(MatcherRule &&) = delete; ~MatcherRule() = default; bool hasMatch(const QStringRef &url, const QStringRef &initiatorUrl, const QStringRef &firstPartyUrl, QWebEngineUrlRequestInfo::ResourceType resourceType = QWebEngineUrlRequestInfo::ResourceTypeMainFrame) const override { const auto index = matcher.indexIn(url); if(index == -1) { return false; } switch(position) { case DomainMatch: // match if // there is only one : left of the index // and // character after pattern is separator or end of string return (url.left(index).count(':') <= 1 && (index + patternLength == url.length() || isSeparator(url[index + patternLength]))); case UrlStartsWith: return (index == 0); case UrlEndsWith: return (index == url.length() - patternLength); case UrlContains: return (index != -1); case UrlEquals: return (index == 0 && patternLength == url.length()); } return false; } private: const MatchPosition position; const QStringMatcher matcher; const int patternLength; }; class RegexRule : public Rule { public: RegexRule(const QString &rule, const Options &opt) : Rule(opt) , regex(rule) { if(!opt.matchcase) { regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption); } } explicit RegexRule(const RegexRule &) = delete; RegexRule &operator=(const RegexRule &) = delete; explicit RegexRule(RegexRule &&) = delete; RegexRule &operator=(RegexRule &&) = delete; ~RegexRule() = default; bool hasMatch(const QStringRef &url, const QStringRef &initiatorUrl, const QStringRef &firstPartyUrl, QWebEngineUrlRequestInfo::ResourceType resourceType = QWebEngineUrlRequestInfo::ResourceTypeMainFrame) const override { const auto match = regex.match(url); return match.hasMatch(); } private: QRegularExpression regex; }; } // namespace AdblockPlus #endif // SMOLBOTE_ADBLOCK_RULE_H