389 lines
13 KiB
C++
389 lines
13 KiB
C++
// For license of this file, see <project-root-folder>/LICENSE.md.
|
|
|
|
#include "network-web/adblock/adblockmanager.h"
|
|
|
|
#include "exceptions/applicationexception.h"
|
|
#include "exceptions/networkexception.h"
|
|
#include "miscellaneous/application.h"
|
|
#include "miscellaneous/settings.h"
|
|
#include "network-web/adblock/adblockdialog.h"
|
|
#include "network-web/adblock/adblockicon.h"
|
|
#include "network-web/adblock/adblockrequestinfo.h"
|
|
#include "network-web/adblock/adblockurlinterceptor.h"
|
|
#include "network-web/networkfactory.h"
|
|
#include "network-web/networkurlinterceptor.h"
|
|
#include "network-web/webfactory.h"
|
|
|
|
#include <QDateTime>
|
|
#include <QDir>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QMessageBox>
|
|
#include <QProcess>
|
|
#include <QTimer>
|
|
#include <QUrlQuery>
|
|
#include <QWebEngineProfile>
|
|
|
|
AdBlockManager::AdBlockManager(QObject* parent)
|
|
: QObject(parent), m_loaded(false), m_enabled(false), m_interceptor(new AdBlockUrlInterceptor(this)),
|
|
m_serverProcess(nullptr), m_cacheBlocks({}) {
|
|
m_adblockIcon = new AdBlockIcon(this);
|
|
m_adblockIcon->setObjectName(QSL("m_adblockIconAction"));
|
|
m_unifiedFiltersFile = qApp->userDataFolder() + QDir::separator() + QSL("adblock-unified-filters.txt");
|
|
}
|
|
|
|
AdBlockManager::~AdBlockManager() {
|
|
if (m_serverProcess != nullptr && m_serverProcess->state() == QProcess::ProcessState::Running) {
|
|
m_serverProcess->kill();
|
|
}
|
|
}
|
|
|
|
BlockingResult AdBlockManager::block(const AdblockRequestInfo& request) {
|
|
if (!isEnabled()) {
|
|
return { false };
|
|
}
|
|
|
|
const QString url_string = request.requestUrl().toEncoded().toLower();
|
|
const QString firstparty_url_string = request.firstPartyUrl().toEncoded().toLower();
|
|
const QString url_scheme = request.requestUrl().scheme().toLower();
|
|
const QPair<QString, QString> url_pair = { firstparty_url_string, url_string };
|
|
const QString url_type = request.resourceType();
|
|
|
|
if (!canRunOnScheme(url_scheme)) {
|
|
return { false };
|
|
}
|
|
else {
|
|
if (m_serverProcess != nullptr && m_serverProcess->state() == QProcess::ProcessState::Running) {
|
|
if (m_cacheBlocks.contains(url_pair)) {
|
|
qDebugNN << LOGSEC_ADBLOCK
|
|
<< "Found blocking data in cache, URL:"
|
|
<< QUOTE_W_SPACE_DOT(url_pair);
|
|
|
|
return m_cacheBlocks.value(url_pair);
|
|
}
|
|
|
|
try {
|
|
auto result = askServerIfBlocked(firstparty_url_string, url_string, url_type);
|
|
|
|
m_cacheBlocks.insert(url_pair, result);
|
|
|
|
qDebugNN << LOGSEC_ADBLOCK
|
|
<< "Inserted blocking data to cache for:"
|
|
<< QUOTE_W_SPACE_DOT(url_pair);
|
|
|
|
return result;
|
|
}
|
|
catch (const ApplicationException& ex) {
|
|
qCriticalNN << LOGSEC_ADBLOCK
|
|
<< "HTTP error when calling server for blocking rules:"
|
|
<< QUOTE_W_SPACE_DOT(ex.message());
|
|
return { false };
|
|
}
|
|
}
|
|
else {
|
|
return { false };
|
|
}
|
|
}
|
|
}
|
|
|
|
void AdBlockManager::load(bool initial_load) {
|
|
auto new_enabled = qApp->settings()->value(GROUP(AdBlock), SETTING(AdBlock::AdBlockEnabled)).toBool();
|
|
|
|
if (!initial_load) {
|
|
new_enabled = !new_enabled;
|
|
}
|
|
|
|
if (new_enabled != m_enabled) {
|
|
emit enabledChanged(new_enabled);
|
|
|
|
qApp->settings()->setValue(GROUP(AdBlock), AdBlock::AdBlockEnabled, new_enabled);
|
|
}
|
|
else if (!initial_load) {
|
|
return;
|
|
}
|
|
|
|
m_enabled = new_enabled;
|
|
|
|
if (!m_loaded) {
|
|
qApp->web()->urlIinterceptor()->installUrlInterceptor(m_interceptor);
|
|
m_loaded = true;
|
|
}
|
|
|
|
if (m_enabled) {
|
|
try {
|
|
updateUnifiedFiltersFile();
|
|
}
|
|
catch (const ApplicationException& ex) {
|
|
qCriticalNN << LOGSEC_ADBLOCK
|
|
<< "Failed to write unified filters to file or re-start server, error:"
|
|
<< QUOTE_W_SPACE_DOT(ex.message());
|
|
|
|
qApp->showGuiMessage(Notification::Event::GeneralEvent,
|
|
tr("AdBlock needs to be configured"),
|
|
tr("AdBlock component is not configured properly."),
|
|
QSystemTrayIcon::MessageIcon::Warning,
|
|
true,
|
|
{},
|
|
[=]() {
|
|
showDialog();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AdBlockManager::isEnabled() const {
|
|
return m_enabled;
|
|
}
|
|
|
|
bool AdBlockManager::canRunOnScheme(const QString& scheme) const {
|
|
return !(scheme == QSL("file") || scheme == QSL("qrc") || scheme == QSL("data") || scheme == QSL("abp"));
|
|
}
|
|
|
|
QString AdBlockManager::elementHidingRulesForDomain(const QUrl& url) const {
|
|
if (m_serverProcess != nullptr && m_serverProcess->state() == QProcess::ProcessState::Running) {
|
|
try {
|
|
auto result = askServerForCosmeticRules(url.toString());
|
|
|
|
return result;
|
|
}
|
|
catch (const ApplicationException& ex) {
|
|
qCriticalNN << LOGSEC_ADBLOCK
|
|
<< "HTTP error when calling server for cosmetic rules:"
|
|
<< QUOTE_W_SPACE_DOT(ex.message());
|
|
return {};
|
|
}
|
|
}
|
|
else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
QStringList AdBlockManager::filterLists() const {
|
|
return qApp->settings()->value(GROUP(AdBlock), SETTING(AdBlock::FilterLists)).toStringList();
|
|
}
|
|
|
|
void AdBlockManager::setFilterLists(const QStringList& filter_lists) {
|
|
qApp->settings()->setValue(GROUP(AdBlock), AdBlock::FilterLists, filter_lists);
|
|
}
|
|
|
|
QStringList AdBlockManager::customFilters() const {
|
|
return qApp->settings()->value(GROUP(AdBlock), SETTING(AdBlock::CustomFilters)).toStringList();
|
|
}
|
|
|
|
void AdBlockManager::setCustomFilters(const QStringList& custom_filters) {
|
|
qApp->settings()->setValue(GROUP(AdBlock), AdBlock::CustomFilters, custom_filters);
|
|
}
|
|
|
|
QString AdBlockManager::generateJsForElementHiding(const QString& css) {
|
|
QString source = QL1S("(function() {"
|
|
"var head = document.getElementsByTagName('head')[0];"
|
|
"if (!head) return;"
|
|
"var css = document.createElement('style');"
|
|
"css.setAttribute('type', 'text/css');"
|
|
"css.appendChild(document.createTextNode('%1'));"
|
|
"head.appendChild(css);"
|
|
"})()");
|
|
QString style = css;
|
|
|
|
style.replace(QL1S("'"), QL1S("\\'"));
|
|
style.replace(QL1S("\n"), QL1S("\\n"));
|
|
|
|
return source.arg(style);
|
|
}
|
|
|
|
void AdBlockManager::showDialog() {
|
|
AdBlockDialog(qApp->mainFormWidget()).exec();
|
|
}
|
|
|
|
BlockingResult AdBlockManager::askServerIfBlocked(const QString& fp_url, const QString& url, const QString& url_type) const {
|
|
QJsonObject req_obj;
|
|
QByteArray out;
|
|
QElapsedTimer tmr;
|
|
|
|
req_obj["fp_url"] = fp_url;
|
|
req_obj["url"] = url;
|
|
req_obj["url_type"] = url_type,
|
|
req_obj["filter"] = true;
|
|
|
|
tmr.start();
|
|
|
|
auto network_res = NetworkFactory::performNetworkOperation(QSL("http://%1:%2").arg(QHostAddress(QHostAddress::SpecialAddress::LocalHost).toString(),
|
|
QString::number(ADBLOCK_SERVER_PORT)),
|
|
500,
|
|
QJsonDocument(req_obj).toJson(),
|
|
out,
|
|
QNetworkAccessManager::Operation::PostOperation,
|
|
{ {
|
|
QSL(HTTP_HEADERS_CONTENT_TYPE).toLocal8Bit(),
|
|
QSL("application/json").toLocal8Bit() } });
|
|
|
|
if (network_res.first == QNetworkReply::NetworkError::NoError) {
|
|
qDebugNN << LOGSEC_ADBLOCK
|
|
<< "Query for blocking info to server took "
|
|
<< tmr.elapsed()
|
|
<< " ms.";
|
|
|
|
QJsonObject out_obj = QJsonDocument::fromJson(out).object();
|
|
bool blocking = out_obj["filter"].toObject()["match"].toBool();
|
|
|
|
return {
|
|
blocking,
|
|
blocking
|
|
? out_obj["filter"].toObject()["filter"].toObject()["filter"].toString()
|
|
: QString()
|
|
};
|
|
}
|
|
else {
|
|
throw NetworkException(network_res.first);
|
|
}
|
|
}
|
|
|
|
QString AdBlockManager::askServerForCosmeticRules(const QString& url) const {
|
|
QJsonObject req_obj;
|
|
QByteArray out;
|
|
QElapsedTimer tmr;
|
|
|
|
req_obj["url"] = url;
|
|
req_obj["cosmetic"] = true;
|
|
|
|
tmr.start();
|
|
|
|
auto network_res = NetworkFactory::performNetworkOperation(QSL("http://%1:%2").arg(QHostAddress(QHostAddress::SpecialAddress::LocalHost).toString(),
|
|
QString::number(ADBLOCK_SERVER_PORT)),
|
|
500,
|
|
QJsonDocument(req_obj).toJson(),
|
|
out,
|
|
QNetworkAccessManager::Operation::PostOperation,
|
|
{ {
|
|
QSL(HTTP_HEADERS_CONTENT_TYPE).toLocal8Bit(),
|
|
QSL("application/json").toLocal8Bit() } });
|
|
|
|
if (network_res.first == QNetworkReply::NetworkError::NoError) {
|
|
qDebugNN << LOGSEC_ADBLOCK
|
|
<< "Query for cosmetic rules to server took "
|
|
<< tmr.elapsed()
|
|
<< " ms.";
|
|
|
|
QJsonObject out_obj = QJsonDocument::fromJson(out).object();
|
|
|
|
return out_obj["cosmetic"].toObject()["styles"].toString();
|
|
}
|
|
else {
|
|
throw NetworkException(network_res.first);
|
|
}
|
|
}
|
|
|
|
QProcess* AdBlockManager::restartServer(int port) {
|
|
QString temp_server = QDir::toNativeSeparators(IOFactory::getSystemFolder(QStandardPaths::StandardLocation::TempLocation)) +
|
|
QDir::separator() +
|
|
QSL("adblock-server.js");
|
|
|
|
if (!IOFactory::copyFile(QSL(":/scripts/adblock/adblock-server.js"), temp_server)) {
|
|
qWarningNN << LOGSEC_ADBLOCK << "Failed to copy server file to TEMP.";
|
|
}
|
|
|
|
QProcess* proc = new QProcess(this);
|
|
|
|
#if defined(Q_OS_WIN)
|
|
proc->setProgram(QSL("node.exe"));
|
|
#else
|
|
proc->setProgram(QSL("node"));
|
|
#endif
|
|
|
|
proc->setArguments({
|
|
QDir::toNativeSeparators(temp_server),
|
|
QString::number(port),
|
|
QDir::toNativeSeparators(m_unifiedFiltersFile)
|
|
});
|
|
|
|
proc->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
|
|
|
|
auto pe = proc->processEnvironment();
|
|
QString default_node_path =
|
|
#if defined(Q_OS_WIN)
|
|
pe.value(QSL("APPDATA")) + QDir::separator() + QSL("npm") + QDir::separator() + QSL("node_modules");
|
|
#elif defined(Q_OS_LINUX)
|
|
QSL("/usr/lib/node_modules");
|
|
#elif defined(Q_OS_MACOS)
|
|
QSL("/usr/local/lib/node_modules");
|
|
#else
|
|
QSL("");
|
|
#endif
|
|
|
|
if (!pe.contains(QSL("NODE_PATH")) && !default_node_path.isEmpty()) {
|
|
pe.insert(QSL("NODE_PATH"), default_node_path);
|
|
}
|
|
|
|
proc->setProcessEnvironment(pe);
|
|
proc->setProcessChannelMode(QProcess::ProcessChannelMode::ForwardedErrorChannel);
|
|
|
|
if (!proc->open() ||
|
|
proc->state() == QProcess::ProcessState::NotRunning ||
|
|
proc->error() != QProcess::ProcessError::UnknownError) {
|
|
auto ers = proc->errorString();
|
|
proc->deleteLater();
|
|
|
|
throw ApplicationException(ers);
|
|
}
|
|
else {
|
|
qDebugNN << LOGSEC_ADBLOCK << "Started server.";
|
|
return proc;
|
|
}
|
|
}
|
|
|
|
void AdBlockManager::updateUnifiedFiltersFile() {
|
|
m_cacheBlocks.clear();
|
|
|
|
if (QFile::exists(m_unifiedFiltersFile)) {
|
|
QFile::remove(m_unifiedFiltersFile);
|
|
}
|
|
|
|
QString unified_contents;
|
|
auto filter_lists = filterLists();
|
|
|
|
// Download filters one by one and append.
|
|
for (const QString& filter_list_url : qAsConst(filter_lists)) {
|
|
if (filter_list_url.simplified().isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
QByteArray out;
|
|
auto res = NetworkFactory::performNetworkOperation(filter_list_url,
|
|
2000,
|
|
{},
|
|
out,
|
|
QNetworkAccessManager::Operation::GetOperation);
|
|
|
|
if (res.first == QNetworkReply::NetworkError::NoError) {
|
|
unified_contents = unified_contents.append(QString::fromUtf8(out));
|
|
unified_contents = unified_contents.append('\n');
|
|
|
|
qDebugNN << LOGSEC_ADBLOCK
|
|
<< "Downloaded filter list from"
|
|
<< QUOTE_W_SPACE_DOT(filter_list_url);
|
|
}
|
|
else {
|
|
throw NetworkException(res.first, tr("failed to download filter list '%1'").arg(filter_list_url));
|
|
}
|
|
}
|
|
|
|
unified_contents = unified_contents.append(customFilters().join(QSL("\n")));
|
|
|
|
// Save.
|
|
m_unifiedFiltersFile = IOFactory::getSystemFolder(QStandardPaths::StandardLocation::TempLocation) +
|
|
QDir::separator() +
|
|
QSL("adblock.filters");
|
|
|
|
IOFactory::writeFile(m_unifiedFiltersFile, unified_contents.toUtf8());
|
|
|
|
if (m_enabled) {
|
|
if (m_serverProcess != nullptr && m_serverProcess->state() == QProcess::ProcessState::Running) {
|
|
m_serverProcess->kill();
|
|
m_serverProcess->waitForFinished(1000);
|
|
m_serverProcess->deleteLater();
|
|
m_serverProcess = nullptr;
|
|
}
|
|
|
|
m_serverProcess = restartServer(ADBLOCK_SERVER_PORT);
|
|
}
|
|
}
|