Skip to content

Commit d78380f

Browse files
committed
qt: force mime type resolution by extension
Fixes BitBoxSwiss#2684 and hopefully BitBoxSwiss#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.
1 parent 67352e1 commit d78380f

File tree

2 files changed

+80
-5
lines changed

2 files changed

+80
-5
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
# 4.46.2
6+
- Fix Linux blank screen issue related to the local mimetype database
7+
58
# 4.46.1
69
- Fix Android app crash on old Android versions
710

frontends/qt/main.cpp

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
#include <QWebEnginePage>
2121
#include <QWebChannel>
2222
#include <QWebEngineUrlRequestInterceptor>
23+
#include <QWebEngineUrlScheme>
24+
#include <QWebEngineUrlSchemeHandler>
25+
#include <QWebEngineUrlRequestJob>
26+
#include <QMimeDatabase>
27+
#include <QFile>
2328
#include <QContextMenuEvent>
2429
#include <QMenu>
2530
#include <QRegularExpression>
@@ -52,6 +57,11 @@
5257

5358
#define APPNAME "BitBoxApp"
5459

60+
// Custom scheme so we can intercept and serve local files.
61+
// If you change this, MoonPay may break, as this scheme is whitelisted as a frame ancestor in
62+
// their CSP response headers.
63+
static const char* scheme = "bitboxapp";
64+
5565
static QWebEngineView* view;
5666
static QWebEnginePage* mainPage;
5767
static QWebEnginePage* externalPage;
@@ -111,12 +121,60 @@ class WebEnginePage : public QWebEnginePage {
111121
}
112122
};
113123

124+
// Custom scheme handler so we can force mime type resolution manually.
125+
// Without this, on linux the local mimetype database can mess with the mimetype resolution.
126+
// Programs like monodevelop or Steam/Proton install weird mime types making for example our
127+
// .js or .html files be served with the wrogn mimetype, resulting in the webengine displaying a
128+
// blank page only.
129+
class SchemeHandler : public QWebEngineUrlSchemeHandler {
130+
public:
131+
// Similar like the built-in qrc scheme handler, but with hardcoded mime-types.
132+
// https://github.com/qt/qtwebengine/blob/v6.2.4/src/core/net/qrc_url_scheme_handler.cpp
133+
void requestStarted(QWebEngineUrlRequestJob *request) override {
134+
QByteArray requestMethod = request->requestMethod();
135+
if (requestMethod != "GET") {
136+
request->fail(QWebEngineUrlRequestJob::RequestDenied);
137+
return;
138+
}
139+
QUrl url = request->requestUrl();
140+
QString path = url.path();
141+
142+
QMimeDatabase mimeDb;
143+
144+
QMimeType mimeType = mimeDb.mimeTypeForFile(path);
145+
146+
QString hardcodedMimeType;
147+
if (path.endsWith(".html")) {
148+
hardcodedMimeType = "text/html";
149+
} else if (path.endsWith(".js")) {
150+
hardcodedMimeType = "application/javascript";
151+
} else if (path.endsWith(".css")) {
152+
hardcodedMimeType = "text/css";
153+
} else if (path.endsWith(".svg")) {
154+
hardcodedMimeType = "image/svg+xml";
155+
} else {
156+
// Fallback to detected mimetype.
157+
hardcodedMimeType = mimeType.name();
158+
}
159+
160+
// Read resource from QRC (local static assets).
161+
auto file = new QFile(":" + path);
162+
if (file->open(QIODevice::ReadOnly)) {
163+
request->reply(hardcodedMimeType.toUtf8(), file);
164+
file->setParent(request);
165+
} else {
166+
delete file;
167+
request->fail(QWebEngineUrlRequestJob::UrlNotFound);
168+
}
169+
}
170+
};
171+
114172
class RequestInterceptor : public QWebEngineUrlRequestInterceptor {
115173
public:
116174
explicit RequestInterceptor() : QWebEngineUrlRequestInterceptor() { }
117175
void interceptRequest(QWebEngineUrlRequestInfo& info) override {
118176
// Do not block qrc:/ local pages or js blobs
119-
if (info.requestUrl().scheme() == "qrc" || info.requestUrl().scheme() == "blob") {
177+
if (info.requestUrl().scheme() == scheme || info.requestUrl().scheme() == "blob") {
120178
return;
121179
}
122180

@@ -126,8 +184,8 @@ class RequestInterceptor : public QWebEngineUrlRequestInterceptor {
126184
// We treat the exchange pages specially because we need to allow exchange
127185
// widgets to load in an iframe as well as let them open external links
128186
// in a browser.
129-
bool onExchangePage = currentUrl.contains(QRegularExpression("^qrc:/exchange/.*$"));
130-
bool onBitsurancePage = currentUrl.contains(QRegularExpression("^qrc:/bitsurance/.*$"));
187+
bool onExchangePage = currentUrl.contains(QRegularExpression(QString("^%1:/exchange/.*$").arg(scheme)));
188+
bool onBitsurancePage = currentUrl.contains(QRegularExpression(QString("^%1:/bitsurance/.*$").arg(scheme)));
131189
if (onExchangePage || onBitsurancePage) {
132190
if (info.firstPartyUrl().toString() == info.requestUrl().toString()) {
133191
// Ignore requests for certain file types (e.g., .js, .css) Somehow Moonpay loads
@@ -150,7 +208,7 @@ class RequestInterceptor : public QWebEngineUrlRequestInterceptor {
150208

151209
// All the requests originated in the wallet-connect section are allowed, as they are needed to
152210
// load the Dapp logos and it is not easy to filter out non-images requests.
153-
bool onWCPage = currentUrl.contains(QRegularExpression(R"(^qrc:/account/[^\/]+/wallet-connect/.*$)"));
211+
bool onWCPage = currentUrl.contains(QRegularExpression(QString(R"(^%1:/account/[^\/]+/wallet-connect/.*$)").arg(scheme)));
154212
if (onWCPage) {
155213
return;
156214
}
@@ -280,6 +338,14 @@ int main(int argc, char *argv[])
280338
return 0;
281339
}
282340

341+
QWebEngineUrlScheme bbappScheme(scheme);
342+
bbappScheme.setSyntax(QWebEngineUrlScheme::Syntax::HostAndPort);
343+
bbappScheme.setFlags(
344+
QWebEngineUrlScheme::LocalScheme |
345+
QWebEngineUrlScheme::SecureScheme |
346+
QWebEngineUrlScheme::LocalAccessAllowed);
347+
QWebEngineUrlScheme::registerScheme(bbappScheme);
348+
283349
view = new WebEngineView();
284350
view->setGeometry(0, 0, a.devicePixelRatio() * view->width(), a.devicePixelRatio() * view->height());
285351
view->setMinimumSize(360, 375);
@@ -369,6 +435,11 @@ int main(int argc, char *argv[])
369435

370436
RequestInterceptor interceptor;
371437
view->page()->profile()->setUrlRequestInterceptor(&interceptor);
438+
439+
// For manual mimetype resolution.
440+
SchemeHandler schemeHandler;
441+
view->page()->profile()->installUrlSchemeHandler(scheme, &schemeHandler);
442+
372443
QObject::connect(
373444
view->page(),
374445
&QWebEnginePage::featurePermissionRequested,
@@ -381,11 +452,12 @@ int main(int argc, char *argv[])
381452
QWebEnginePage::PermissionGrantedByUser);
382453
}
383454
});
455+
384456
QWebChannel channel;
385457
channel.registerObject("backend", webClass);
386458
view->page()->setWebChannel(&channel);
387459
view->show();
388-
view->load(QUrl("qrc:/index.html"));
460+
view->load(QUrl(QString("%1:/index.html").arg(scheme)));
389461

390462
// Create TrayIcon
391463
{

0 commit comments

Comments
 (0)