From 2aec131158da9908dad0e56986f5366dd3de700e Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 13 Mar 2019 17:46:29 +0100 Subject: Add black/whitelisting of URLs --- src/main.cpp | 55 +++++++++++++++++--- src/slxbrowser.cpp | 149 +++++++++++++++++++++++++++++++++++++++-------------- src/slxbrowser.h | 18 +++++-- 3 files changed, 172 insertions(+), 50 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b828d60..208a635 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include class KeyHandler : public QObject { @@ -18,6 +19,8 @@ public: } }; +QStringList loadUrlList(const QString &file); + /** * MAIN */ @@ -26,23 +29,61 @@ int main(int argc, char** argv) QApplication app(argc, argv); QCommandLineParser parser; parser.addHelpOption(); - parser.addPositionalArgument("url", "URL to load"); - QCommandLineOption ignoreSsl("insecure", "Ignore SSL errors"); - QCommandLineOption fullscreen("fullscreen", "Run browser in full screen"); - QCommandLineOption reloadInterval("reload-interval", "Reload displayed page every X seconds", "seconds"); + parser.addPositionalArgument("url", "URL to load."); + QCommandLineOption ignoreSsl("insecure", "Ignore SSL errors."); + QCommandLineOption fullscreen("fullscreen", "Run browser in full screen."); + QCommandLineOption reloadInterval("reload-interval", "Reload displayed page every X seconds.", "seconds"); + QCommandLineOption whitelist("whitelist", "Path to a file of allowed URLs. Mutually exclusive with blacklist.", "file"); + QCommandLineOption blacklist("blacklist", "Path to a file of disallowed URLs. Mutually exclusive with whitelist.", "file"); parser.addOption(ignoreSsl); parser.addOption(fullscreen); parser.addOption(reloadInterval); + parser.addOption(whitelist); + parser.addOption(blacklist); parser.process(app); QStringList list(parser.positionalArguments()); if (list.empty()) { - QMessageBox::critical(NULL, "Error", "Need one argument: file name"); + QMessageBox::critical(nullptr, "Error", "Need one argument: file name"); return 1; } - QString url(list[0]); - SLXbrowser main(url, parser.isSet(fullscreen), parser.isSet(ignoreSsl), parser.value(reloadInterval).toInt()); + if (parser.isSet(whitelist) && parser.isSet(blacklist)) { + QMessageBox::critical(nullptr, "Error", "Need either blacklist or whitelist, not both"); + return 2; + } + BrowserSettings settings; + settings.url = list[0]; + settings.fullscreen = parser.isSet(fullscreen); + settings.ignoreSslErrors = parser.isSet(ignoreSsl); + settings.reloadInterval = parser.value(reloadInterval).toInt(); + if (parser.isSet(whitelist)) { + settings.urlList = loadUrlList(parser.value(whitelist)); + } else if (parser.isSet(blacklist)) { + settings.urlList = loadUrlList(parser.value(blacklist)); + } + settings.isWhitelist = parser.isSet(whitelist); + SLXbrowser main(settings); main.show(); app.installEventFilter(new KeyHandler()); app.exec(); return 0; } + +QStringList loadUrlList(const QString &file) +{ + QStringList stringList; + QFile textFile(file); + if (!textFile.open(QFile::ReadOnly)) { + QTextStream(stdout) << "Cannot open URL list\n"; + return QStringList(); + } + QTextStream textStream(&textFile); + while (true) + { + QString line = textStream.readLine(); + if (line.isNull()) + break; + else + stringList.append(line); + } + return stringList; +} diff --git a/src/slxbrowser.cpp b/src/slxbrowser.cpp index 0690102..5ffe281 100644 --- a/src/slxbrowser.cpp +++ b/src/slxbrowser.cpp @@ -1,19 +1,24 @@ #include "slxbrowser.h" #include "webview.h" +#include "nam.h" #include #include #include #include #include +#include -SLXbrowser::SLXbrowser(QString url, bool fullscreen, bool ignoreSslErrors, int reloadIntervalSeconds) +static QRegularExpression urlListToRegExp(const QStringList &list); + +SLXbrowser::SLXbrowser(BrowserSettings settings) : QMainWindow(nullptr), - _url(url), - _ignoreSslErrors(ignoreSslErrors), + _settings(settings), _unsupportedUri(false), - _reloadIntervalMs(reloadIntervalSeconds * 1000) + _blockedSite(false), + _lastPageLoad(0) { - if (fullscreen) { + _settings.reloadInterval *= 1000; + if (_settings.fullscreen) { this->showFullScreen(); this->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); } @@ -40,12 +45,21 @@ SLXbrowser::SLXbrowser(QString url, bool fullscreen, bool ignoreSslErrors, int r connect(_browser, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int))); // QWebPage *page = _browser->page(); - QNetworkAccessManager *nam = new QNetworkAccessManager(this); + QNetworkAccessManager *nam; + if (_settings.urlList.isEmpty()) { + nam = new QNetworkAccessManager(this); + } else { + if (_settings.isWhitelist) { + // Just to be safe + _settings.urlList << _settings.url; + } + nam = new SlxNetworkAccessManager(urlListToRegExp(_settings.urlList), _settings.isWhitelist); + } connect(nam, SIGNAL(sslErrors(QNetworkReply*, const QList&)), this, SLOT(sslErrors(QNetworkReply*, const QList&))); connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*))); page->setNetworkAccessManager(nam); - page->mainFrame()->load(url); + page->mainFrame()->load(_settings.url); // _browser->show(); } @@ -57,8 +71,8 @@ SLXbrowser::~SLXbrowser() void SLXbrowser::loadStarted() { _reset.stop(); - if (_reloadIntervalMs > 0) { - _reset.start(_reloadIntervalMs + 10000); + if (_settings.reloadInterval > 0) { + _reset.start(_settings.reloadInterval + 10000); } _normalError.clear(); _sslErrors.clear(); @@ -69,37 +83,63 @@ void SLXbrowser::loadStarted() void SLXbrowser::loadFinished(bool ok) { _progress->hide(); - if (!_browser->wasAbortedDownload() && !_unsupportedUri && !ok) { - _browser->page()->mainFrame()->setHtml("

Page Load Error

");
-		QWebElement el = _browser->page()->mainFrame()->documentElement().findFirst("#content");
-		QString str;
-		if (!_sslErrors.empty()) {
-			str.append("SSL Errors:\n");
-			for (QSslError err : _sslErrors) {
-				str.append(err.errorString());
-				str.append('\n');
+	qint64 now = QDateTime::currentMSecsSinceEpoch();
+	if (!_browser->wasAbortedDownload() && !ok) {
+
+		if (_unsupportedUri) {
+			QMessageBox::warning(this, QString::fromUtf8("Denied"),
+					QString::fromUtf8("This URL type is not supported.\n\n"
+							"Diese Art Link wird nicht unterstützt.\n\n"
+							"(z.B. Mail)"));
+		} else if (_blockedSite) {
+			int numElems = _browser->page()->mainFrame()->documentElement().findAll("*").count();
+			if (now - _lastPageLoad < 500 && numElems < 15) {
+				// Was probably a JS redirect, go back before displaying the error
+				_browser->page()->triggerAction(QWebPage::Back);
 			}
-		} else if (_normalError.length() != 0) {
-			str.append("Load Error:\n");
-			str.append(_normalError);
+			QTimer::singleShot(5, [=]() {
+				QMessageBox::warning(this, QString::fromUtf8("Denied"),
+						QString::fromUtf8("Target URL not allowed.\n\n"
+								"Dieser Link führt auf eine nicht erlaubte Seite."));
+			});
+			/*
+			_browser->page()->mainFrame()->setHtml("

" + "

Zugriff verweigert

" + "

Diese URL wurde gefiltert.
" + "Zurück

" + ""); + */ } else { - str.append("Unknown Error"); + _browser->page()->mainFrame()->setHtml("

" + "

Page Load Error

" + ""); + QWebElement el = _browser->page()->mainFrame()->documentElement().findFirst("#content"); + QString str; + if (!_sslErrors.empty()) { + str.append("SSL Errors:\n"); + for (QSslError err : _sslErrors) { + str.append(err.errorString()); + str.append('\n'); + } + } else if (_normalError.length() != 0) { + str.append("Load Error:\n"); + str.append(_normalError); + } else { + str.append("Unknown Error"); + } + str.append("\n\n\n\n" + QDateTime::currentDateTime().toString()); + el.setPlainText(str); } - str.append("\n\n\n\n" + QDateTime::currentDateTime().toString()); - el.setPlainText(str); _sslErrors.clear(); _normalError.clear(); - _reset.start(30000); - } else if (_reloadIntervalMs > 0) { - _reset.start(_reloadIntervalMs); + _reset.start(qMin(30000, _settings.reloadInterval > 0 ? _settings.reloadInterval : 30000)); + } else if (_settings.reloadInterval > 0) { + _reset.start(_settings.reloadInterval); } - if (_unsupportedUri && !ok) { - QMessageBox::warning(this, QString::fromUtf8("Denied"), - QString::fromUtf8("This URL type is not supported.\n\n" - "Diese Art Link wird nicht unterstützt.\n\n" - "(z.B. Mail)")); - } + _unsupportedUri = false; + _blockedSite = false; + _lastPageLoad = now; } void SLXbrowser::loadProgress(int progress) @@ -109,7 +149,7 @@ void SLXbrowser::loadProgress(int progress) void SLXbrowser::sslErrors(QNetworkReply* reply, const QList& errors) { - if (_ignoreSslErrors) { + if (_settings.ignoreSslErrors) { reply->ignoreSslErrors(); return; } @@ -118,17 +158,48 @@ void SLXbrowser::sslErrors(QNetworkReply* reply, const QList& errors) void SLXbrowser::requestFinished(QNetworkReply *reply) { - if (reply->error() == QNetworkReply::NoError) { - _unsupportedUri = false; - } else if (reply->error() == QNetworkReply::ProtocolUnknownError) { + if (reply->error() == QNetworkReply::ProtocolUnknownError) { _unsupportedUri = true; + } else if (reply->error() == QNetworkReply::UnknownNetworkError) { + _blockedSite = true; } else { - qDebug() << reply->error(); + //qDebug() << reply->error(); _normalError = reply->errorString(); } } void SLXbrowser::reloadInitial() { - _browser->page()->mainFrame()->load(_url); + _browser->page()->mainFrame()->load(_settings.url); +} + +static QRegularExpression urlListToRegExp(const QStringList &list) +{ + // We search in the escaped string, so actually look for \*\* and \* + // Capture char before that because it must not be another backslash, as that + // means the star was already escaped in the list. + // Since these are C strings there are some additional backslashes here. + static const QRegularExpression STARSTAR("(^|[^\\\\])\\\\\\*\\\\\\*"); + static const QRegularExpression STAR("(^|[^\\\\])\\\\\\*"); + static const QRegularExpression QUEST("(^|[^\\\\])\\\\\\?"); + static const QString STARSTAR_REP("\\1.*"); + static const QString STAR_REP("\\1[^/]*"); + static const QString QUEST_REP("\\1.?"); + QStringList regexes; // All my regex's live in Regtexas + for (const QString &str : list) { + QString mangled; + if (str.contains(QLatin1String("//"))) { + mangled = str; + } else if (str.contains(QLatin1Char('/')) || str.contains(QLatin1String("**"))) { + mangled = "*//" + str; + } else { + mangled = "*//" + str + "/**"; + } + mangled = QRegularExpression::escape(mangled); + mangled = mangled.replace(STARSTAR, STARSTAR_REP).replace(STAR, STAR_REP) + .replace(QUEST, QUEST_REP); + regexes << mangled; + } + qDebug() << regexes; + return QRegularExpression("^(" + regexes.join('|') + ")$"); } diff --git a/src/slxbrowser.h b/src/slxbrowser.h index c036821..f68e186 100644 --- a/src/slxbrowser.h +++ b/src/slxbrowser.h @@ -11,11 +11,21 @@ class WebView; class QProgressBar; class QNetworkReply; +struct BrowserSettings +{ + QString url; + bool fullscreen; + bool ignoreSslErrors; + int reloadInterval; + bool isWhitelist; + QStringList urlList; +}; + class SLXbrowser : public QMainWindow { Q_OBJECT public: - SLXbrowser(QString url, bool fullscreen, bool ignoreSslErrors, int reloadInterval); + SLXbrowser(BrowserSettings settings); virtual ~SLXbrowser(); private slots: @@ -27,15 +37,15 @@ private slots: void reloadInitial(); private: - QString _url; - bool _ignoreSslErrors; + BrowserSettings _settings; bool _unsupportedUri; + bool _blockedSite; WebView *_browser; QProgressBar *_progress; QTimer _reset; QList _sslErrors; QString _normalError; - int _reloadIntervalMs; + qint64 _lastPageLoad; }; #endif /* SLXBROWSER_H_ */ -- cgit v1.2.3-55-g7522