diff options
| author | Simon Rettberg | 2025-11-13 13:48:37 +0100 |
|---|---|---|
| committer | Simon Rettberg | 2025-11-13 14:00:03 +0100 |
| commit | 2c3b026349349729141ca46bd9d292327da2dfe2 (patch) | |
| tree | af0650430c311a319bda68c89b4fd39036a9ec2c | |
| parent | Fix: error page not showing HTTP error codes, but "Unknown Error" (diff) | |
| download | slxbrowser-master.tar.gz slxbrowser-master.tar.xz slxbrowser-master.zip | |
| -rw-r--r-- | CMakeLists.txt | 52 | ||||
| -rw-r--r-- | src/Makefile | 256 | ||||
| -rw-r--r-- | src/main.cpp | 8 | ||||
| -rw-r--r-- | src/nam.cpp | 70 | ||||
| -rw-r--r-- | src/nam.h | 38 | ||||
| -rw-r--r-- | src/slxbrowser.cpp | 290 | ||||
| -rw-r--r-- | src/slxbrowser.h | 25 | ||||
| -rw-r--r-- | src/webview.cpp | 89 | ||||
| -rw-r--r-- | src/webview.h | 47 |
9 files changed, 283 insertions, 592 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index f188f66..b71df00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,58 +1,44 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.11) # project name project(slxbrowser) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_BUILD_TYPE Debug) -set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wall -Wextra -Werror -Wno-multichar -std=c++11") -set(CMAKE_CXX_FLAGS_RELEASE "-O2 -Wno-multichar -std=c++11") +set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wall -Wextra -Werror -Wno-multichar -std=c++20") +set(CMAKE_CXX_FLAGS_RELEASE "-O2 -Wno-multichar -std=c++20") set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -file(GLOB_RECURSE SLXBROWSER_SOURCES src/*.cpp) -#file(GLOB_RECURSE SLXBROWSER_MOC_HEADERS src/*.h) -#file(GLOB_RECURSE SLXBROWSER_UIS src/ui/*.ui) -#file(GLOB_RECURSE SLXBROWSER_RESOURCES src/*.qrc) +set(SLXBROWSER_HEADERS + src/nam.h + src/slxbrowser.h + src/webview.h +) -#include_directories(${CMAKE_CURRENT_BINARY_DIR} ./) +set(SLXBROWSER_SOURCES + src/main.cpp + src/nam.cpp + src/slxbrowser.cpp + src/webview.cpp +) # -# Qt4 +# Qt5 # -find_package(Qt5WebKitWidgets) - -#if(Qt5_FOUND) -# message(STATUS "Qt5 found") -#else(Qt5_FOUND) -# message(FATAL_ERROR "Qt5 not found") -#endif(Qt5_FOUND) - -#set(QT_USE_QTXML TRUE) -#set(QT_USE_QTSVG TRUE) -#set(QT_USE_QTNETWORK TRUE) -#set(QT_USE_QTWEBKIT TRUE) - -#include(${QT_USE_FILE}) - -#QT4_ADD_RESOURCES(SLXBROWSER_RC_SOURCES ${SLXBROWSER_RESOURCES}) -#QT5_WRAP_UI(SLXBROWSER_UI_HEADERS ${SLXBROWSER_UIS}) -#QT5_WRAP_CPP(SLXBROWSER_MOC_SOURCES ${SLXBROWSER_MOC_HEADERS}) +find_package(Qt5 COMPONENTS WebEngineWidgets REQUIRED) # # build slxbrowser # add_executable(slxbrowser + ${SLXBROWSER_HEADERS} ${SLXBROWSER_SOURCES} -# ${SLXBROWSER_MOC_SOURCES} -# ${SLXBROWSER_UI_HEADERS} -# ${SLXBROWSER_RC_SOURCES} -# ${SLXBROWSER_QMS} ) target_link_libraries(slxbrowser - Qt5::WebKitWidgets + Qt5::WebEngineWidgets ) install(TARGETS slxbrowser RUNTIME DESTINATION bin) diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index 4fd1610..0000000 --- a/src/Makefile +++ /dev/null @@ -1,256 +0,0 @@ -# CMAKE generated file: DO NOT EDIT! -# Generated by "Unix Makefiles" Generator, CMake Version 3.5 - -# Default target executed when no arguments are given to make. -default_target: all - -.PHONY : default_target - -# Allow only one "make -f Makefile2" at a time, but pass parallelism. -.NOTPARALLEL: - - -#============================================================================= -# Special targets provided by cmake. - -# Disable implicit rules so canonical targets will work. -.SUFFIXES: - - -# Remove some rules from gmake that .SUFFIXES does not remove. -SUFFIXES = - -.SUFFIXES: .hpux_make_needs_suffix_list - - -# Suppress display of executed commands. -$(VERBOSE).SILENT: - - -# A target that is always out of date. -cmake_force: - -.PHONY : cmake_force - -#============================================================================= -# Set environment variables for the build. - -# The shell in which to execute make rules. -SHELL = /bin/sh - -# The CMake executable. -CMAKE_COMMAND = /usr/bin/cmake - -# The command to remove a file. -RM = /usr/bin/cmake -E remove -f - -# Escaping for special characters. -EQUALS = = - -# The top-level source directory on which CMake was run. -CMAKE_SOURCE_DIR = /home/sr/dev/openslx/slxbrowser - -# The top-level build directory on which CMake was run. -CMAKE_BINARY_DIR = /home/sr/dev/openslx/slxbrowser/src - -#============================================================================= -# Targets provided globally by CMake. - -# Special rule for the target install -install: preinstall - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." - /usr/bin/cmake -P cmake_install.cmake -.PHONY : install - -# Special rule for the target install -install/fast: preinstall/fast - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." - /usr/bin/cmake -P cmake_install.cmake -.PHONY : install/fast - -# Special rule for the target list_install_components -list_install_components: - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Available install components are: \"Unspecified\"" -.PHONY : list_install_components - -# Special rule for the target list_install_components -list_install_components/fast: list_install_components - -.PHONY : list_install_components/fast - -# Special rule for the target rebuild_cache -rebuild_cache: - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." - /usr/bin/cmake -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) -.PHONY : rebuild_cache - -# Special rule for the target rebuild_cache -rebuild_cache/fast: rebuild_cache - -.PHONY : rebuild_cache/fast - -# Special rule for the target install/strip -install/strip: preinstall - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." - /usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake -.PHONY : install/strip - -# Special rule for the target install/strip -install/strip/fast: install/strip - -.PHONY : install/strip/fast - -# Special rule for the target install/local -install/local: preinstall - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." - /usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake -.PHONY : install/local - -# Special rule for the target install/local -install/local/fast: install/local - -.PHONY : install/local/fast - -# Special rule for the target edit_cache -edit_cache: - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake cache editor..." - /usr/bin/ccmake -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) -.PHONY : edit_cache - -# Special rule for the target edit_cache -edit_cache/fast: edit_cache - -.PHONY : edit_cache/fast - -# The main all target -all: cmake_check_build_system - $(CMAKE_COMMAND) -E cmake_progress_start /home/sr/dev/openslx/slxbrowser/src/CMakeFiles /home/sr/dev/openslx/slxbrowser/src/CMakeFiles/progress.marks - $(MAKE) -f CMakeFiles/Makefile2 all - $(CMAKE_COMMAND) -E cmake_progress_start /home/sr/dev/openslx/slxbrowser/src/CMakeFiles 0 -.PHONY : all - -# The main clean target -clean: - $(MAKE) -f CMakeFiles/Makefile2 clean -.PHONY : clean - -# The main clean target -clean/fast: clean - -.PHONY : clean/fast - -# Prepare targets for installation. -preinstall: all - $(MAKE) -f CMakeFiles/Makefile2 preinstall -.PHONY : preinstall - -# Prepare targets for installation. -preinstall/fast: - $(MAKE) -f CMakeFiles/Makefile2 preinstall -.PHONY : preinstall/fast - -# clear depends -depend: - $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 -.PHONY : depend - -#============================================================================= -# Target rules for targets named slxbrowser - -# Build rule for target. -slxbrowser: cmake_check_build_system - $(MAKE) -f CMakeFiles/Makefile2 slxbrowser -.PHONY : slxbrowser - -# fast build rule for target. -slxbrowser/fast: - $(MAKE) -f CMakeFiles/slxbrowser.dir/build.make CMakeFiles/slxbrowser.dir/build -.PHONY : slxbrowser/fast - -CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.o: CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.o - -.PHONY : CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.o - -# target to build an object file -CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.o: - $(MAKE) -f CMakeFiles/slxbrowser.dir/build.make CMakeFiles/slxbrowser.dir/CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.o -.PHONY : CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.o - -CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.i: CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.i - -.PHONY : CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.i - -# target to preprocess a source file -CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.i: - $(MAKE) -f CMakeFiles/slxbrowser.dir/build.make CMakeFiles/slxbrowser.dir/CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.i -.PHONY : CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.i - -CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.s: CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.s - -.PHONY : CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.s - -# target to generate assembly for a file -CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.s: - $(MAKE) -f CMakeFiles/slxbrowser.dir/build.make CMakeFiles/slxbrowser.dir/CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.s -.PHONY : CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.s - -main.o: main.cpp.o - -.PHONY : main.o - -# target to build an object file -main.cpp.o: - $(MAKE) -f CMakeFiles/slxbrowser.dir/build.make CMakeFiles/slxbrowser.dir/main.cpp.o -.PHONY : main.cpp.o - -main.i: main.cpp.i - -.PHONY : main.i - -# target to preprocess a source file -main.cpp.i: - $(MAKE) -f CMakeFiles/slxbrowser.dir/build.make CMakeFiles/slxbrowser.dir/main.cpp.i -.PHONY : main.cpp.i - -main.s: main.cpp.s - -.PHONY : main.s - -# target to generate assembly for a file -main.cpp.s: - $(MAKE) -f CMakeFiles/slxbrowser.dir/build.make CMakeFiles/slxbrowser.dir/main.cpp.s -.PHONY : main.cpp.s - -# Help Target -help: - @echo "The following are some of the valid targets for this Makefile:" - @echo "... all (the default if no target is provided)" - @echo "... clean" - @echo "... depend" - @echo "... install" - @echo "... list_install_components" - @echo "... rebuild_cache" - @echo "... slxbrowser" - @echo "... install/strip" - @echo "... install/local" - @echo "... edit_cache" - @echo "... CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.o" - @echo "... CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.i" - @echo "... CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.s" - @echo "... main.o" - @echo "... main.i" - @echo "... main.s" -.PHONY : help - - - -#============================================================================= -# Special targets to cleanup operation of make. - -# Special rule to run CMake to check the build system integrity. -# No rule that depends on this can have commands that come from listfiles -# because they might be regenerated. -cmake_check_build_system: - $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 -.PHONY : cmake_check_build_system - diff --git a/src/main.cpp b/src/main.cpp index 4d3b6e8..f9659d8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,11 +8,11 @@ class KeyHandler : public QObject { public: - KeyHandler(SlxBrowser *win) : QObject(), _win(win) {} - bool eventFilter(QObject *obj, QEvent *event) + explicit KeyHandler(SlxBrowser *win) : _win(win) {} + bool eventFilter(QObject *obj, QEvent *event) override { if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); + auto *keyEvent = dynamic_cast<QKeyEvent*>(event); if ( keyEvent->key() == Qt::Key_Q && (keyEvent->modifiers() & Qt::ControlModifier)) exit(0); _win->activity(); @@ -86,7 +86,7 @@ QStringList loadUrlList(const QString &file) QFile textFile(file); if (!textFile.open(QFile::ReadOnly)) { QTextStream(stdout) << "Cannot open URL list\n"; - return QStringList(); + return {}; } QTextStream textStream(&textFile); while (true) diff --git a/src/nam.cpp b/src/nam.cpp index 798a8e6..3f2c252 100644 --- a/src/nam.cpp +++ b/src/nam.cpp @@ -1,49 +1,33 @@ #include "nam.h" -#include <QCoreApplication> #include <QDebug> -SlxDisabledNetworkReply::SlxDisabledNetworkReply(QObject *parent, const QNetworkRequest &req, - QNetworkAccessManager::Operation op) - : QNetworkReply(parent) +void SlxRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info) { - setRequest(req); - setUrl(req.url()); - setOperation(op); - setFinished(true); - qRegisterMetaType<QNetworkReply::NetworkError>(); - QString msg = QCoreApplication::translate("QNetworkAccessManager", - "Network access is disabled."); - setError(UnknownNetworkError, msg); - QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, - Q_ARG(QNetworkReply::NetworkError, UnknownNetworkError)); - QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); -} - -QNetworkReply* SlxNetworkAccessManager::createRequest(QNetworkAccessManager::Operation op, - const QNetworkRequest &req, QIODevice *outgoingData) -{ - const QUrl url(req.url()); - //qDebug() << url; - bool ok; - if (url.isLocalFile()) { - ok = true; - } else if (url.scheme() == QLatin1String("qrc") || url.scheme() == QLatin1String("data")) { - ok = true; - } else if (url.host().isEmpty()) { - ok = true; - } else { - auto str = url.toDisplayString(QUrl::NormalizePathSegments); - if (_white.isValid() && _white.match(str).hasMatch()) { - ok = true; - } else if (_black.isValid() && _black.match(str).hasMatch()) { - ok = false; - } else { - ok = true; - } - } - if (!ok) { - return new SlxDisabledNetworkReply(this, req, op); - } - return QNetworkAccessManager::createRequest(op, req, outgoingData); + const QUrl url = info.requestUrl(); + bool ok = true; + if (url.isLocalFile()) { + ok = true; + } else if (url.scheme() == QLatin1String("qrc") || url.scheme() == QLatin1String("data")) { + ok = true; + } else if (url.host().isEmpty()) { + ok = true; + } else if (!url.isValid()) { + ok = false; + } else { + auto str = url.toDisplayString(QUrl::NormalizePathSegments); + if (_white.isValid() && _white.match(str).hasMatch()) { + ok = true; + } else if (_black.isValid() && _black.match(str).hasMatch()) { + ok = false; + } else { + ok = true; + } + } + if (!ok) { + info.block(true); + emit urlBlocked(url); + } else if (!QUrl::fromUserInput(url.toString()).isValid()) { + emit unsupportedScheme(url); + } } @@ -1,38 +1,30 @@ #ifndef NAM_H_ #define NAM_H_ -#include <QNetworkAccessManager> #include <QRegularExpression> -#include <QNetworkReply> +#include <QtWebEngineCore/QWebEngineUrlRequestInterceptor> +#include <QtWebEngineCore/QWebEngineUrlRequestInfo> /** - * Block certain requests based on URL + * Block certain requests based on URL via QtWebEngine interceptor */ -class SlxNetworkAccessManager : public QNetworkAccessManager +class SlxRequestInterceptor : public QWebEngineUrlRequestInterceptor { -Q_OBJECT + Q_OBJECT public: - SlxNetworkAccessManager(QRegularExpression blackList, QRegularExpression whiteList, QObject *parent = nullptr) - : QNetworkAccessManager(parent), _black(blackList), _white(whiteList) {} -protected: - QNetworkReply* createRequest(QNetworkAccessManager::Operation op, - const QNetworkRequest &originalReq, QIODevice *outgoingData = nullptr) override; + SlxRequestInterceptor(const QRegularExpression& blackList, const QRegularExpression& whiteList, QObject *parent = nullptr) + : QWebEngineUrlRequestInterceptor(parent), _black(blackList), _white(whiteList) {} + void interceptRequest(QWebEngineUrlRequestInfo &info) override; + +signals: + void urlBlocked(const QUrl &url); + void unsupportedScheme(const QUrl &url); + private: - QRegularExpression _black, _white; + QRegularExpression _black, _white; }; -class SlxDisabledNetworkReply : public QNetworkReply -{ - Q_OBJECT -public: - SlxDisabledNetworkReply(QObject *parent, const QNetworkRequest &req, - QNetworkAccessManager::Operation op); - - ~SlxDisabledNetworkReply() {} - void abort() override { } -protected: - qint64 readData(char *, qint64) override { return -1; } -}; +// no reply subclass needed with WebEngine #endif diff --git a/src/slxbrowser.cpp b/src/slxbrowser.cpp index 8eceea0..3bb1161 100644 --- a/src/slxbrowser.cpp +++ b/src/slxbrowser.cpp @@ -1,24 +1,29 @@ #include "slxbrowser.h" #include "webview.h" #include "nam.h" -#include <QtWebKitWidgets> -#include <QWebPage> -#include <QNetworkReply> -#include <QSslConfiguration> +#include <QtWebEngineWidgets/QWebEngineSettings> +#include <QtWebEngineWidgets/QWebEnginePage> +#include <QtWebEngineWidgets/QWebEngineProfile> +#include <QtWebEngineWidgets/QWebEngineHistory> #include <QProgressBar> #include <QDateTime> +#include <QVBoxLayout> +#include <QMessageBox> +#include <QRegularExpression> +#include <QDebug> +#include <utility> static QRegularExpression urlListToRegExp(const QStringList &list); SlxBrowser::SlxBrowser(BrowserSettings settings) : QMainWindow(nullptr), - _settings(settings), + _settings(std::move(settings)), _unsupportedUri(false), - _blockedSite(false), - _lastPageLoad(0), - _activity(false), - _lastActivity(0), - _pageValid(false) + _blockedSite(false), + _lastPageLoad(0), + _activity(false), + _lastActivity(0), + _pageValid(false) { _settings.reloadInterval *= 1000; if (_settings.zoom <= 0) { @@ -32,16 +37,17 @@ SlxBrowser::SlxBrowser(BrowserSettings settings) } else if (_settings.maximized) { this->showMaximized(); } - QWebSettings::globalSettings()->setAttribute(QWebSettings::LocalStorageEnabled, true); - //QWebSettings::globalSettings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); - QWidget *w = new QWidget; - QLayout *l = new QVBoxLayout; - _browser = new WebView; - _browser->setZoomFactor(float(_settings.zoom) * .01f); - _progress = new QProgressBar; - _progress->hide(); - l->addWidget(_browser); - l->addWidget(_progress); + // Enable local storage in WebEngine + _browser = new WebView; + _browser->page()->settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true); + _browser->setIgnoreSslErrors(_settings.ignoreSslErrors); + auto *w = new QWidget; + QLayout *l = new QVBoxLayout; + _browser->setZoomFactor(float(_settings.zoom) * .01f); + _progress = new QProgressBar; + _progress->hide(); + l->addWidget(_browser); + l->addWidget(_progress); l->setMargin(0); l->setSpacing(0); l->setContentsMargins(0,0,0,0); @@ -52,180 +58,117 @@ SlxBrowser::SlxBrowser(BrowserSettings settings) _reset.setSingleShot(true); connect(&_reset, &QTimer::timeout, this, &SlxBrowser::reloadInitial); - connect(_browser, &WebView::loadStarted, this, &SlxBrowser::loadStarted); - connect(_browser, &WebView::loadFinished, this, &SlxBrowser::loadFinished); - connect(_browser, &WebView::loadProgress, this, &SlxBrowser::loadProgress); - // - QWebPage *page = _browser->page(); - QNetworkAccessManager *nam; - if (_settings.whiteList.isEmpty() && _settings.blackList.isEmpty()) { - nam = new QNetworkAccessManager(this); - } else { - if (_settings.blackList.isEmpty()) { - _settings.blackList << "*"; - } - // Just to be safe - _settings.whiteList << _settings.url; - nam = new SlxNetworkAccessManager(urlListToRegExp(_settings.blackList), urlListToRegExp(_settings.whiteList)); - } - connect(nam, &QNetworkAccessManager::sslErrors, this, &SlxBrowser::sslErrors); - connect(nam, &QNetworkAccessManager::finished, this, &SlxBrowser::requestFinished); - page->setNetworkAccessManager(nam); - page->mainFrame()->load(_settings.url); - // - _browser->show(); + connect(_browser, &WebView::loadStarted, this, &SlxBrowser::loadStarted); + connect(_browser, &WebView::loadFinished, this, &SlxBrowser::loadFinished); + connect(_browser, &WebView::loadProgress, this, &SlxBrowser::loadProgress); + // URL filtering via interceptor + if (!_settings.whiteList.isEmpty() || !_settings.blackList.isEmpty()) { + if (_settings.blackList.isEmpty()) { + _settings.blackList << "*"; + } + _settings.whiteList << _settings.url; // ensure initial URL allowed + auto *interceptor = new SlxRequestInterceptor(urlListToRegExp(_settings.blackList), urlListToRegExp(_settings.whiteList), this); + connect(interceptor, &SlxRequestInterceptor::urlBlocked, this, [this](const QUrl&){ + _blockedSite = true; + }); + connect(interceptor, &SlxRequestInterceptor::unsupportedScheme, this, [this](const QUrl&){ + _unsupportedUri = true; + }); + _browser->page()->profile()->setUrlRequestInterceptor(interceptor); + } + _browser->load(QUrl(_settings.url)); + // + _browser->show(); } SlxBrowser::~SlxBrowser() -{ -} += default; void SlxBrowser::loadStarted() { - _pageValid = false; - _reset.stop(); - if (_settings.reloadInterval > 0) { - _reset.start(_settings.reloadInterval + 5000); - } - _normalError.clear(); - _sslErrors.clear(); - _progress->setValue(0); - _progress->show(); + _pageValid = false; + _reset.stop(); + if (_settings.reloadInterval > 0) { + _reset.start(_settings.reloadInterval + 5000); + } + _progress->setValue(0); + _progress->show(); } void SlxBrowser::maybeTriggerBack() { - auto elems = _browser->page()->mainFrame()->documentElement().findAll("*"); - if (elems.count() > 10) - return; - QStringList want{{ "HEAD", "TITLE", "H1", "HR", "A", "PRE" }}; - for (auto x : elems) { - if (x.tagName() != want.first()) - continue; - want.pop_front(); - if (want.isEmpty()) - break; - } - if (want.isEmpty()) { - // Was probably a JS redirect, go back before displaying the error - _browser->page()->triggerAction(QWebPage::Back); - } + // Best-effort: if there is a back entry, go back + if (_browser->page()->history()->canGoBack()) + _browser->back(); } void SlxBrowser::loadFinished(bool ok) { - _progress->hide(); - bool abortedDl = _browser->wasAbortedDownload(); - if (!abortedDl && !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) { - maybeTriggerBack(); - QTimer::singleShot(10, [=]() { - maybeTriggerBack(); - QMessageBox::warning(this, QString::fromUtf8("Denied"), - QString::fromUtf8("Target URL not allowed.\n\n" - "Dieser Link führt auf eine nicht erlaubte Seite.")); - }); - } else { - _browser->blockSignals(true); - _browser->page()->mainFrame()->setHtml("<html><body style='background:blue;color:white'><br><br>" - "<center><h1>Page Load Error</h1><div id='content'></div><br>" - "<p><a style='color:white' href='#' onclick='window.history.back()'>Back</a></p>" - "</center></body></html>"); - _browser->blockSignals(false); - 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.isEmpty()) { - 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); - _pageValid = false; - if (_settings.reloadInterval > 0) { - _reset.start(qMin(30000, _settings.reloadInterval)); - } else { - _reset.start(30000); - } - } - _sslErrors.clear(); - _normalError.clear(); - } else { - _pageValid = true; - if (_settings.reloadInterval > 0) { - _reset.start(qMax(_settings.reloadInterval / 20, 1000)); - } - } + _progress->hide(); + bool abortedDl = _browser->wasAbortedDownload(); + if (!abortedDl && !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) { + maybeTriggerBack(); + QTimer::singleShot(10, [this]() { + QMessageBox::warning(this, QString::fromUtf8("Denied"), + QString::fromUtf8("Target URL not allowed.\n\n" + "Dieser Link führt auf eine nicht erlaubte Seite.")); + }); + } else { + QString html = QString::fromUtf8( + "<html><body style='background:blue;color:white'><br><br>" + "<center><h1>Page Load Error</h1><div>%1</div><br>" + "<p><a style='color:white' href='#' onclick='history.back()'>Back</a></p>" + "</center></body></html>") + .arg(QDateTime::currentDateTime().toString()); + _browser->setHtml(html); + _pageValid = false; + if (_settings.reloadInterval > 0) { + _reset.start(qMin(30000, _settings.reloadInterval)); + } else { + _reset.start(30000); + } + } + } else { + _pageValid = true; + if (_settings.reloadInterval > 0) { + _reset.start(qMax(_settings.reloadInterval / 20, 1000)); + } + } _unsupportedUri = false; - _blockedSite = false; - _lastPageLoad = QDateTime::currentMSecsSinceEpoch();; + _blockedSite = false; + _lastPageLoad = QDateTime::currentMSecsSinceEpoch();; } void SlxBrowser::loadProgress(int progress) { - _progress->setValue(progress); -} - -void SlxBrowser::sslErrors(QNetworkReply* reply, const QList<QSslError>& errors) -{ - if (_settings.ignoreSslErrors) { - reply->ignoreSslErrors(); - return; - } - for (const auto& err : errors) { - qDebug() << "SSL:" << err; - } - _sslErrors.append(errors); -} - -void SlxBrowser::requestFinished(QNetworkReply *reply) -{ - if (reply->error() == QNetworkReply::ProtocolUnknownError) { - _unsupportedUri = true; - } else if (reply->error() == QNetworkReply::UnknownNetworkError) { - _blockedSite = true; - } else { - _normalError = reply->errorString(); - if (_normalError.isEmpty()) { - int ec = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (ec >= 400) { - _normalError = QString::asprintf("HTTP ERROR %d", ec); - } - } - } + _progress->setValue(progress); } void SlxBrowser::reloadInitial() { - if (_pageValid) { - if (_activity) { - _lastActivity = QDateTime::currentMSecsSinceEpoch(); - _activity = false; - _reset.start(qMax(_settings.reloadInterval / 20, 1000)); - return; - } - qint64 now = QDateTime::currentMSecsSinceEpoch(); - if (now - qMax(_lastActivity, _lastPageLoad) > _settings.reloadInterval) { - _browser->page()->mainFrame()->load(_settings.url); - } else { - _reset.start(qMax(_settings.reloadInterval / 20, 1000)); - } - } else { - _browser->page()->mainFrame()->load(_settings.url); - } + if (_pageValid) { + if (_activity) { + _lastActivity = QDateTime::currentMSecsSinceEpoch(); + _activity = false; + _reset.start(qMax(_settings.reloadInterval / 20, 1000)); + return; + } + qint64 now = QDateTime::currentMSecsSinceEpoch(); + if (now - qMax(_lastActivity, _lastPageLoad) > _settings.reloadInterval) { + _browser->load(QUrl(_settings.url)); + } else { + _reset.start(qMax(_settings.reloadInterval / 20, 1000)); + } + } else { + _browser->load(QUrl(_settings.url)); + } } static QRegularExpression urlListToRegExp(const QStringList &list) @@ -233,10 +176,9 @@ 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 QRegularExpression STARSTAR(R"((^|[^\\])\\\*\\\*)"); + static const QRegularExpression STAR(R"((^|[^\\])\\\*)"); + static const QRegularExpression QUEST(R"((^|[^\\])\\\?)"); static const QString STARSTAR_REP("\\1.*"); static const QString STAR_REP("\\1[^/]*"); static const QString QUEST_REP("\\1.?"); diff --git a/src/slxbrowser.h b/src/slxbrowser.h index ba95f6b..cdde7af 100644 --- a/src/slxbrowser.h +++ b/src/slxbrowser.h @@ -3,13 +3,10 @@ #include <QMainWindow> #include <QList> -#include <QSslError> #include <QTimer> -class QNetworkReply; class WebView; class QProgressBar; -class QNetworkReply; struct BrowserSettings { @@ -27,31 +24,27 @@ class SlxBrowser : public QMainWindow Q_OBJECT public: SlxBrowser(BrowserSettings settings); - virtual ~SlxBrowser(); + ~SlxBrowser() override; void activity() { _activity = true; } private slots: void loadStarted(); void loadFinished(bool ok); void loadProgress(int progress); - void sslErrors(QNetworkReply * reply, const QList<QSslError> & errors); - void requestFinished(QNetworkReply *reply); - void reloadInitial(); - void maybeTriggerBack(); + void reloadInitial(); + void maybeTriggerBack(); private: BrowserSettings _settings; bool _unsupportedUri; bool _blockedSite; WebView *_browser; - QProgressBar *_progress; - QTimer _reset; - QList<QSslError> _sslErrors; - QString _normalError; - qint64 _lastPageLoad; - bool _activity; - qint64 _lastActivity; - bool _pageValid; + QProgressBar *_progress; + QTimer _reset; + qint64 _lastPageLoad; + bool _activity; + qint64 _lastActivity; + bool _pageValid; }; #endif /* SLXBROWSER_H_ */ diff --git a/src/webview.cpp b/src/webview.cpp index bbed1cf..bd2861a 100644 --- a/src/webview.cpp +++ b/src/webview.cpp @@ -1,54 +1,81 @@ #include "webview.h" -#include <QWebFrame> -#include <QNetworkReply> #include <QMessageBox> #include <QTimer> +#include <QtWebEngineWidgets/QWebEngineProfile> +#include <QtWebEngineWidgets/QWebEnginePage> +#include <QMenu> +#include <QContextMenuEvent> WebView::WebView(QWidget* parent) - : QWebView(parent), - _timer(new QTimer(this)), - _abortedDownload(false) { - _timer->setSingleShot(true); - connect(page(), SIGNAL(windowCloseRequested()), this, SLOT(windowCloseRequested())); - page()->setForwardUnsupportedContent(true); - page()->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); - connect(page(), SIGNAL(unsupportedContent(QNetworkReply*)),this,SLOT(unsupportedContent(QNetworkReply*))); - connect(page(), SIGNAL(downloadRequested(QNetworkRequest)),this,SLOT(downloadRequest(QNetworkRequest))); - connect(_timer, SIGNAL(timeout()), this, SLOT(downloadDeniedMessage())); + : QWebEngineView(parent), + _timer(new QTimer(this)), + _abortedDownload(false) { + _timer->setSingleShot(true); + // Use custom page to manage SSL errors and window behavior + _customPage = new CustomPage(this); + setPage(_customPage); + // Cancel all downloads and show message + connect(page()->profile(), &QWebEngineProfile::downloadRequested, + this, &WebView::onDownloadRequested); + connect(page(), &QWebEnginePage::windowCloseRequested, + this, &WebView::windowCloseRequested); + connect(_timer, &QTimer::timeout, this, &WebView::downloadDeniedMessage); +} + +void WebView::setIgnoreSslErrors(bool on) +{ + if (_customPage) + _customPage->setIgnoreSslErrors(on); } void WebView::windowCloseRequested() { - // If we have an old URL stored on the stack, navigate back to it, otherwise we return and nothing happens - if (_urls.empty()) - return; - QUrl url = _urls.pop(); - page()->mainFrame()->load(url); + // If we have an old URL stored on the stack, navigate back to it + if (_urls.empty()) + return; + QUrl url = _urls.pop(); + this->load(url); } -QWebView* WebView::createWindow(QWebPage::WebWindowType) + +void WebView::contextMenuEvent(QContextMenuEvent* ev) { - // Remember current URL, then return the current Web View so no new window opens - _urls.push(this->url()); - return this; + // Build the default menu + QMenu* menu = page()->createStandardContextMenu(); + + // Find and remove/hide the "View source" action + QAction* viewSource = page()->action(QWebEnginePage::ViewSource); + if (viewSource) { + viewSource->setVisible(false); + viewSource->setEnabled(false); + } + + // Show the customized menu + menu->exec(ev->globalPos()); + menu->deleteLater(); + ev->accept(); } -void WebView::unsupportedContent(QNetworkReply* rep) +QWebEngineView* WebView::createWindow(QWebEnginePage::WebWindowType) { - _abortedDownload = true; - rep->abort(); - rep->deleteLater(); - _timer->start(1); + // Remember current URL, then load into same view (ignore creating new window) + _urls.push(this->url()); + // Return this view so the request is loaded here + return this; } -void WebView::downloadRequest(QNetworkRequest) +void WebView::onDownloadRequested(QWebEngineDownloadItem* item) { - _timer->start(1); + if (!item) + return; + _abortedDownload = true; + item->cancel(); + _timer->start(1); } void WebView::downloadDeniedMessage() { - QMessageBox::warning(this->parentWidget(), QString::fromUtf8("Denied"), - QString::fromUtf8("The requested action triggered a download, which is not allowed.\n\n" - "Diese Aktion löst einen Download aus, was nicht erlaubt ist.")); + QMessageBox::warning(this->parentWidget(), QString::fromUtf8("Denied"), + QString::fromUtf8("The requested action triggered a download, which is not allowed.\n\n" + "Diese Aktion löst einen Download aus, was nicht erlaubt ist.")); } diff --git a/src/webview.h b/src/webview.h index 9d7e28d..43e293e 100644 --- a/src/webview.h +++ b/src/webview.h @@ -2,8 +2,12 @@ #define WEBVIEW_H_ #include <QStack> -#include <QWebView> -#include <QNetworkRequest> +#include <QUrl> +#include <QtWebEngineWidgets/QWebEngineView> +#include <QtWebEngineWidgets/QWebEnginePage> +#include <QtWebEngineWidgets/QWebEngineProfile> +#include <QtWebEngineWidgets/QWebEngineDownloadItem> +#include <QtWebEngineWidgets/QWebEngineCertificateError> class QNetworkReply; class QTimer; @@ -12,11 +16,29 @@ class QTimer; * Make sure pages that want to load in a new tab are actually loaded in the same page, * and remember the previous URL in case the "new tab" requests to be closed. */ -class WebView : public QWebView +class CustomPage : public QWebEnginePage +{ + Q_OBJECT +public: + explicit CustomPage(QObject *parent = nullptr) : QWebEnginePage(parent) {} + void setIgnoreSslErrors(bool on) { _ignoreSslErrors = on; } +protected: + bool certificateError(const QWebEngineCertificateError &error) override { + if (_ignoreSslErrors) { + return true; + } + return QWebEnginePage::certificateError(error); + } +private: + bool _ignoreSslErrors{false}; +}; + +class WebView : public QWebEngineView { Q_OBJECT public: - WebView(QWidget* parent = NULL); + explicit WebView(QWidget* parent = nullptr); + void setIgnoreSslErrors(bool on); bool wasAbortedDownload() { bool r = _abortedDownload; _abortedDownload = false; @@ -24,18 +46,19 @@ public: } protected: - QWebView *createWindow(QWebPage::WebWindowType); + QWebEngineView *createWindow(QWebEnginePage::WebWindowType) override; + void contextMenuEvent(QContextMenuEvent *ev) override; protected slots: - void windowCloseRequested(); - void unsupportedContent(QNetworkReply*); - void downloadRequest(QNetworkRequest); - void downloadDeniedMessage(); + void windowCloseRequested(); + void onDownloadRequested(QWebEngineDownloadItem* item); + void downloadDeniedMessage(); private: - QStack<QUrl> _urls; - QTimer *_timer; - bool _abortedDownload; + QStack<QUrl> _urls; + QTimer *_timer; + bool _abortedDownload; + CustomPage *_customPage{nullptr}; }; #endif |
