/* * 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 */ #include "filterlist.h" #include "rule.h" #include #include /** * Documentation: * * https://adblockplus.org/filter-cheatsheet * https://help.eyeo.com/adblockplus/how-to-write-filters * * https://github.com/gorhill/uBlock/wiki/Introduction-to-basic-filtering-syntax * https://github.com/gorhill/uBlock/wiki/Static-filter-syntax * */ const QLatin1String comment_lastModified("! Last modified: "); const QLatin1String comment_expires("! Expires: "); using namespace AdblockPlus; FilterList::~FilterList() { qDeleteAll(m_rules); } FilterList::ParseResult FilterList::parse(QTextStream &stream) { FilterList::ParseResult result; if(stream.readLine().trimmed() != "[Adblock Plus 2.0]") { result.state = FilterList::InvalidFormat; return result; } QString line; while(stream.readLineInto(&line)) { if(!line.isEmpty()) { ++result.lines_total; if(line.startsWith('!')) { ++result.lines_comments; parseComment(line); } else if(line.contains("##") || line.contains("#@#")) { // ## is element hiding rule // #@# is element hiding exception rule if(qgetenv("PRINT_IGNORED") == "1") qDebug("ignored: >%s<", qUtf8Printable(line)); ++result.lines_ignored; } else { if(parseRule(line)) ++result.lines_parsed; else { if(qgetenv("PRINT_FAILED") == "1") qDebug("failed: >%s<", qUtf8Printable(line)); ++result.lines_failed; } } } } result.state = FilterList::Ok; return result; } void FilterList::parseComment(QString &line) { if(line.startsWith(comment_lastModified)) { lastModified = QDateTime::fromString(line.mid(comment_lastModified.size()), "dd MMM yyyy HH:mm 'UTC'"); expires = lastModified; } else if(line.startsWith(comment_expires)) { const QRegularExpression time_re("(?:(\\d+) days)|(?:(\\d+) hours)"); const auto match = time_re.match(line); if(match.hasMatch()) { expires = expires.addDays(match.captured(1).toInt()); expires = expires.addSecs(match.captured(2).toInt() * 60 * 60); } } } bool FilterList::parseRule(const QString &line) { QString pattern = line; Options opt; if(pattern.startsWith(QLatin1String("@@"))) { pattern.remove(0, 2); opt.exception = true; } // parse options if(pattern.contains('$')) { const auto list = pattern.split('$'); pattern = list.at(0); const auto options = list.at(1).split(','); for(const auto &option : options) { if(!opt.set(option)) return false; } } if(pattern.startsWith("||") && pattern.endsWith("^")) { // domain match pattern = pattern.mid(2, pattern.length() - 3); m_rules.append(new MatcherRule(pattern, opt)); } else if(pattern.startsWith("|") && pattern.endsWith("|")) { // string equals pattern = pattern.mid(1, pattern.length() - 2); m_rules.append(new MatcherRule(pattern, opt)); } else if(pattern.startsWith("||")) { // string starts with pattern = pattern.mid(2, pattern.length() - 2); m_rules.append(new MatcherRule(pattern, opt)); } else if(pattern.endsWith("|")) { // string ends with pattern = pattern.mid(0, pattern.length() - 1); m_rules.append(new MatcherRule(pattern, opt)); } else if(pattern.startsWith("/") && pattern.endsWith("/")) { // regular expression pattern = pattern.mid(1, pattern.length() - 2); m_rules.append(new RegexRule(pattern, opt)); } else { // wildcard pattern pattern = QRegularExpression::wildcardToRegularExpression(pattern); m_rules.append(new RegexRule(pattern, opt)); } return true; } void FilterList::filter(QWebEngineUrlRequestInfo &info) const { } bool FilterList::isUpToDate() const { const auto current = QDateTime::currentDateTime(); return expires > current; }