diff options
Diffstat (limited to 'tests/auto/widgets/origins/tst_origins.cpp')
-rw-r--r-- | tests/auto/widgets/origins/tst_origins.cpp | 492 |
1 files changed, 449 insertions, 43 deletions
diff --git a/tests/auto/widgets/origins/tst_origins.cpp b/tests/auto/widgets/origins/tst_origins.cpp index 61d54e6de..4e415af90 100644 --- a/tests/auto/widgets/origins/tst_origins.cpp +++ b/tests/auto/widgets/origins/tst_origins.cpp @@ -31,15 +31,100 @@ #include <QtCore/qfile.h> #include <QtTest/QtTest> #include <QtWebEngineCore/qwebengineurlrequestjob.h> +#include <QtWebEngineCore/qwebengineurlscheme.h> #include <QtWebEngineCore/qwebengineurlschemehandler.h> #include <QtWebEngineWidgets/qwebenginepage.h> #include <QtWebEngineWidgets/qwebengineprofile.h> #include <QtWebEngineWidgets/qwebenginesettings.h> +#if defined(WEBSOCKETS) +#include <QtWebSockets/qwebsocket.h> +#include <QtWebSockets/qwebsocketserver.h> +#include <QtWebChannel/qwebchannel.h> +#endif +#include <QtWidgets/qaction.h> #define QSL QStringLiteral #define QBAL QByteArrayLiteral #define THIS_DIR TESTS_SOURCE_DIR "origins/" +void registerSchemes() +{ + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax")); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-Secure")); + scheme.setFlags(QWebEngineUrlScheme::SecureScheme); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-Secure-ServiceWorkersAllowed")); + scheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::ServiceWorkersAllowed); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-Local")); + scheme.setFlags(QWebEngineUrlScheme::LocalScheme); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-LocalAccessAllowed")); + scheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-NoAccessAllowed")); + scheme.setFlags(QWebEngineUrlScheme::NoAccessAllowed); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-ServiceWorkersAllowed")); + scheme.setFlags(QWebEngineUrlScheme::ServiceWorkersAllowed); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-ViewSourceAllowed")); + scheme.setFlags(QWebEngineUrlScheme::ViewSourceAllowed); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("HostSyntax")); + scheme.setSyntax(QWebEngineUrlScheme::Syntax::Host); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("HostSyntax-ContentSecurityPolicyIgnored")); + scheme.setSyntax(QWebEngineUrlScheme::Syntax::Host); + scheme.setFlags(QWebEngineUrlScheme::ContentSecurityPolicyIgnored); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("HostAndPortSyntax")); + scheme.setSyntax(QWebEngineUrlScheme::Syntax::HostAndPort); + scheme.setDefaultPort(42); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("HostPortAndUserInformationSyntax")); + scheme.setSyntax(QWebEngineUrlScheme::Syntax::HostPortAndUserInformation); + scheme.setDefaultPort(42); + QWebEngineUrlScheme::registerScheme(scheme); + } +} +Q_CONSTRUCTOR_FUNCTION(registerSchemes) + class TstUrlSchemeHandler final : public QWebEngineUrlSchemeHandler { Q_OBJECT @@ -47,6 +132,19 @@ public: TstUrlSchemeHandler(QWebEngineProfile *profile) { profile->installUrlSchemeHandler(QBAL("tst"), this); + + profile->installUrlSchemeHandler(QBAL("PathSyntax"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-Secure"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-Secure-ServiceWorkersAllowed"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-Local"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-LocalAccessAllowed"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-NoAccessAllowed"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-ServiceWorkersAllowed"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-ViewSourceAllowed"), this); + profile->installUrlSchemeHandler(QBAL("HostSyntax"), this); + profile->installUrlSchemeHandler(QBAL("HostSyntax-ContentSecurityPolicyIgnored"), this); + profile->installUrlSchemeHandler(QBAL("HostAndPortSyntax"), this); + profile->installUrlSchemeHandler(QBAL("HostPortAndUserInformationSyntax"), this); } private: @@ -59,7 +157,10 @@ private: job->fail(QWebEngineUrlRequestJob::RequestFailed); return; } - job->reply(QBAL("text/html"), file); + QByteArray mimeType = QBAL("text/html"); + if (pathSuffix.endsWith(QSL(".js"))) + mimeType = QBAL("application/javascript"); + job->reply(mimeType, file); } }; @@ -67,35 +168,56 @@ class tst_Origins final : public QObject { Q_OBJECT private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void jsUrlCanon(); void jsUrlOrigin(); void subdirWithAccess(); void subdirWithoutAccess(); void mixedSchemes(); + void mixedSchemesWithCsp(); + void mixedXHR(); +#if defined(WEBSOCKETS) void webSocket(); +#endif void dedicatedWorker(); void sharedWorker(); void serviceWorker(); + void viewSource(); + void createObjectURL(); private: bool load(const QUrl &url) { - QSignalSpy spy(&m_page, &QWebEnginePage::loadFinished); - m_page.load(url); - return (!spy.empty() || spy.wait()) + QSignalSpy spy(m_page, &QWebEnginePage::loadFinished); + m_page->load(url); + return (!spy.empty() || spy.wait(20000)) && spy.front().value(0).toBool(); } QVariant eval(const QString &code) { - return evaluateJavaScriptSync(&m_page, code); + return evaluateJavaScriptSync(m_page, code); } QWebEngineProfile m_profile; - QWebEnginePage m_page{&m_profile}; - TstUrlSchemeHandler m_handler{&m_profile}; + QWebEnginePage *m_page = nullptr; + TstUrlSchemeHandler *m_handler = nullptr; }; +void tst_Origins::initTestCase() +{ + m_page = new QWebEnginePage(&m_profile, nullptr); + m_handler = new TstUrlSchemeHandler(&m_profile); +} + +void tst_Origins::cleanupTestCase() +{ + delete m_handler; + delete m_page; +} + // Test URL parsing and canonicalization in Blink. The implementation of this // part is mostly shared between Blink and Chromium proper. void tst_Origins::jsUrlCanon() @@ -121,17 +243,42 @@ void tst_Origins::jsUrlCanon() QCOMPARE(eval(QSL("new URL(\"file:///foo/bar\").href")), QVariant(QSL("file:///foo/bar"))); #endif - // The qrc scheme is a 'dumb' URL, having only a path and nothing else. + // The qrc scheme is a PathSyntax scheme, having only a path and nothing else. QCOMPARE(eval(QSL("new URL(\"qrc:foo/bar\").href")), QVariant(QSL("qrc:foo/bar"))); QCOMPARE(eval(QSL("new URL(\"qrc:/foo/bar\").href")), QVariant(QSL("qrc:/foo/bar"))); QCOMPARE(eval(QSL("new URL(\"qrc://foo/bar\").href")), QVariant(QSL("qrc://foo/bar"))); QCOMPARE(eval(QSL("new URL(\"qrc:///foo/bar\").href")), QVariant(QSL("qrc:///foo/bar"))); - // Same for custom schemes. + // Same for unregistered schemes. QCOMPARE(eval(QSL("new URL(\"tst:foo/bar\").href")), QVariant(QSL("tst:foo/bar"))); QCOMPARE(eval(QSL("new URL(\"tst:/foo/bar\").href")), QVariant(QSL("tst:/foo/bar"))); QCOMPARE(eval(QSL("new URL(\"tst://foo/bar\").href")), QVariant(QSL("tst://foo/bar"))); QCOMPARE(eval(QSL("new URL(\"tst:///foo/bar\").href")), QVariant(QSL("tst:///foo/bar"))); + + // A HostSyntax scheme is like http without the port & user information. + QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo/bar\").href")), QVariant(QSL("hostsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo:42/bar\").href")), QVariant(QSL("hostsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostSyntax:a:b@foo/bar\").href")), QVariant(QSL("hostsyntax://foo/bar"))); + + // A HostAndPortSyntax scheme is like http without the user information. + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo/bar\").href")), + QVariant(QSL("hostandportsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:41/bar\").href")), + QVariant(QSL("hostandportsyntax://foo:41/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:42/bar\").href")), + QVariant(QSL("hostandportsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:a:b@foo/bar\").href")), + QVariant(QSL("hostandportsyntax://foo/bar"))); + + // A HostPortAndUserInformationSyntax scheme is exactly like http. + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo/bar\").href")), + QVariant(QSL("hostportanduserinformationsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:41/bar\").href")), + QVariant(QSL("hostportanduserinformationsyntax://foo:41/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:42/bar\").href")), + QVariant(QSL("hostportanduserinformationsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:a:b@foo/bar\").href")), + QVariant(QSL("hostportanduserinformationsyntax://a:b@foo/bar"))); } // Test origin serialization in Blink, implemented by blink::KURL and @@ -154,9 +301,29 @@ void tst_Origins::jsUrlOrigin() QCOMPARE(eval(QSL("new URL(\"qrc:/crysis.css\").origin")), QVariant(QSL("qrc://"))); QCOMPARE(eval(QSL("new URL(\"qrc://foo.com/crysis.css\").origin")), QVariant(QSL("qrc://"))); - // Same with custom schemes. + // Same with unregistered schemes. QCOMPARE(eval(QSL("new URL(\"tst:/banana\").origin")), QVariant(QSL("tst://"))); QCOMPARE(eval(QSL("new URL(\"tst://foo.com/banana\").origin")), QVariant(QSL("tst://"))); + + // The non-PathSyntax schemes should have hosts and potentially ports. + QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo:41/bar\").origin")), + QVariant(QSL("hostsyntax://foo"))); + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:41/bar\").origin")), + QVariant(QSL("hostandportsyntax://foo:41"))); + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:42/bar\").origin")), + QVariant(QSL("hostandportsyntax://foo"))); + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:41/bar\").origin")), + QVariant(QSL("hostportanduserinformationsyntax://foo:41"))); + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:42/bar\").origin")), + QVariant(QSL("hostportanduserinformationsyntax://foo"))); + + // A PathSyntax scheme should have a 'universal' origin. + QCOMPARE(eval(QSL("new URL(\"PathSyntax:foo\").origin")), + QVariant(QSL("pathsyntax://"))); + + // The NoAccessAllowed flag forces opaque origins. + QCOMPARE(eval(QSL("new URL(\"PathSyntax-NoAccessAllowed:foo\").origin")), + QVariant(QSL("null"))); } class ScopedAttribute { @@ -185,7 +352,7 @@ private: // demonstrate the difference with Firefox where such access is not allowed. void tst_Origins::subdirWithAccess() { - ScopedAttribute sa(m_page.settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true); + ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true); QVERIFY(load(QSL("file:" THIS_DIR "resources/subdir/index.html"))); QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); @@ -211,7 +378,7 @@ void tst_Origins::subdirWithAccess() // - the blink::SecurityOrigin::BlockLocalAccessFromLocalOrigin() method. void tst_Origins::subdirWithoutAccess() { - ScopedAttribute sa(m_page.settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false); + ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false); QVERIFY(load(QSL("file:" THIS_DIR "resources/subdir/index.html"))); QCOMPARE(eval(QSL("msg[0]")), QVariant()); @@ -226,46 +393,200 @@ void tst_Origins::subdirWithoutAccess() QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); } -// Try to mix schemes, for example by loading the main page over file with an -// iframe over qrc. This should be forbidden. +// Load the main page over one scheme with an iframe over another scheme. +// +// For file and qrc schemes, the iframe should load but it should not be +// possible for scripts in different frames to interact. +// +// Additionally for unregistered custom schemes and custom schemes without +// LocalAccessAllowed it should not be possible to load an iframe over the +// file: scheme. void tst_Origins::mixedSchemes() { - QVERIFY(load(QSL("file:" THIS_DIR "resources/mixed_qrc.html"))); - QCOMPARE(eval(QSL("msg")), QVariant()); - QVERIFY(load(QSL("file:" THIS_DIR "resources/mixed_tst.html"))); - QCOMPARE(eval(QSL("msg")), QVariant()); - - QVERIFY(load(QSL("qrc:/resources/mixed_qrc.html"))); - QCOMPARE(eval(QSL("msg")), QVariant(QSL("mixed"))); - QVERIFY(load(QSL("qrc:/resources/mixed_tst.html"))); - QCOMPARE(eval(QSL("msg")), QVariant()); - - QVERIFY(load(QSL("tst:/resources/mixed_qrc.html"))); - QCOMPARE(eval(QSL("msg")), QVariant()); - QVERIFY(load(QSL("tst:/resources/mixed_tst.html"))); - QCOMPARE(eval(QSL("msg")), QVariant(QSL("mixed"))); + QVERIFY(load(QSL("file:" THIS_DIR "resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(load(QSL("qrc:/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(load(QSL("tst:/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); + eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + + QVERIFY(load(QSL("PathSyntax:/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); + eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(load(QSL("PathSyntax-LocalAccessAllowed:/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); + eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(load(QSL("HostSyntax://a/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); +} + +// Like mixedSchemes but adds a Content-Security-Policy: frame-src 'none' header. +void tst_Origins::mixedSchemesWithCsp() +{ + QVERIFY(load(QSL("HostSyntax://a/resources/mixedSchemesWithCsp.html"))); + eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(load(QSL("HostSyntax-ContentSecurityPolicyIgnored://a/resources/mixedSchemesWithCsp.html"))); + eval(QSL("setIFrameUrl('HostSyntax-ContentSecurityPolicyIgnored://a/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + eval(QSL("setIFrameUrl('HostSyntax-ContentSecurityPolicyIgnored://b/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); } +// Load the main page over one scheme, then make an XMLHttpRequest to a +// different scheme. +// +// XMLHttpRequests can only be made to http, https, data, and chrome. +void tst_Origins::mixedXHR() +{ + QVERIFY(load(QSL("file:" THIS_DIR "resources/mixedXHR.html"))); + eval(QSL("sendXHR('file:" THIS_DIR "resources/mixedXHR.txt')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); + eval(QSL("sendXHR('qrc:/resources/mixedXHR.txt')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("error"))); + eval(QSL("sendXHR('tst:/resources/mixedXHR.txt')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("error"))); + eval(QSL("sendXHR('data:,ok')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); + + QVERIFY(load(QSL("qrc:/resources/mixedXHR.html"))); + eval(QSL("sendXHR('file:" THIS_DIR "resources/mixedXHR.txt')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); + eval(QSL("sendXHR('qrc:/resources/mixedXHR.txt')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); + eval(QSL("sendXHR('tst:/resources/mixedXHR.txt')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("error"))); + eval(QSL("sendXHR('data:,ok')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); + + QVERIFY(load(QSL("tst:/resources/mixedXHR.html"))); + eval(QSL("sendXHR('file:" THIS_DIR "resources/mixedXHR.txt')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("error"))); + eval(QSL("sendXHR('qrc:/resources/mixedXHR.txt')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("error"))); + eval(QSL("sendXHR('tst:/resources/mixedXHR.txt')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); + eval(QSL("sendXHR('data:,ok')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); +} + +#if defined(WEBSOCKETS) +class EchoServer : public QObject { + Q_OBJECT + Q_PROPERTY(QUrl url READ url NOTIFY urlChanged) +public: + EchoServer() : webSocketServer(QSL("EchoServer"), QWebSocketServer::NonSecureMode) + { + connect(&webSocketServer, &QWebSocketServer::newConnection, this, &EchoServer::onNewConnection); + } + + bool listen() + { + if (webSocketServer.listen(QHostAddress::Any)) { + Q_EMIT urlChanged(); + return true; + } + return false; + } + + QUrl url() const + { + return webSocketServer.serverUrl(); + } + +Q_SIGNALS: + void urlChanged(); + +private: + void onNewConnection() + { + QWebSocket *socket = webSocketServer.nextPendingConnection(); + connect(socket, &QWebSocket::textMessageReceived, this, &EchoServer::onTextMessageReceived); + connect(socket, &QWebSocket::disconnected, socket, &QObject::deleteLater); + } + + void onTextMessageReceived(const QString &message) + { + QWebSocket *socket = qobject_cast<QWebSocket *>(sender()); + socket->sendTextMessage(message); + } + + QWebSocketServer webSocketServer; +}; + // Try opening a WebSocket from pages loaded over various URL schemes. void tst_Origins::webSocket() { - // 1006 indicates 'Abnormal Closure'. - // - // The example page is passing a URL with a non-existent domain to the - // WebSocket constructor, so we expect the connection to fail. This is - // enough though to trigger the origin checks. - const int expected = 1006; + const int kAbnormalClosure = 1006; + + EchoServer echoServer; + QWebChannel channel; + channel.registerObject(QSL("echoServer"), &echoServer); + m_page->setWebChannel(&channel); + QVERIFY(echoServer.listen()); QVERIFY(load(QSL("file:" THIS_DIR "resources/websocket.html"))); - QTRY_VERIFY(eval(QSL("err")) == QVariant(expected)); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); QVERIFY(load(QSL("qrc:/resources/websocket.html"))); - QTRY_VERIFY(eval(QSL("err")) == QVariant(expected)); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); + // Only registered schemes can open WebSockets. QVERIFY(load(QSL("tst:/resources/websocket.html"))); - QTRY_VERIFY(eval(QSL("err")) == QVariant(expected)); -} + QTRY_COMPARE(eval(QSL("result")), QVariant(kAbnormalClosure)); + // Even an insecure registered scheme can open WebSockets. + QVERIFY(load(QSL("PathSyntax:/resources/websocket.html"))); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); +} +#endif // Create a (Dedicated)Worker. Since dedicated workers can only be accessed from // one page, there is not much need for security restrictions. void tst_Origins::dedicatedWorker() @@ -278,11 +599,22 @@ void tst_Origins::dedicatedWorker() QTRY_VERIFY(eval(QSL("done")).toBool()); QCOMPARE(eval(QSL("result")), QVariant(42)); - // FIXME(juvaldma): QTBUG-62536 + // Unregistered schemes cannot create Workers. QVERIFY(load(QSL("tst:/resources/dedicatedWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QVERIFY(eval(QSL("error")).toString() .contains(QSL("Access to dedicated workers is denied to origin 'tst://'"))); + + // Even an insecure registered scheme can create Workers. + QVERIFY(load(QSL("PathSyntax:/resources/dedicatedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); + + // But not if the NoAccessAllowed flag is set. + QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/dedicatedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("cannot be accessed from origin 'null'"))); } // Create a SharedWorker. Shared workers can be accessed from multiple pages, @@ -290,7 +622,7 @@ void tst_Origins::dedicatedWorker() void tst_Origins::sharedWorker() { { - ScopedAttribute sa(m_page.settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false); + ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false); QVERIFY(load(QSL("file:" THIS_DIR "resources/sharedWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QVERIFY(eval(QSL("error")).toString() @@ -298,7 +630,7 @@ void tst_Origins::sharedWorker() } { - ScopedAttribute sa(m_page.settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true); + ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true); QVERIFY(load(QSL("file:" THIS_DIR "resources/sharedWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QCOMPARE(eval(QSL("result")), QVariant(42)); @@ -308,12 +640,22 @@ void tst_Origins::sharedWorker() QTRY_VERIFY(eval(QSL("done")).toBool()); QCOMPARE(eval(QSL("result")), QVariant(42)); + // Even unregistered schemes can create SharedWorkers. QVERIFY(load(QSL("tst:/resources/sharedWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QCOMPARE(eval(QSL("result")), QVariant(42)); + + QVERIFY(load(QSL("PathSyntax:/resources/sharedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); + + QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/sharedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("denied to origin 'null'"))); } -// Service workers don't work. +// Service workers have to be explicitly enabled for a scheme. void tst_Origins::serviceWorker() { QVERIFY(load(QSL("file:" THIS_DIR "resources/serviceWorker.html"))); @@ -329,7 +671,71 @@ void tst_Origins::serviceWorker() QVERIFY(load(QSL("tst:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QVERIFY(eval(QSL("error")).toString() - .contains(QSL("Only secure origins are allowed"))); + .contains(QSL("Cannot read property 'register' of undefined"))); + + QVERIFY(load(QSL("PathSyntax:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("Cannot read property 'register' of undefined"))); + + QVERIFY(load(QSL("PathSyntax-Secure:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("The URL protocol of the current origin ('pathsyntax-secure://') is not supported."))); + + QVERIFY(load(QSL("PathSyntax-ServiceWorkersAllowed:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("Cannot read property 'register' of undefined"))); + + QVERIFY(load(QSL("PathSyntax-Secure-ServiceWorkersAllowed:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("error")), QVariant()); + + QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("Cannot read property 'register' of undefined"))); +} + +// Support for view-source must be enabled explicitly. +void tst_Origins::viewSource() +{ + QVERIFY(load(QSL("view-source:file:" THIS_DIR "resources/viewSource.html"))); +#ifdef Q_OS_WIN + QCOMPARE(m_page->requestedUrl().toString(), QSL("file:///" THIS_DIR "resources/viewSource.html")); +#else + QCOMPARE(m_page->requestedUrl().toString(), QSL("file://" THIS_DIR "resources/viewSource.html")); +#endif + + QVERIFY(load(QSL("view-source:qrc:/resources/viewSource.html"))); + QCOMPARE(m_page->requestedUrl().toString(), QSL("qrc:/resources/viewSource.html")); + + QVERIFY(load(QSL("view-source:tst:/resources/viewSource.html"))); + QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank")); + + QVERIFY(load(QSL("view-source:PathSyntax:/resources/viewSource.html"))); + QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank")); + + QVERIFY(load(QSL("view-source:PathSyntax-ViewSourceAllowed:/resources/viewSource.html"))); + QCOMPARE(m_page->requestedUrl().toString(), QSL("pathsyntax-viewsourceallowed:/resources/viewSource.html")); +} + +void tst_Origins::createObjectURL() +{ + // Legal for registered custom schemes. + QVERIFY(load(QSL("qrc:/resources/createObjectURL.html"))); + QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:qrc:"))); + + // Illegal for unregistered schemes (renderer gets terminated). + qRegisterMetaType<QWebEnginePage::RenderProcessTerminationStatus>("RenderProcessTerminationStatus"); + QSignalSpy loadFinishedSpy(m_page, &QWebEnginePage::loadFinished); + QSignalSpy renderProcessTerminatedSpy(m_page, &QWebEnginePage::renderProcessTerminated); + m_page->load(QSL("tst:/resources/createObjectURL.html")); + QVERIFY(!renderProcessTerminatedSpy.empty() || renderProcessTerminatedSpy.wait(20000)); + QVERIFY(renderProcessTerminatedSpy.front().value(0).value<QWebEnginePage::RenderProcessTerminationStatus>() + != QWebEnginePage::NormalTerminationStatus); + QVERIFY(loadFinishedSpy.empty()); } QTEST_MAIN(tst_Origins) |