Skip to content

Commit

Permalink
qt: force mime type resolution by extension
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
benma committed Nov 22, 2024
1 parent 67352e1 commit d78380f
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
82 changes: 77 additions & 5 deletions frontends/qt/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
#include <QWebEnginePage>
#include <QWebChannel>
#include <QWebEngineUrlRequestInterceptor>
#include <QWebEngineUrlScheme>
#include <QWebEngineUrlSchemeHandler>
#include <QWebEngineUrlRequestJob>
#include <QMimeDatabase>
#include <QFile>
#include <QContextMenuEvent>
#include <QMenu>
#include <QRegularExpression>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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
{
Expand Down

0 comments on commit d78380f

Please sign in to comment.