From d78380f45135296f11e61dabeb2c5008bcd9aaa8 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Thu, 21 Nov 2024 09:33:25 +0100 Subject: [PATCH 1/2] qt: force mime type resolution by extension Fixes #2684 and hopefully #3061. Without this, on linux the local mimetype database (influenced by various packages installed on the system) can mess with the mimetype resolution of our .html/.js/.css files served locally, resulting in a blank page. This manually intercepts local requests and forces the correct mimetype for a set of hardcoded extensions. --- CHANGELOG.md | 3 ++ frontends/qt/main.cpp | 82 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27997d447a..0004fc4ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +# 4.46.2 +- Fix Linux blank screen issue related to the local mimetype database + # 4.46.1 - Fix Android app crash on old Android versions diff --git a/frontends/qt/main.cpp b/frontends/qt/main.cpp index 7a34a4d00b..3c28524338 100644 --- a/frontends/qt/main.cpp +++ b/frontends/qt/main.cpp @@ -20,6 +20,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -52,6 +57,11 @@ #define APPNAME "BitBoxApp" +// Custom scheme so we can intercept and serve local files. +// If you change this, MoonPay may break, as this scheme is whitelisted as a frame ancestor in +// their CSP response headers. +static const char* scheme = "bitboxapp"; + static QWebEngineView* view; static QWebEnginePage* mainPage; static QWebEnginePage* externalPage; @@ -111,12 +121,60 @@ class WebEnginePage : public QWebEnginePage { } }; +// Custom scheme handler so we can force mime type resolution manually. +// Without this, on linux the local mimetype database can mess with the mimetype resolution. +// Programs like monodevelop or Steam/Proton install weird mime types making for example our +// .js or .html files be served with the wrogn mimetype, resulting in the webengine displaying a +// blank page only. +class SchemeHandler : public QWebEngineUrlSchemeHandler { +public: + // Similar like the built-in qrc scheme handler, but with hardcoded mime-types. + // https://github.com/qt/qtwebengine/blob/v6.2.4/src/core/net/qrc_url_scheme_handler.cpp + void requestStarted(QWebEngineUrlRequestJob *request) override { + QByteArray requestMethod = request->requestMethod(); + if (requestMethod != "GET") { + request->fail(QWebEngineUrlRequestJob::RequestDenied); + return; + } + QUrl url = request->requestUrl(); + QString path = url.path(); + + QMimeDatabase mimeDb; + + QMimeType mimeType = mimeDb.mimeTypeForFile(path); + + QString hardcodedMimeType; + if (path.endsWith(".html")) { + hardcodedMimeType = "text/html"; + } else if (path.endsWith(".js")) { + hardcodedMimeType = "application/javascript"; + } else if (path.endsWith(".css")) { + hardcodedMimeType = "text/css"; + } else if (path.endsWith(".svg")) { + hardcodedMimeType = "image/svg+xml"; + } else { + // Fallback to detected mimetype. + hardcodedMimeType = mimeType.name(); + } + + // Read resource from QRC (local static assets). + auto file = new QFile(":" + path); + if (file->open(QIODevice::ReadOnly)) { + request->reply(hardcodedMimeType.toUtf8(), file); + file->setParent(request); + } else { + delete file; + request->fail(QWebEngineUrlRequestJob::UrlNotFound); + } + } +}; + class RequestInterceptor : public QWebEngineUrlRequestInterceptor { public: explicit RequestInterceptor() : QWebEngineUrlRequestInterceptor() { } void interceptRequest(QWebEngineUrlRequestInfo& info) override { // Do not block qrc:/ local pages or js blobs - if (info.requestUrl().scheme() == "qrc" || info.requestUrl().scheme() == "blob") { + if (info.requestUrl().scheme() == scheme || info.requestUrl().scheme() == "blob") { return; } @@ -126,8 +184,8 @@ class RequestInterceptor : public QWebEngineUrlRequestInterceptor { // We treat the exchange pages specially because we need to allow exchange // widgets to load in an iframe as well as let them open external links // in a browser. - bool onExchangePage = currentUrl.contains(QRegularExpression("^qrc:/exchange/.*$")); - bool onBitsurancePage = currentUrl.contains(QRegularExpression("^qrc:/bitsurance/.*$")); + bool onExchangePage = currentUrl.contains(QRegularExpression(QString("^%1:/exchange/.*$").arg(scheme))); + bool onBitsurancePage = currentUrl.contains(QRegularExpression(QString("^%1:/bitsurance/.*$").arg(scheme))); if (onExchangePage || onBitsurancePage) { if (info.firstPartyUrl().toString() == info.requestUrl().toString()) { // Ignore requests for certain file types (e.g., .js, .css) Somehow Moonpay loads @@ -150,7 +208,7 @@ class RequestInterceptor : public QWebEngineUrlRequestInterceptor { // All the requests originated in the wallet-connect section are allowed, as they are needed to // load the Dapp logos and it is not easy to filter out non-images requests. - bool onWCPage = currentUrl.contains(QRegularExpression(R"(^qrc:/account/[^\/]+/wallet-connect/.*$)")); + bool onWCPage = currentUrl.contains(QRegularExpression(QString(R"(^%1:/account/[^\/]+/wallet-connect/.*$)").arg(scheme))); if (onWCPage) { return; } @@ -280,6 +338,14 @@ int main(int argc, char *argv[]) return 0; } + QWebEngineUrlScheme bbappScheme(scheme); + bbappScheme.setSyntax(QWebEngineUrlScheme::Syntax::HostAndPort); + bbappScheme.setFlags( + QWebEngineUrlScheme::LocalScheme | + QWebEngineUrlScheme::SecureScheme | + QWebEngineUrlScheme::LocalAccessAllowed); + QWebEngineUrlScheme::registerScheme(bbappScheme); + view = new WebEngineView(); view->setGeometry(0, 0, a.devicePixelRatio() * view->width(), a.devicePixelRatio() * view->height()); view->setMinimumSize(360, 375); @@ -369,6 +435,11 @@ int main(int argc, char *argv[]) RequestInterceptor interceptor; view->page()->profile()->setUrlRequestInterceptor(&interceptor); + + // For manual mimetype resolution. + SchemeHandler schemeHandler; + view->page()->profile()->installUrlSchemeHandler(scheme, &schemeHandler); + QObject::connect( view->page(), &QWebEnginePage::featurePermissionRequested, @@ -381,11 +452,12 @@ int main(int argc, char *argv[]) QWebEnginePage::PermissionGrantedByUser); } }); + QWebChannel channel; channel.registerObject("backend", webClass); view->page()->setWebChannel(&channel); view->show(); - view->load(QUrl("qrc:/index.html")); + view->load(QUrl(QString("%1:/index.html").arg(scheme))); // Create TrayIcon { From 6b2ddac98ae1007a3979c8bdcde9dfe9ac10cf31 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Fri, 22 Nov 2024 10:45:12 +0100 Subject: [PATCH 2/2] backend: update to version 4.46.2 --- backend/update.go | 2 +- frontends/android/BitBoxApp/app/build.gradle | 4 ++-- frontends/ios/BitBoxApp/Config.xcconfig | 4 ++-- frontends/qt/Makefile | 4 ++-- frontends/qt/resources/MacOS/Info.plist | 4 ++-- frontends/qt/setup.nsi | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/update.go b/backend/update.go index f4dc9cb78f..7fc260d92e 100644 --- a/backend/update.go +++ b/backend/update.go @@ -27,7 +27,7 @@ const updateFileURL = "https://bitboxapp.shiftcrypto.io/desktop.json" var ( // Version of the backend as displayed to the user. - Version = semver.NewSemVer(4, 46, 1) + Version = semver.NewSemVer(4, 46, 2) ) // UpdateFile is retrieved from the server. diff --git a/frontends/android/BitBoxApp/app/build.gradle b/frontends/android/BitBoxApp/app/build.gradle index 3bdbbad1ff..c11b8a7a2c 100644 --- a/frontends/android/BitBoxApp/app/build.gradle +++ b/frontends/android/BitBoxApp/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "ch.shiftcrypto.bitboxapp" minSdkVersion 21 targetSdkVersion 34 - versionCode 56 - versionName "android-4.46.1" + versionCode 57 + versionName "android-4.46.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/frontends/ios/BitBoxApp/Config.xcconfig b/frontends/ios/BitBoxApp/Config.xcconfig index 756f1f5db9..434ce8d08f 100644 --- a/frontends/ios/BitBoxApp/Config.xcconfig +++ b/frontends/ios/BitBoxApp/Config.xcconfig @@ -2,8 +2,8 @@ // https://help.apple.com/xcode/#/dev745c5c974 // App version -MARKETING_VERSION = 4.46.1 +MARKETING_VERSION = 4.46.2 // Build number, increment this for every separate publication, // even if the app version is the same. -CURRENT_PROJECT_VERSION = 4 +CURRENT_PROJECT_VERSION = 5 diff --git a/frontends/qt/Makefile b/frontends/qt/Makefile index d31a0a9433..9f41243562 100644 --- a/frontends/qt/Makefile +++ b/frontends/qt/Makefile @@ -37,8 +37,8 @@ linux: cp resources/linux/usr/share/icons/hicolor/128x128/apps/bitbox.png build/linux-tmp mkdir build/tmp-deb/opt/ cp -aR build/linux-tmp build/tmp-deb/opt/bitbox - cd build/linux && fpm --after-install ../../resources/deb-afterinstall.sh -s dir -t deb -n bitbox -v 4.46.1 -C ../tmp-deb/ - cd build/linux && fpm --after-install ../../resources/deb-afterinstall.sh -s dir -t rpm -n bitbox -v 4.46.1 -C ../tmp-deb/ + cd build/linux && fpm --after-install ../../resources/deb-afterinstall.sh -s dir -t deb -n bitbox -v 4.46.2 -C ../tmp-deb/ + cd build/linux && fpm --after-install ../../resources/deb-afterinstall.sh -s dir -t rpm -n bitbox -v 4.46.2 -C ../tmp-deb/ # create AppImage cd build/linux-tmp && /opt/linuxdeployqt-continuous-x86_64.AppImage BitBox -appimage -unsupported-allow-new-glibc mv build/linux-tmp/BitBoxApp-*-x86_64.AppImage build/linux/ diff --git a/frontends/qt/resources/MacOS/Info.plist b/frontends/qt/resources/MacOS/Info.plist index a43c572a2d..10f0476ec4 100644 --- a/frontends/qt/resources/MacOS/Info.plist +++ b/frontends/qt/resources/MacOS/Info.plist @@ -21,10 +21,10 @@ APPL CFBundleVersion - 4.46.1 + 4.46.2 CFBundleShortVersionString - 4.46.1 + 4.46.2 CFBundleSignature ???? diff --git a/frontends/qt/setup.nsi b/frontends/qt/setup.nsi index 277221fb95..7b420b4d47 100755 --- a/frontends/qt/setup.nsi +++ b/frontends/qt/setup.nsi @@ -22,7 +22,7 @@ SetCompressor /SOLID lzma # General Symbol Definitions !define REGKEY "SOFTWARE\$(^Name)" -!define VERSION 4.46.1.0 +!define VERSION 4.46.2.0 !define COMPANY "Shift Crypto AG" !define URL https://github.com/BitBoxSwiss/bitbox-wallet-app/releases/ !define BINDIR "build\windows"