/* * 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: git://neueland.iserlohn-fortress.net/smolbote.git * * SPDX-License-Identifier: GPL-3.0 */ #include "blockerrule.h" /* AdBlock filter reference * https://adblockplus.org/en/filters * https://adblockplus.org/en/filter-cheatsheet */ BlockerRule::BlockerRule(QString rule, QObject *parent) : QObject(parent) { m_filter = rule; QString pattern = rule; // Empty rule or comment if(pattern.trimmed().isEmpty() || pattern.startsWith("!")) { m_valid = false; return; } // Exception if(pattern.startsWith("@@")) { m_exception = true; pattern.remove(0, 2); } // Ignore element hiding rules for now if(pattern.contains("##") || pattern.contains("#@#")) { m_valid = false; return; } // Options if(pattern.contains("$")) { QString opts = pattern.mid(pattern.indexOf("$")+1); pattern.remove(pattern.indexOf("$"), pattern.length()); const QStringList optList = opts.split(','); QStringList::const_iterator i; for(i = optList.constBegin(); i != optList.constEnd(); ++i) { parseOption(*i); } } // Regular expression if(rule.startsWith("/") && rule.endsWith("/")) { m_valid = true; ruleExpression.setPattern(pattern); return; } // Domain rules if(pattern.startsWith("||")) { pattern.remove(0, 2); // find the end point for the domain int end = pattern.indexOf(QRegularExpression("(?:[^\\w\\d\\_\\-\\.\\%]|$)"), 0); domainExpression.setPattern(pattern.mid(0, end)); pattern.remove(0, end+1); } else if(pattern.startsWith("|") && pattern.endsWith("|")) { pattern.remove(0, 1); pattern.chop(1); domainExpression.setPattern(pattern); } else { domainExpression.setPattern(".*"); } // Regular rule ruleExpression.setWildcardPattern(pattern); m_valid = true; } bool BlockerRule::match(const QWebEngineUrlRequestInfo &info) { if(!m_valid) { return false; } // if both domain and rule match if(domainExpression.match(info.requestUrl().host()) && ruleExpression.match(info.requestUrl().toString())) { // option explicitly allows if(matchOptions(info, m_whitelistOptions)) { return false; } // option explicitly bans if(matchOptions(info, m_blacklistOptions)) { return true; } // no options, but both domain and rule match --> rule matches return true; } // domain and/or rule do not match return false; } bool BlockerRule::isValid() { return m_valid; } bool BlockerRule::isException() { return m_exception; } QString BlockerRule::filter() const { return m_filter; } void BlockerRule::parseOption(const QString &opt) { if(opt.startsWith("script")) { m_blacklistOptions.setFlag(RuleOption::script, true); } else if(opt.startsWith("~script")) { m_whitelistOptions.setFlag(RuleOption::script, true); } else if(opt.startsWith("image")) { m_blacklistOptions.setFlag(RuleOption::image, true); } else if(opt.startsWith("~image")) { m_whitelistOptions.setFlag(RuleOption::image, true); } else if(opt.startsWith("stylesheet")) { m_blacklistOptions.setFlag(RuleOption::stylesheet, true); } else if(opt.startsWith("~stylesheet")) { m_whitelistOptions.setFlag(RuleOption::stylesheet, true); } else if(opt.startsWith("object")) { m_blacklistOptions.setFlag(RuleOption::object, true); } else if(opt.startsWith("~object")) { m_whitelistOptions.setFlag(RuleOption::object, true); } else if(opt.startsWith("object-subrequest")) { m_blacklistOptions.setFlag(RuleOption::objectsubrequest, true); } else if(opt.startsWith("~object-subrequest")) { m_whitelistOptions.setFlag(RuleOption::objectsubrequest, true); } else if(opt.startsWith("subdocument")) { m_blacklistOptions.setFlag(RuleOption::subdocument, true); } else if(opt.startsWith("~subdocument")) { m_whitelistOptions.setFlag(RuleOption::subdocument, true); } } bool BlockerRule::matchOptions(const QWebEngineUrlRequestInfo &info, const RuleOptions &options) { // no options are defined if(options == 0) { return false; } bool hasOption = false; switch (info.resourceType()) { case QWebEngineUrlRequestInfo::ResourceTypeScript: if(options.testFlag(RuleOption::script)) { hasOption = true; } break; case QWebEngineUrlRequestInfo::ResourceTypeImage: if(options.testFlag(RuleOption::image)) { hasOption = true; } break; case QWebEngineUrlRequestInfo::ResourceTypeStylesheet: if(options.testFlag(RuleOption::stylesheet)) { hasOption = true; } break; case QWebEngineUrlRequestInfo::ResourceTypeObject: if(options.testFlag(RuleOption::object)) { hasOption = true; } break; case QWebEngineUrlRequestInfo::ResourceTypePluginResource: if(options.testFlag(RuleOption::objectsubrequest)) { hasOption = true; } break; case QWebEngineUrlRequestInfo::ResourceTypeSubFrame: if(options.testFlag(RuleOption::subdocument)) { hasOption = true; } break; default: break; } return hasOption; }