From c28da40405f122a88fcd9cdf6aa44e0fc3298144 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Thu, 21 Nov 2024 09:33:25 +0100 Subject: [PATCH] qt: force mime type resolution by filename extension only 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 mimetype resolution by filename extension only, to avoid the system mimetype database from breaking the app. --- CHANGELOG.md | 1 + frontends/qt/main.cpp | 44 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27997d447a..2003e279da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ # 4.46.1 - Fix Android app crash on old Android versions +- Fix Linux blank screen issue related to the local mimetype database # 4.46.0 - Android: enable export logs feature diff --git a/frontends/qt/main.cpp b/frontends/qt/main.cpp index 7a34a4d00b..dee2894886 100644 --- a/frontends/qt/main.cpp +++ b/frontends/qt/main.cpp @@ -20,6 +20,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -52,6 +56,9 @@ #define APPNAME "BitBoxApp" +// Custom scheme so we can intercept and serve local files. +static const char* scheme = "bitboxapp"; + static QWebEngineView* view; static QWebEnginePage* mainPage; static QWebEnginePage* externalPage; @@ -111,12 +118,37 @@ class WebEnginePage : public QWebEnginePage { } }; +// Custom scheme handler so we can force mime type detection by filename extension only. +// 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: + void requestStarted(QWebEngineUrlRequestJob *request) override { + QUrl url = request->requestUrl(); + QString path = url.path(); + + QMimeDatabase mimeDb; + // Force matching by extension only, not using any mimetype database. + QMimeType mimeType = mimeDb.mimeTypeForFile(path, QMimeDatabase::MatchExtension); + // Read resource from QRC (local static assets). + auto file = new QFile(":" + path); + if (file->open(QIODevice::ReadOnly)) { + request->reply(mimeType.name().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 +158,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 +182,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; } @@ -369,6 +401,8 @@ int main(int argc, char *argv[]) RequestInterceptor interceptor; view->page()->profile()->setUrlRequestInterceptor(&interceptor); + SchemeHandler schemeHandler; + view->page()->profile()->installUrlSchemeHandler(scheme, &schemeHandler); QObject::connect( view->page(), &QWebEnginePage::featurePermissionRequested, @@ -385,7 +419,7 @@ int main(int argc, char *argv[]) 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 {