File indexing completed on 2025-01-19 04:47:00
0001 /* 0002 SPDX-FileCopyrightText: 2023-2024 Laurent Montel <montel.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "adblockmanager.h" 0008 #include "adblocklistsmanager.h" 0009 0010 #include "globalsettings_webengineurlinterceptoradblock.h" 0011 #include "libadblockplugin_debug.h" 0012 0013 #include <QDir> 0014 #include <QFile> 0015 #include <QNetworkReply> 0016 #include <QNetworkRequest> 0017 #include <QStandardPaths> 0018 0019 [[nodiscard]] QString filterListPath() 0020 { 0021 static const auto path = []() -> QString { 0022 QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/filterlists/"); 0023 QDir(path).mkpath(QStringLiteral(".")); 0024 return path; 0025 }(); 0026 return path; 0027 } 0028 0029 AdblockManager::AdblockManager(QObject *parent) 0030 : QObject{parent} // parsing the block lists takes some time, try to do it asynchronously 0031 // if it is not ready when it's needed, reading the future will block 0032 , mAdblockListManager(new AdblockListsManager(this)) 0033 , mAdblockInitFuture(std::async(std::launch::async, 0034 [this] { 0035 return createOrRestoreAdblock(); 0036 })) 0037 , mAdblock(std::nullopt) 0038 { 0039 reloadConfig(); 0040 0041 connect(&m_networkManager, &QNetworkAccessManager::finished, this, &AdblockManager::handleListFetched); 0042 m_networkManager.setRedirectPolicy(QNetworkRequest::SameOriginRedirectPolicy); 0043 0044 if (QDir(filterListPath()).isEmpty()) { 0045 refreshLists(); 0046 } 0047 } 0048 0049 AdblockManager::~AdblockManager() 0050 { 0051 if (mAdblock && (*mAdblock)->isValid() && (*mAdblock)->needsSave()) { 0052 (*mAdblock)->save(adblockCacheLocation().toStdString()); 0053 } 0054 } 0055 0056 QString AdblockManager::adblockCacheLocation() const 0057 { 0058 return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/adblockCache"); 0059 } 0060 0061 rust::Box<Adblock> AdblockManager::createOrRestoreAdblock() 0062 { 0063 rust::Box<Adblock> adb = [this] { 0064 auto cacheLocation = adblockCacheLocation(); 0065 if (QFile::exists(cacheLocation)) { 0066 return loadAdblock(cacheLocation.toStdString()); 0067 } 0068 return newAdblock(mAdblockListManager->filterListsPath().toStdString()); 0069 }(); 0070 0071 Q_EMIT adblockInitialized(); 0072 return adb; 0073 } 0074 0075 void copyStream(QIODevice &input, QIODevice &output) 0076 { 0077 constexpr auto BUFFER_SIZE = 1024; 0078 0079 QByteArray buffer; 0080 buffer.reserve(BUFFER_SIZE); 0081 0082 while (true) { 0083 int64_t read = input.read(buffer.data(), BUFFER_SIZE); 0084 0085 if (read > 0) { 0086 output.write(buffer.data(), read); 0087 } else { 0088 break; 0089 } 0090 } 0091 } 0092 0093 AdblockManager *AdblockManager::self() 0094 { 0095 static AdblockManager s_self = AdblockManager(); 0096 return &s_self; 0097 } 0098 0099 void AdblockManager::reloadConfig() 0100 { 0101 const bool enabled = AdBlockSettings::self()->adBlockEnabled(); 0102 Q_EMIT enabledChanged(enabled); 0103 0104 mAdblockFilterLists.clear(); 0105 0106 const auto filterUrls = AdBlockSettings::self()->adblockFilterUrls(); 0107 const auto filterNames = AdBlockSettings::self()->adblockFilterNames(); 0108 0109 auto namesIt = filterNames.begin(); 0110 auto urlsIt = filterUrls.begin(); 0111 0112 // Otherwise list is corrupted, but we will still not crash in release mode 0113 Q_ASSERT(filterNames.size() == filterUrls.size()); 0114 0115 while (namesIt != filterNames.end() && urlsIt != filterUrls.end()) { 0116 AdblockFilter filter; 0117 filter.setName(*namesIt); 0118 filter.setUrl(urlsIt->toDisplayString()); 0119 0120 namesIt++; 0121 urlsIt++; 0122 mAdblockFilterLists << filter; 0123 } 0124 } 0125 0126 void AdblockManager::writeConfig() 0127 { 0128 QStringList filterNames; 0129 QList<QUrl> filterUrls; 0130 0131 for (const auto &filterList : std::as_const(mAdblockFilterLists)) { 0132 filterNames.push_back(filterList.name()); 0133 filterUrls.push_back(QUrl(filterList.url())); 0134 } 0135 0136 AdBlockSettings::self()->setAdblockFilterNames(filterNames); 0137 AdBlockSettings::self()->setAdblockFilterUrls(filterUrls); 0138 AdBlockSettings::self()->save(); 0139 } 0140 0141 QString AdblockManager::filterListIdFromUrl(const QString &url) const 0142 { 0143 QCryptographicHash fileNameHash(QCryptographicHash::Sha256); 0144 fileNameHash.addData(url.toUtf8()); 0145 return QString::fromUtf8(fileNameHash.result().toHex()); 0146 } 0147 0148 QString AdblockManager::adblockListText(const QString &url) 0149 { 0150 const auto id = filterListIdFromUrl(url); 0151 0152 QFile file(filterListPath() + id); 0153 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 0154 qCWarning(LIBADBLOCKPLUGIN_PLUGIN_LOG) << "Impossible to open file " << file.fileName(); 0155 return {}; 0156 } 0157 const QString list = QString::fromLatin1(file.readAll()); 0158 return list; 0159 } 0160 0161 void AdblockManager::handleListFetched(QNetworkReply *reply) 0162 { 0163 Q_ASSERT(reply); 0164 0165 m_runningRequests--; 0166 0167 if (m_runningRequests < 1) { 0168 Q_EMIT refreshFinished(); 0169 } 0170 0171 const auto id = filterListIdFromUrl(reply->url().toString()); 0172 0173 QFile file(filterListPath() + id); 0174 if (!file.open(QIODevice::WriteOnly)) { 0175 qCWarning(LIBADBLOCKPLUGIN_PLUGIN_LOG) << "Failed to open" << file.fileName() << "for writing." 0176 << "Filter list not updated"; 0177 return; 0178 } 0179 0180 copyStream(*reply, file); 0181 } 0182 0183 void AdblockManager::refreshLists() 0184 { 0185 // Delete old lists, in case the names change. 0186 // Otherwise we might not be overwriting all of them. 0187 const QDir dir(filterListPath()); 0188 const auto entries = dir.entryList(QDir::Files | QDir::NoDotAndDotDot); 0189 for (const auto &entry : entries) { 0190 const QString path{dir.path() + QDir::separator() + entry}; 0191 if (!QFile::remove(path)) { 0192 qCWarning(LIBADBLOCKPLUGIN_PLUGIN_LOG) << "Impossible to remove file: " << path; 0193 } 0194 } 0195 0196 for (const auto &list : std::as_const(mAdblockFilterLists)) { 0197 m_runningRequests++; 0198 m_networkManager.get(QNetworkRequest(QUrl(list.url()))); 0199 } 0200 } 0201 0202 QList<AdblockFilter> AdblockManager::adblockFilterLists() const 0203 { 0204 return mAdblockFilterLists; 0205 } 0206 0207 void AdblockManager::setAdblockFilterLists(const QList<AdblockFilter> &newAdblockFilterLists) 0208 { 0209 if (mAdblockFilterLists != newAdblockFilterLists) { 0210 mAdblockFilterLists = newAdblockFilterLists; 0211 writeConfig(); 0212 } 0213 } 0214 0215 inline auto resourceTypeToString(const QWebEngineUrlRequestInfo::ResourceType type) 0216 { 0217 // Strings from https://docs.rs/crate/adblock/0.8.1/source/src/request.rs 0218 using Type = QWebEngineUrlRequestInfo::ResourceType; 0219 switch (type) { 0220 case Type::ResourceTypeMainFrame: 0221 return "main_frame"; 0222 case Type::ResourceTypeSubFrame: 0223 return "sub_frame"; 0224 case Type::ResourceTypeStylesheet: 0225 return "stylesheet"; 0226 case Type::ResourceTypeScript: 0227 return "script"; 0228 case Type::ResourceTypeFontResource: 0229 return "font"; 0230 case Type::ResourceTypeImage: 0231 return "image"; 0232 case Type::ResourceTypeSubResource: 0233 return "object_subrequest"; // TODO CHECK 0234 case Type::ResourceTypeObject: 0235 return "object"; 0236 case Type::ResourceTypeMedia: 0237 return "media"; 0238 case Type::ResourceTypeFavicon: 0239 return "image"; // almost 0240 case Type::ResourceTypeXhr: 0241 return "xhr"; 0242 case Type::ResourceTypePing: 0243 return "ping"; 0244 case Type::ResourceTypeCspReport: 0245 return "csp_report"; 0246 default: 0247 return "other"; 0248 } 0249 } 0250 bool AdblockManager::interceptRequest(QWebEngineUrlRequestInfo &info) 0251 { 0252 // Only wait for the adblock initialization if it isn't ready on first use 0253 if (!mAdblock) { 0254 qCDebug(LIBADBLOCKPLUGIN_PLUGIN_LOG) << "Adblock not yet initialized, blindly allowing request"; 0255 return false; 0256 } 0257 0258 const std::string url = info.requestUrl().toString().toStdString(); 0259 const std::string firstPartyUrl = info.firstPartyUrl().toString().toStdString(); 0260 const AdblockResult result = (*mAdblock)->shouldBlock(url, firstPartyUrl, resourceTypeToString(info.resourceType())); 0261 0262 const auto &redirect = result.redirect; 0263 const auto &rewrittenUrl = result.rewrittenUrl; 0264 if (!redirect.empty()) { 0265 info.redirect(QUrl(QString::fromStdString(std::string(redirect)))); 0266 } else if (result.matched) { 0267 info.block(result.matched); 0268 } else if (!rewrittenUrl.empty()) { 0269 info.redirect(QUrl(QString::fromStdString(std::string(rewrittenUrl)))); 0270 } 0271 return true; 0272 } 0273 0274 void q_cdebug_adblock(const char *message) 0275 { 0276 qCDebug(LIBADBLOCKPLUGIN_PLUGIN_LOG) << "AdblockManager message: " << message; 0277 } 0278 0279 #include "moc_adblockmanager.cpp"