/* * 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://neueland.iserlohn-fortress.net/gitea/aqua/smolbote * * SPDX-License-Identifier: GPL-3.0 */ #include "urlinterceptor.h" #include "urlfilter/formats/adblockrule.h" #include #include #include #include #include #include inline std::vector parseAdBlockList(const QString &filename) { std::vector rules; QFile list(filename); if(list.open(QIODevice::ReadOnly | QIODevice::Text), true) { QTextStream l(&list); QString line; while(l.readLineInto(&line)) { AdBlockRule rule(line); if(rule.isEnabled()) { rules.emplace_back(std::move(rule)); } } list.close(); } return rules; } UrlRequestInterceptor::UrlRequestInterceptor(const std::unique_ptr &config, QObject *parent) : QWebEngineUrlRequestInterceptor(parent) { QDir hostsD(config->value("filter.path").value()); const QStringList hostFiles = hostsD.entryList(QDir::Files); for(const QString &file : hostFiles) { const QString absPath = hostsD.absoluteFilePath(file); auto r = parse(absPath); #ifdef QT_DEBUG qDebug("Parsed %i rules from %s", r.count(), qUtf8Printable(absPath)); #endif rules.unite(r); } const auto header = config->value>("filter.header"); if(header) { for(const std::string &h : header.value()) { std::vector s; boost::split(s, h, boost::is_any_of(":=")); auto pair = std::make_pair(s.at(0), s.at(1)); m_headers.emplace_back(pair); } } auto filtersPath = config->value("filter.adblock"); if(filtersPath) filters = parseAdBlockList(filtersPath.value()); } // test DNT on https://browserleaks.com/donottrack void UrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info) { for(const Header &header : m_headers) { info.setHttpHeader(QByteArray::fromStdString(header.first), QByteArray::fromStdString(header.second)); } if(rules.contains(info.requestUrl().host())) { info.block(rules.value(info.requestUrl().host()).isBlocking); return; } const uint domainHash = qHash(info.firstPartyUrl().host()); const QWebEngineUrlRequestInfo::ResourceType type = info.resourceType(); const QUrl requestUrl = info.requestUrl(); for(const FilterRule &rule : filters) { if(rule.matchesDomain(domainHash) && rule.matchesType(type) && rule.matchesUrl(requestUrl)) { info.block(rule.isBlocking()); #ifdef QT_DEBUG qDebug("--> blocked %s", qUtf8Printable(info.requestUrl().toString())); #endif break; } } } QHash parse(const QString &filename) { QHash rules; QFile hostfile(filename); if(hostfile.open(QIODevice::ReadOnly | QIODevice::Text)) { // with a QTextStream we can read lines without getting linebreaks at the end QTextStream hostfile_stream(&hostfile); while(!hostfile_stream.atEnd()) { // read line and remove any whitespace at the end const QString &line = hostfile_stream.readLine().trimmed(); // skip comments and empty lines if(line.isEmpty() || line.startsWith('#')) continue; // everything else should be a rule // format is // 0.0.0.0 hostname const QStringList &parts = line.split(' '); const QString &redirect = parts.at(0); for(auto i = parts.constBegin() + 1; i != parts.constEnd(); ++i) { if(!rules.contains(*i)) { UrlRequestInterceptor::HostRule rule{}; rule.isBlocking = (redirect == "0.0.0.0"); rules.insert(*i, rule); } } // for(const QString &host : parts.mid(1)) { // if(!rules.contains(host)) { // UrlRequestInterceptor::HostRule rule{}; // rule.isBlocking = redirect == "0.0.0.0"; // rules.insert(host, rule); // } // } } hostfile.close(); } return rules; }