diff options
-rw-r--r-- | doc/Usage/Filter.asciidoc | 12 | ||||
-rw-r--r-- | lib/configuration/configuration.h | 7 | ||||
-rw-r--r-- | lib/web/profilemanager.cpp | 13 | ||||
-rw-r--r-- | lib/web/webprofile.cpp | 14 | ||||
-rw-r--r-- | lib/web/webprofile.h | 9 | ||||
-rw-r--r-- | plugins/ProfileEditor/CMakeLists.txt | 3 | ||||
-rw-r--r-- | plugins/ProfileEditor/forms/newhttpheaderdialog.cpp | 32 | ||||
-rw-r--r-- | plugins/ProfileEditor/forms/newhttpheaderdialog.h | 33 | ||||
-rw-r--r-- | plugins/ProfileEditor/forms/newhttpheaderdialog.ui | 84 | ||||
-rw-r--r-- | plugins/ProfileEditor/forms/profileview.cpp | 46 | ||||
-rw-r--r-- | plugins/ProfileEditor/forms/profileview.h | 3 | ||||
-rw-r--r-- | plugins/ProfileEditor/forms/profileview.ui | 68 | ||||
-rw-r--r-- | src/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/browser.cpp | 6 | ||||
-rw-r--r-- | src/browser.h | 4 | ||||
-rw-r--r-- | src/webengine/filter.cpp | 116 | ||||
-rw-r--r-- | src/webengine/filter.h | 45 | ||||
-rw-r--r-- | src/webengine/urlinterceptor.cpp | 127 | ||||
-rw-r--r-- | src/webengine/urlinterceptor.h | 18 |
19 files changed, 512 insertions, 130 deletions
diff --git a/doc/Usage/Filter.asciidoc b/doc/Usage/Filter.asciidoc new file mode 100644 index 0000000..d23d57a --- /dev/null +++ b/doc/Usage/Filter.asciidoc @@ -0,0 +1,12 @@ +== Url Request Filter +smolbote has a singular URL request filter that is installed onto all profiles. +Any setting applied to it will be applied to all profiles. + +=== filter.header + A list of header-value pairs, separated by a colon (':'). + +You can specify multiple headers by using --filter.header multiple times: +[source, sh] +---- +poi --filter.header "Dnt:1" --filter.header "Accept:text/html" +---- diff --git a/lib/configuration/configuration.h b/lib/configuration/configuration.h index d4770ae..1524536 100644 --- a/lib/configuration/configuration.h +++ b/lib/configuration/configuration.h @@ -56,6 +56,13 @@ public: return std::optional<QString>(QString::fromStdString(this->value<std::string>(path).value())); //return std::optional<QString>(vm[path].as<const char*>()); + } else if constexpr(std::is_same_v<T, QStringList>) { + QStringList r; + for(const std::string &item : this->value<std::vector<std::string>>(path).value()) { + r.append(QString::fromStdString(item)); + } + return std::optional<QStringList>(r); + } else if constexpr(std::is_same_v<T, std::string>) { if(vm[path].value().type() == typeid(int)) { diff --git a/lib/web/profilemanager.cpp b/lib/web/profilemanager.cpp index 74ddc75..17435e8 100644 --- a/lib/web/profilemanager.cpp +++ b/lib/web/profilemanager.cpp @@ -73,6 +73,19 @@ WebProfile *ProfileManager::loadProfile(const QString &path) this->m_profiles.at(id)->settings.setValue("attributes/" + QString::number(attr), value); }); + // headers + ptr->settings.beginGroup("headers"); + for(const QString &key : ptr->settings.childKeys()) { + ptr->profile->setHttpHeader(key.toLatin1(), ptr->settings.value(key).toString().toLatin1()); + } + ptr->settings.endGroup(); + connect(ptr->profile, &WebProfile::headerChanged, ptr->profile, [this, id](const QString &name, const QString &value) { + this->m_profiles.at(id)->settings.setValue("headers/" + name, value); + }); + connect(ptr->profile, &WebProfile::headerRemoved, ptr->profile, [this, id](const QString &name) { + this->m_profiles.at(id)->settings.remove("headers/" + name); + }); + m_profiles[id] = std::move(ptr); return m_profiles.at(id)->profile; } diff --git a/lib/web/webprofile.cpp b/lib/web/webprofile.cpp index bd84d38..16c12b7 100644 --- a/lib/web/webprofile.cpp +++ b/lib/web/webprofile.cpp @@ -135,6 +135,20 @@ void WebProfile::setHttpUserAgent(const QString &userAgent) emit propertyChanged("httpUserAgent", userAgent); } +void WebProfile::setHttpHeader(const QString& name, const QString& value) +{ + m_headers[name.toLatin1()] = value.toLatin1(); + emit headerChanged(name, value); +} + +void WebProfile::removeHttpHeader(const QString& name) +{ + if(m_headers.contains(name.toLatin1())) { + m_headers.remove(name.toLatin1()); + emit headerRemoved(name); + } +} + void WebProfile::setSpellCheckEnabled(bool enable) { QWebEngineProfile::setSpellCheckEnabled(enable); diff --git a/lib/web/webprofile.h b/lib/web/webprofile.h index b58fad7..269989b 100644 --- a/lib/web/webprofile.h +++ b/lib/web/webprofile.h @@ -65,6 +65,10 @@ public: { return qAsConst(m_cookies); } + const QMap<QByteArray, QByteArray> headers() const + { + return qAsConst(m_headers); + } // search url QString search() const; @@ -86,6 +90,8 @@ public: void setHttpCacheMaximumSize(int maxSize); void setHttpCacheType(int type); void setHttpUserAgent(const QString &userAgent); + void setHttpHeader(const QString &name, const QString &value); + void removeHttpHeader(const QString &name); void setSpellCheckEnabled(bool enable); @@ -97,6 +103,8 @@ signals: void propertyChanged(const QString &name, const QVariant &value); void attributeChanged(const QWebEngineSettings::WebAttribute attribute, const bool value); + void headerChanged(const QString &name, const QString &value); + void headerRemoved(const QString &name); private: static WebProfile *profile; @@ -107,6 +115,7 @@ private: QUrl m_newtab = QUrl("about:blank"); QVector<QNetworkCookie> m_cookies; + QMap<QByteArray, QByteArray> m_headers; }; #endif // SMOLBOTE_WEBENGINEPROFILE_H diff --git a/plugins/ProfileEditor/CMakeLists.txt b/plugins/ProfileEditor/CMakeLists.txt index d22d3d6..7c9a774 100644 --- a/plugins/ProfileEditor/CMakeLists.txt +++ b/plugins/ProfileEditor/CMakeLists.txt @@ -20,6 +20,9 @@ add_library(ProfileEditorPlugin SHARED forms/newprofiledialog.cpp forms/newprofiledialog.h forms/newprofiledialog.ui + forms/newhttpheaderdialog.cpp + forms/newhttpheaderdialog.h + forms/newhttpheaderdialog.ui ) target_include_directories(ProfileEditorPlugin diff --git a/plugins/ProfileEditor/forms/newhttpheaderdialog.cpp b/plugins/ProfileEditor/forms/newhttpheaderdialog.cpp new file mode 100644 index 0000000..3978c4e --- /dev/null +++ b/plugins/ProfileEditor/forms/newhttpheaderdialog.cpp @@ -0,0 +1,32 @@ +/* + * 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 "newhttpheaderdialog.h" +#include "ui_newhttpheaderdialog.h" + +NewHttpHeaderDialog::NewHttpHeaderDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::NewHttpHeaderDialog) +{ + ui->setupUi(this); +} + +NewHttpHeaderDialog::~NewHttpHeaderDialog() +{ + delete ui; +} + +QString NewHttpHeaderDialog::header() const +{ + return ui->header->text(); +} + +QString NewHttpHeaderDialog::value() const +{ + return ui->value->text(); +} diff --git a/plugins/ProfileEditor/forms/newhttpheaderdialog.h b/plugins/ProfileEditor/forms/newhttpheaderdialog.h new file mode 100644 index 0000000..53a2a80 --- /dev/null +++ b/plugins/ProfileEditor/forms/newhttpheaderdialog.h @@ -0,0 +1,33 @@ +/* + * 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 + */ + +#ifndef NEWHTTPHEADERDIALOG_H +#define NEWHTTPHEADERDIALOG_H + +#include <QDialog> + +namespace Ui { +class NewHttpHeaderDialog; +} + +class NewHttpHeaderDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewHttpHeaderDialog(QWidget *parent = nullptr); + ~NewHttpHeaderDialog(); + + QString header() const; + QString value() const; + +private: + Ui::NewHttpHeaderDialog *ui; +}; + +#endif // NEWHTTPHEADERDIALOG_H diff --git a/plugins/ProfileEditor/forms/newhttpheaderdialog.ui b/plugins/ProfileEditor/forms/newhttpheaderdialog.ui new file mode 100644 index 0000000..a457ba6 --- /dev/null +++ b/plugins/ProfileEditor/forms/newhttpheaderdialog.ui @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>NewHttpHeaderDialog</class> + <widget class="QDialog" name="NewHttpHeaderDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>320</width> + <height>108</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="header_label"> + <property name="text"> + <string>Header</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="header"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="value_label"> + <property name="text"> + <string>Value</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="value"/> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>NewHttpHeaderDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>NewHttpHeaderDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/plugins/ProfileEditor/forms/profileview.cpp b/plugins/ProfileEditor/forms/profileview.cpp index ccb4ae5..4a89af6 100644 --- a/plugins/ProfileEditor/forms/profileview.cpp +++ b/plugins/ProfileEditor/forms/profileview.cpp @@ -13,6 +13,7 @@ #include <QWebEngineSettings> #include <QWebEngineCookieStore> #include <QDateTime> +#include "newhttpheaderdialog.h" inline void connectSetting(QCheckBox *checkBox, WebProfile *profile, QWebEngineSettings::WebAttribute attr) { @@ -86,6 +87,28 @@ ProfileView::ProfileView(WebProfile *profile, QWidget *parent) ui->storagePath_lineEdit->setText(m_profile->persistentStoragePath()); ui->cachePath_lineEdit->setText(m_profile->cachePath()); + // headers tab + for(auto i = m_profile->headers().constBegin(); i != m_profile->headers().constEnd(); ++i) { + //ui->httpHeaders->addItem(); + headerChanged(i.key(), i.value()); + } + connect(m_profile, &WebProfile::headerChanged, this, &ProfileView::headerChanged); + connect(m_profile, &WebProfile::headerRemoved, this, &ProfileView::headerRemoved); + connect(ui->headers_insert, &QPushButton::clicked, m_profile, [this]() { + auto *dlg = new NewHttpHeaderDialog(this); + if(dlg->exec() == QDialog::Accepted) { + m_profile->setHttpHeader(dlg->header(), dlg->value()); + } + delete dlg; + }); + connect(ui->headers_delete, &QPushButton::clicked, m_profile, [this]() { + for(auto &list : ui->httpHeaders->selectedRanges()) { + for(int i = list.bottomRow(); i >= list.topRow(); --i) { + m_profile->removeHttpHeader(ui->httpHeaders->item(i, 0)->text()); + } + } + }); + // settings tab connectSetting(ui->autoloadImages, m_profile, QWebEngineSettings::AutoLoadImages); connectSetting(ui->autoloadIcons, m_profile, QWebEngineSettings::AutoLoadIconsForPage); @@ -157,6 +180,29 @@ void ProfileView::loadCookies(QWebEngineCookieStore *store) connect(ui->cookies_deleteAll, &QPushButton::clicked, store, &QWebEngineCookieStore::deleteAllCookies); } +void ProfileView::headerChanged(const QString &name, const QString &value) +{ + const auto items = ui->httpHeaders->findItems(name, Qt::MatchExactly); + if(!items.isEmpty()) { + QTableWidgetItem *valueItem = ui->httpHeaders->item(items.constFirst()->row(), 1); + valueItem->setText(value); + } else { + // new header + const int index = ui->httpHeaders->rowCount(); + ui->httpHeaders->setRowCount(index + 1); + ui->httpHeaders->setItem(index, 0, new QTableWidgetItem(name)); + ui->httpHeaders->setItem(index, 1, new QTableWidgetItem(value)); + } +} + +void ProfileView::headerRemoved(const QString& name) +{ + const auto items = ui->httpHeaders->findItems(name, Qt::MatchExactly); + if(!items.isEmpty()) { + ui->httpHeaders->removeRow(items.constFirst()->row()); + } +} + void ProfileView::cookieAdded(const QNetworkCookie &cookie) { auto index = ui->cookies->rowCount(); diff --git a/plugins/ProfileEditor/forms/profileview.h b/plugins/ProfileEditor/forms/profileview.h index 5ff472b..d05b0f1 100644 --- a/plugins/ProfileEditor/forms/profileview.h +++ b/plugins/ProfileEditor/forms/profileview.h @@ -33,6 +33,9 @@ public: private slots: void loadCookies(QWebEngineCookieStore *store); + void headerChanged(const QString &name, const QString &value); + void headerRemoved(const QString &name); + void cookieAdded(const QNetworkCookie &cookie); void cookieRemoved(const QNetworkCookie &cookie); diff --git a/plugins/ProfileEditor/forms/profileview.ui b/plugins/ProfileEditor/forms/profileview.ui index 74b6d5d..630e53d 100644 --- a/plugins/ProfileEditor/forms/profileview.ui +++ b/plugins/ProfileEditor/forms/profileview.ui @@ -234,6 +234,70 @@ </item> </layout> </widget> + <widget class="QWidget" name="httpHeadersTab"> + <attribute name="title"> + <string>Headers</string> + </attribute> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QTableWidget" name="httpHeaders"> + <attribute name="horizontalHeaderDefaultSectionSize"> + <number>200</number> + </attribute> + <attribute name="horizontalHeaderMinimumSectionSize"> + <number>100</number> + </attribute> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string>Name</string> + </property> + </column> + <column> + <property name="text"> + <string>Value</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QPushButton" name="headers_insert"> + <property name="text"> + <string>Insert</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="headers_delete"> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> <widget class="QWidget" name="settingsTab"> <attribute name="title"> <string>Settings</string> @@ -249,8 +313,8 @@ <rect> <x>0</x> <y>0</y> - <width>276</width> - <height>855</height> + <width>584</width> + <height>797</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout_4"> diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 45a4c06..e8f2794 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,6 +38,8 @@ set(srclist subwindow/tabwidget.h # webengine + webengine/filter.cpp + webengine/filter.h webengine/urlinterceptor.cpp webengine/urlinterceptor.h webengine/webpage.cpp diff --git a/src/browser.cpp b/src/browser.cpp index 7bf4adf..7295b56 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -27,6 +27,7 @@ #include <version.h> #include <web/profilemanager.h> #include <web/webprofile.h> +#include "webengine/filter.h" Browser::Browser(int &argc, char *argv[], bool allowSecondary) : SingleApplication(argc, argv, allowSecondary, SingleApplication::User | SingleApplication::SecondaryNotification | SingleApplication::ExcludeAppVersion) @@ -58,7 +59,8 @@ QPair<QString, WebProfile *> Browser::loadProfile(const QString &id) { WebProfile *profile = m_profileManager->loadProfile(id); connect(profile, &WebProfile::downloadRequested, m_downloads.get(), &DownloadsWidget::addDownload); - profile->setRequestInterceptor(m_urlFilter.get()); + auto *interceptor = new UrlRequestInterceptor(m_urlFilter.get(), profile, profile); + profile->setRequestInterceptor(interceptor); return QPair<QString, WebProfile *>(m_profileManager->id(profile), profile); } @@ -107,7 +109,7 @@ void Browser::setup() // downloads m_downloads = std::make_unique<DownloadsWidget>(m_config->value<QString>("downloads.path").value()); // url request filter - m_urlFilter = std::make_unique<UrlRequestInterceptor>(m_config); + m_urlFilter = std::make_unique<Filter>(m_config); // cookie request filter // load profiles diff --git a/src/browser.h b/src/browser.h index b4352a7..6731f59 100644 --- a/src/browser.h +++ b/src/browser.h @@ -20,7 +20,7 @@ class Configuration; class BookmarksWidget; class DownloadsWidget; -class UrlRequestInterceptor; +class Filter; class MainWindow; class WebProfile; class Browser : public SingleApplication, public BrowserInterface @@ -62,7 +62,7 @@ private: std::shared_ptr<BookmarksWidget> m_bookmarks; std::unique_ptr<DownloadsWidget> m_downloads; ProfileManager *m_profileManager; - std::unique_ptr<UrlRequestInterceptor> m_urlFilter; + std::unique_ptr<Filter> m_urlFilter; QVector<MainWindow *> m_windows; QVector<Plugin> m_plugins; diff --git a/src/webengine/filter.cpp b/src/webengine/filter.cpp new file mode 100644 index 0000000..b250843 --- /dev/null +++ b/src/webengine/filter.cpp @@ -0,0 +1,116 @@ +/* + * 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 "filter.h" +#include "urlinterceptor.h" +#include <QDir> +#include <QJsonArray> +#include <QJsonDocument> +#include <QTextStream> +#include <configuration/configuration.h> +#include "util.h" + + +QHash<QString, Filter::HostRule> parseHostlist(const QString &filename) +{ + QHash<QString, Filter::HostRule> rules; + + if(QFile hostfile(filename); 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 <redirect> <host> + // 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)) { + Filter::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; +} +/* +inline std::vector<FilterRule> parseAdBlockList(const QString &filename) +{ + std::vector<FilterRule> 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; +}*/ + +Filter::Filter::Filter(const std::unique_ptr<Configuration> &config, QObject* parent) + : QObject(parent) +{ + // parse headers + if(const auto headers = config->value<QStringList>("filter.header"); headers) { + for(const QString &header : headers.value()) { + const auto list = header.split(QLatin1Literal(":")); + if(list.length() == 2) + m_headers.insert(list.at(0).toLatin1(), list.at(1).toLatin1()); + } + } + + const QStringList hostfiles = Util::files(config->value<QString>("filter.path").value()); + for(const QString &hostfile : hostfiles) { + m_hostlist.unite(parseHostlist(hostfile)); + } + + /* + auto filtersPath = config->value<QString>("filter.adblock"); + if(filtersPath) + filters = parseAdBlockList(filtersPath.value()); + */ +} + +std::optional<Filter::HostRule> Filter::hostlistRule(const QString& url) const +{ + if(!m_hostlist.contains(url)) + return std::nullopt; + + return std::optional<Filter::HostRule>(m_hostlist.value(url)); +} diff --git a/src/webengine/filter.h b/src/webengine/filter.h new file mode 100644 index 0000000..3eac5ee --- /dev/null +++ b/src/webengine/filter.h @@ -0,0 +1,45 @@ +/* + * 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 + */ + +#ifndef SMOLBOTE_FILTER_H +#define SMOLBOTE_FILTER_H + +#include "urlfilter/filterrule.h" +#include <QByteArray> +#include <QVector> +#include <optional> + +class Configuration; +class Filter : public QObject +{ + Q_OBJECT +public: + struct HostRule { + bool isBlocking; + }; + + explicit Filter(const std::unique_ptr<Configuration> &config, QObject *parent = nullptr); + ~Filter() override = default; + + const QHash<QString, HostRule> hostlist() const + { + return qAsConst(m_hostlist); + } + std::optional<HostRule> hostlistRule(const QString &url) const; + + const QMap<QByteArray, QByteArray> headers() const + { + return qAsConst(m_headers); + } + +private: + QHash<QString, HostRule> m_hostlist; + QMap<QByteArray, QByteArray> m_headers; +}; + +#endif // SMOLBOTE_FILTER_H diff --git a/src/webengine/urlinterceptor.cpp b/src/webengine/urlinterceptor.cpp index db4aea9..cf9b85f 100644 --- a/src/webengine/urlinterceptor.cpp +++ b/src/webengine/urlinterceptor.cpp @@ -14,127 +14,32 @@ #include <QTextStream> #include <boost/algorithm/string.hpp> #include <configuration/configuration.h> +#include "filter.h" +#include <web/webprofile.h> -inline std::vector<FilterRule> parseAdBlockList(const QString &filename) -{ - std::vector<FilterRule> 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; -} +// test DNT on https://browserleaks.com/donottrack -UrlRequestInterceptor::UrlRequestInterceptor(const std::unique_ptr<Configuration> &config, QObject *parent) +UrlRequestInterceptor::UrlRequestInterceptor(Filter* filter, WebProfile* profile, QObject* parent) : QWebEngineUrlRequestInterceptor(parent) { - QDir hostsD(config->value<QString>("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<std::vector<std::string>>("filter.header"); - if(header) { - for(const std::string &h : header.value()) { - std::vector<std::string> 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<QString>("filter.adblock"); - if(filtersPath) - filters = parseAdBlockList(filtersPath.value()); + Q_CHECK_PTR(filter); + m_filter = filter; + Q_CHECK_PTR(profile); + m_profile = profile; } -// 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)); + auto hostlistCheck = m_filter->hostlistRule(info.requestUrl().host()); + if(hostlistCheck) { + info.block(hostlistCheck.value().isBlocking); } - if(rules.contains(info.requestUrl().host())) { - info.block(rules.value(info.requestUrl().host()).isBlocking); - return; + // set headers + for(auto i = m_filter->headers().constBegin(); i != m_filter->headers().constEnd(); ++i) { + info.setHttpHeader(i.key(), i.value()); } - - 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; - } + for(auto i = m_profile->headers().constBegin(); i != m_profile->headers().constEnd(); ++i) { + info.setHttpHeader(i.key(), i.value()); } } - -QHash<QString, UrlRequestInterceptor::HostRule> parse(const QString &filename) -{ - QHash<QString, UrlRequestInterceptor::HostRule> 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 <redirect> <host> - // 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; -} diff --git a/src/webengine/urlinterceptor.h b/src/webengine/urlinterceptor.h index 575e0c9..420a161 100644 --- a/src/webengine/urlinterceptor.h +++ b/src/webengine/urlinterceptor.h @@ -15,29 +15,21 @@ #include <QWebEngineUrlRequestInterceptor> #include <memory> -typedef std::pair<std::string, std::string> Header; - +class Filter; +class WebProfile; class Configuration; class UrlRequestInterceptor : public QWebEngineUrlRequestInterceptor { Q_OBJECT public: - struct HostRule { - bool isBlocking; - }; - - explicit UrlRequestInterceptor(const std::unique_ptr<Configuration> &config, QObject *parent = nullptr); + explicit UrlRequestInterceptor(Filter *filter, WebProfile *profile, QObject *parent = nullptr); ~UrlRequestInterceptor() override = default; void interceptRequest(QWebEngineUrlRequestInfo &info) override; private: - QHash<QString, HostRule> rules; - std::vector<FilterRule> filters; - std::vector<Header> m_headers; + Filter *m_filter; + WebProfile *m_profile; }; -QHash<QString, UrlRequestInterceptor::HostRule> parse(const QString &filename); -inline std::vector<FilterRule> parseAdBlockList(const QString &filename); - #endif // SMOLBOTE_URLREQUESTINTERCEPTOR_H |