File indexing completed on 2024-05-12 16:23:45

0001 // SPDX-FileCopyrightText: 2020 Jonah BrĂ¼chert <jbb@kaidan.im>
0002 //
0003 // SPDX-License-Identifier: GPL-2.0-or-later
0004 
0005 #include "adblockurlinterceptor.h"
0006 
0007 #include <QDebug>
0008 #include <QDir>
0009 #include <QLoggingCategory>
0010 #include <QStandardPaths>
0011 #include <QStringBuilder>
0012 
0013 #include "adblockfilterlistsmanager.h"
0014 
0015 #include "angelfishsettings.h"
0016 
0017 namespace ranges = std::ranges;
0018 
0019 Q_LOGGING_CATEGORY(AdblockCategory, "org.kde.angelfish.adblock", QtWarningMsg);
0020 
0021 #ifdef BUILD_ADBLOCK
0022 template <typename T>
0023 auto toRustType(T input) {
0024     if constexpr (std::is_same_v<T, std::vector<QString>>) {
0025         std::vector<rust::String> rustStringVec;
0026         ranges::transform(input, std::back_inserter(rustStringVec), [](const QString &c) {
0027             return rust::String(c.toStdString());
0028         });
0029         return rustStringVec;
0030     }
0031 }
0032 
0033 template <typename T>
0034 auto toQtType(T input) {
0035     if constexpr (std::is_same_v<T, rust::Vec<rust::String>>) {
0036         std::vector<QString> qStringVec;
0037         ranges::transform(input, std::back_inserter(qStringVec), [](const auto &c) {
0038             return QString::fromStdString(std::string(c));
0039         });
0040         return qStringVec;
0041     } else if constexpr (std::is_same_v<T, rust::String>) {
0042         return QString::fromStdString(std::string(input));
0043     }
0044 }
0045 #endif
0046 
0047 AdblockUrlInterceptor::AdblockUrlInterceptor(QObject *parent)
0048     : QWebEngineUrlRequestInterceptor(parent)
0049 #ifdef BUILD_ADBLOCK
0050     // parsing the block lists takes some time, try to do it asynchronously
0051     // if it is not ready when it's needed, reading the future will block
0052     , m_adblockInitFuture(std::async(std::launch::async, [this] { return createOrRestoreAdblock(); }))
0053     , m_adblock(std::nullopt)
0054 #endif
0055     , m_enabled(AngelfishSettings::adblockEnabled())
0056 {
0057 #ifdef BUILD_ADBLOCK
0058     connect(this, &AdblockUrlInterceptor::adblockInitialized, this, [this] {
0059         if (m_adblockInitFuture.valid()) {
0060             qDebug() << "Adblock ready";
0061             m_adblock = m_adblockInitFuture.get();
0062         }
0063     });
0064 #endif
0065 }
0066 
0067 #ifdef BUILD_ADBLOCK
0068 rust::Box<Adblock> AdblockUrlInterceptor::createOrRestoreAdblock()
0069 {
0070     rust::Box<Adblock> adb = [] {
0071         auto cacheLocation = adblockCacheLocation();
0072         if (QFile::exists(cacheLocation)) {
0073             return loadAdblock(cacheLocation.toStdString());
0074         }
0075         return newAdblock(AdblockFilterListsManager::filterListPath().toStdString());
0076     }();
0077 
0078     Q_EMIT adblockInitialized();
0079     return adb;
0080 }
0081 #endif
0082 
0083 QString AdblockUrlInterceptor::adblockCacheLocation()
0084 {
0085     return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) % u"/adblockCache";
0086 }
0087 
0088 bool AdblockUrlInterceptor::enabled() const
0089 {
0090     return m_enabled;
0091 }
0092 
0093 void AdblockUrlInterceptor::setEnabled(bool enabled)
0094 {
0095     m_enabled = enabled;
0096     AngelfishSettings::setAdblockEnabled(enabled);
0097 }
0098 
0099 AdblockUrlInterceptor &AdblockUrlInterceptor::instance()
0100 {
0101     static AdblockUrlInterceptor instance;
0102 
0103     return instance;
0104 }
0105 
0106 AdblockUrlInterceptor::~AdblockUrlInterceptor()
0107 {
0108 #ifdef BUILD_ADBLOCK
0109     if (m_adblock && (*m_adblock)->isValid() && (*m_adblock)->needsSave()) {
0110         (*m_adblock)->save(adblockCacheLocation().toStdString());
0111     }
0112 #endif
0113 }
0114 
0115 bool AdblockUrlInterceptor::downloadNeeded() const
0116 {
0117     return QDir(AdblockFilterListsManager::filterListPath()).isEmpty();
0118 }
0119 
0120 void AdblockUrlInterceptor::resetAdblock()
0121 {
0122 #ifdef BUILD_ADBLOCK
0123     if (m_adblock) {
0124         m_adblock = std::nullopt;
0125     }
0126     m_adblockInitFuture = std::async(std::launch::async, [this] {
0127         auto adb = newAdblock(AdblockFilterListsManager::filterListPath().toStdString());
0128         Q_EMIT adblockInitialized();
0129         return adb;
0130     });
0131 #endif
0132 }
0133 
0134 #ifdef BUILD_ADBLOCK
0135 std::vector<QString> AdblockUrlInterceptor::getCosmeticFilters(const QUrl &url,
0136                                                                const std::vector<QString> &classes,
0137                                                                const std::vector<QString> &ids) const
0138 {
0139     if (!m_adblock.has_value()) {
0140         return {};
0141     }
0142 
0143     const auto rustClasses = toRustType(classes);
0144     const auto rustIds = toRustType(ids);
0145     return toQtType((*m_adblock)->getCosmeticFilters(url.toString().toStdString(),
0146                                                      {rustClasses.data(), rustClasses.size()},
0147                                                      {rustIds.data(), rustIds.size()}));
0148 }
0149 
0150 QString AdblockUrlInterceptor::getInjectedScript(const QUrl &url) const
0151 {
0152     if (!m_adblock) {
0153         return {};
0154     }
0155 
0156     auto u = (*m_adblock)->getInjectedScript(url.toString().toStdString());
0157     return toQtType(u);
0158 }
0159 #endif
0160 
0161 inline auto resourceTypeToString(const QWebEngineUrlRequestInfo::ResourceType type)
0162 {
0163     // Strings from https://docs.rs/crate/adblock/0.3.3/source/src/request.rs
0164     using Type = QWebEngineUrlRequestInfo::ResourceType;
0165     switch (type) {
0166     case Type::ResourceTypeMainFrame:
0167         return "main_frame";
0168     case Type::ResourceTypeSubFrame:
0169         return "sub_frame";
0170     case Type::ResourceTypeStylesheet:
0171         return "stylesheet";
0172     case Type::ResourceTypeScript:
0173         return "script";
0174     case Type::ResourceTypeFontResource:
0175         return "font";
0176     case Type::ResourceTypeImage:
0177         return "image";
0178     case Type::ResourceTypeSubResource:
0179         return "object_subrequest"; // TODO CHECK
0180     case Type::ResourceTypeObject:
0181         return "object";
0182     case Type::ResourceTypeMedia:
0183         return "media";
0184     case Type::ResourceTypeFavicon:
0185         return "image"; // almost
0186     case Type::ResourceTypeXhr:
0187         return "xhr";
0188     case Type::ResourceTypePing:
0189         return "ping";
0190     case Type::ResourceTypeCspReport:
0191         return "csp_report";
0192     default:
0193         return "other";
0194     }
0195 }
0196 
0197 void AdblockUrlInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info)
0198 {
0199 #ifdef BUILD_ADBLOCK
0200     if (!m_enabled) {
0201         return;
0202     }
0203 
0204     // Only wait for the adblock initialization if it isn't ready on first use
0205     if (!m_adblock) {
0206         qDebug() << "Adblock not yet initialized, blindly allowing request";
0207         return;
0208     }
0209 
0210     const std::string url = info.requestUrl().toString().toStdString();
0211     const std::string firstPartyUrl = info.firstPartyUrl().toString().toStdString();
0212     const AdblockResult result = (*m_adblock)->shouldBlock(url, firstPartyUrl, resourceTypeToString(info.resourceType()));
0213 
0214     const auto &redirect = result.redirect;
0215     const auto &rewrittenUrl = result.rewrittenUrl;
0216     if (!redirect.empty()) {
0217         info.redirect(QUrl(QString::fromStdString(std::string(redirect))));
0218     } else if (result.matched) {
0219         info.block(result.matched);
0220     } else if (!rewrittenUrl.empty()) {
0221         info.redirect(QUrl(QString::fromStdString(std::string(rewrittenUrl))));
0222     }
0223 #else
0224     Q_UNUSED(info);
0225 #endif
0226 }
0227 
0228 void q_cdebug_adblock(const char *message)
0229 {
0230     qCDebug(AdblockCategory) << message;
0231 }
0232 
0233 #include "moc_adblockurlinterceptor.cpp"