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 {