20
20
#include < QWebEnginePage>
21
21
#include < QWebChannel>
22
22
#include < QWebEngineUrlRequestInterceptor>
23
+ #include < QWebEngineUrlScheme>
24
+ #include < QWebEngineUrlSchemeHandler>
25
+ #include < QWebEngineUrlRequestJob>
26
+ #include < QMimeDatabase>
27
+ #include < QFile>
23
28
#include < QContextMenuEvent>
24
29
#include < QMenu>
25
30
#include < QRegularExpression>
52
57
53
58
#define APPNAME " BitBoxApp"
54
59
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
+
55
65
static QWebEngineView* view;
56
66
static QWebEnginePage* mainPage;
57
67
static QWebEnginePage* externalPage;
@@ -111,12 +121,60 @@ class WebEnginePage : public QWebEnginePage {
111
121
}
112
122
};
113
123
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
+
114
172
class RequestInterceptor : public QWebEngineUrlRequestInterceptor {
115
173
public:
116
174
explicit RequestInterceptor () : QWebEngineUrlRequestInterceptor() { }
117
175
void interceptRequest (QWebEngineUrlRequestInfo& info) override {
118
176
// 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" ) {
120
178
return ;
121
179
}
122
180
@@ -126,8 +184,8 @@ class RequestInterceptor : public QWebEngineUrlRequestInterceptor {
126
184
// We treat the exchange pages specially because we need to allow exchange
127
185
// widgets to load in an iframe as well as let them open external links
128
186
// 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) ));
131
189
if (onExchangePage || onBitsurancePage) {
132
190
if (info.firstPartyUrl ().toString () == info.requestUrl ().toString ()) {
133
191
// Ignore requests for certain file types (e.g., .js, .css) Somehow Moonpay loads
@@ -150,7 +208,7 @@ class RequestInterceptor : public QWebEngineUrlRequestInterceptor {
150
208
151
209
// All the requests originated in the wallet-connect section are allowed, as they are needed to
152
210
// 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) ));
154
212
if (onWCPage) {
155
213
return ;
156
214
}
@@ -280,6 +338,14 @@ int main(int argc, char *argv[])
280
338
return 0 ;
281
339
}
282
340
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
+
283
349
view = new WebEngineView ();
284
350
view->setGeometry (0 , 0 , a.devicePixelRatio () * view->width (), a.devicePixelRatio () * view->height ());
285
351
view->setMinimumSize (360 , 375 );
@@ -369,6 +435,11 @@ int main(int argc, char *argv[])
369
435
370
436
RequestInterceptor interceptor;
371
437
view->page ()->profile ()->setUrlRequestInterceptor (&interceptor);
438
+
439
+ // For manual mimetype resolution.
440
+ SchemeHandler schemeHandler;
441
+ view->page ()->profile ()->installUrlSchemeHandler (scheme, &schemeHandler);
442
+
372
443
QObject::connect (
373
444
view->page (),
374
445
&QWebEnginePage::featurePermissionRequested,
@@ -381,11 +452,12 @@ int main(int argc, char *argv[])
381
452
QWebEnginePage::PermissionGrantedByUser);
382
453
}
383
454
});
455
+
384
456
QWebChannel channel;
385
457
channel.registerObject (" backend" , webClass);
386
458
view->page ()->setWebChannel (&channel);
387
459
view->show ();
388
- view->load (QUrl (" qrc :/index.html" ));
460
+ view->load (QUrl (QString ( " %1 :/index.html" ). arg (scheme) ));
389
461
390
462
// Create TrayIcon
391
463
{
0 commit comments