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"