diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2021-06-14 16:11:54 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2021-06-16 16:42:22 +0000 |
commit | 551b83b280bf83137e0c8ceefad59558bfe9e03e (patch) | |
tree | a3133e21416d599db2a635c9bbfec9b560516de7 /tests/auto/core/origins | |
parent | 5f4fb14cb0f6b443885e79516f450853e44f650d (diff) |
Stop using Widgets in WebEngineCore tests
With QWebEnginePage in Core we can now make the core tests core-only.
Add the same times moves tests from widgets that only uses core
classes.
Change-Id: I67a25b534912d9a0891e16b0419f0db9bf434e92
Reviewed-by: Peter Varga <pvarga@inf.u-szeged.hu>
(cherry picked from commit b8c48ee53009365a39d9dde1f6773048ec5ee4a0)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'tests/auto/core/origins')
23 files changed, 1298 insertions, 0 deletions
diff --git a/tests/auto/core/origins/CMakeLists.txt b/tests/auto/core/origins/CMakeLists.txt new file mode 100644 index 000000000..a044ea2ef --- /dev/null +++ b/tests/auto/core/origins/CMakeLists.txt @@ -0,0 +1,45 @@ +include(../../httpserver/httpserver.cmake) +include(../../util/util.cmake) + +qt_internal_add_test(tst_origins + SOURCES + tst_origins.cpp + LIBRARIES + Qt::WebEngineCore + Test::HttpServer + Test::Util +) + +set(tst_origins_resource_files + "resources/createObjectURL.html" + "resources/dedicatedWorker.html" + "resources/dedicatedWorker.js" + "resources/mixedSchemes.html" + "resources/mixedSchemesWithCsp.html" + "resources/mixedSchemes_frame.html" + "resources/mixedXHR.html" + "resources/mixedXHR.txt" + "resources/serviceWorker.html" + "resources/serviceWorker.js" + "resources/sharedWorker.html" + "resources/sharedWorker.js" + "resources/subdir/frame2.html" + "resources/subdir/index.html" + "resources/subdir_frame1.html" + "resources/viewSource.html" + "resources/websocket.html" +) + +qt_internal_add_resource(tst_origins "tst_origins" + PREFIX + "/" + FILES + ${tst_origins_resource_files} +) + +qt_internal_extend_target(tst_origins CONDITION QT_FEATURE_webengine_webchannel AND TARGET Qt::WebSockets + DEFINES + WEBSOCKETS + LIBRARIES + Qt::WebSockets +) diff --git a/tests/auto/core/origins/origins.pro b/tests/auto/core/origins/origins.pro new file mode 100644 index 000000000..6cf0b2b92 --- /dev/null +++ b/tests/auto/core/origins/origins.pro @@ -0,0 +1,8 @@ +include(../tests.pri) +include(../../shared/http.pri) + +qtConfig(webengine-webchannel):qtHaveModule(websockets) { + QT += websockets + DEFINES += WEBSOCKETS +} + diff --git a/tests/auto/core/origins/resources/createObjectURL.html b/tests/auto/core/origins/resources/createObjectURL.html new file mode 100644 index 000000000..133f636bb --- /dev/null +++ b/tests/auto/core/origins/resources/createObjectURL.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head> + <title>createObjectURL</title> + <script> + const blob = new Blob(['foo']); + const result = URL.createObjectURL(blob); + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/core/origins/resources/dedicatedWorker.html b/tests/auto/core/origins/resources/dedicatedWorker.html new file mode 100644 index 000000000..cb4f14e73 --- /dev/null +++ b/tests/auto/core/origins/resources/dedicatedWorker.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <title>dedicatedWorker</title> + <script> + var done = false; + var result; + var error; + try { + let worker = new Worker("dedicatedWorker.js"); + worker.onmessage = (e) => { done = true; result = e.data; }; + worker.postMessage(41); + } catch (e) { + done = true; error = e.message; + } + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/core/origins/resources/dedicatedWorker.js b/tests/auto/core/origins/resources/dedicatedWorker.js new file mode 100644 index 000000000..2631939d7 --- /dev/null +++ b/tests/auto/core/origins/resources/dedicatedWorker.js @@ -0,0 +1 @@ +onmessage = (e) => { postMessage(e.data + 1); }; diff --git a/tests/auto/core/origins/resources/mixedSchemes.html b/tests/auto/core/origins/resources/mixedSchemes.html new file mode 100644 index 000000000..c73e9ecdc --- /dev/null +++ b/tests/auto/core/origins/resources/mixedSchemes.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> + <head> + <title>Mixed</title> + <script> + var result; + var canary; + + function setIFrameUrl(url) { + result = undefined; + canary = undefined; + document.getElementById("iframe").setAttribute("src", url); + // Early fire is OK unless the test is expecting cannotLoad. + // If timeout is too short then a false positive is possible. + setTimeout(() => { result = result || "cannotLoad"; }, 500); + } + + addEventListener("load", function() { + document.getElementById("iframe").addEventListener("load", function() { + if (canary && window[0].canary) + result = "canLoadAndAccess"; + else + result = "canLoadButNotAccess"; + }); + }); + </script> + </head> + <body> + <iframe id="iframe"></iframe> + </body> +</html> diff --git a/tests/auto/core/origins/resources/mixedSchemesWithCsp.html b/tests/auto/core/origins/resources/mixedSchemesWithCsp.html new file mode 100644 index 000000000..ad7cbeeb7 --- /dev/null +++ b/tests/auto/core/origins/resources/mixedSchemesWithCsp.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Security-Policy" content="frame-src 'none'"> + <title>Mixed</title> + <script> + var result; + var canary; + + function setIFrameUrl(url) { + result = undefined; + canary = undefined; + document.getElementById("iframe").setAttribute("src", url); + // Early fire is OK unless the test is expecting cannotLoad. + // If timeout is too short then a false positive is possible. + setTimeout(() => { result = result || "cannotLoad"; }, 500); + } + + addEventListener("load", function() { + document.getElementById("iframe").addEventListener("load", function() { + if (canary && window[0].canary) + result = "canLoadAndAccess"; + else + result = "canLoadButNotAccess"; + }); + }); + </script> + </head> + <body> + <iframe id="iframe"></iframe> + </body> +</html> diff --git a/tests/auto/core/origins/resources/mixedSchemes_frame.html b/tests/auto/core/origins/resources/mixedSchemes_frame.html new file mode 100644 index 000000000..00c20ba37 --- /dev/null +++ b/tests/auto/core/origins/resources/mixedSchemes_frame.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head> + <title>Mixed - Frame</title> + <script> + var canary = true; + parent.canary = true; + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/core/origins/resources/mixedXHR.html b/tests/auto/core/origins/resources/mixedXHR.html new file mode 100644 index 000000000..3dfd90006 --- /dev/null +++ b/tests/auto/core/origins/resources/mixedXHR.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <title>Mixed</title> + <script> + var result; + function sendXHR(url) { + result = undefined; + let req = new XMLHttpRequest(); + req.addEventListener("load", () => { result = req.responseText }); + req.addEventListener("error", () => { result = "error"; }); + req.open("GET", url); + req.send(); + } + </script> + </head> + <body> + </body> +</html> diff --git a/tests/auto/core/origins/resources/mixedXHR.txt b/tests/auto/core/origins/resources/mixedXHR.txt new file mode 100644 index 000000000..b5754e203 --- /dev/null +++ b/tests/auto/core/origins/resources/mixedXHR.txt @@ -0,0 +1 @@ +ok
\ No newline at end of file diff --git a/tests/auto/core/origins/resources/redirect.css b/tests/auto/core/origins/resources/redirect.css new file mode 100644 index 000000000..41d7560cc --- /dev/null +++ b/tests/auto/core/origins/resources/redirect.css @@ -0,0 +1,8 @@ +@font-face { + font-family: 'MyWebFont'; + src: url('redirect1:/resources/Akronim-Regular.woff2') format('woff2'); +} + +body { + font-family: 'MyWebFont', Fallback, sans-serif; +} diff --git a/tests/auto/core/origins/resources/redirect.html b/tests/auto/core/origins/resources/redirect.html new file mode 100644 index 000000000..04948e14b --- /dev/null +++ b/tests/auto/core/origins/resources/redirect.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <title>redirect</title> + <link rel="stylesheet" href="redirect1:/resources/redirect.css"> + </head> + <body> + Text + </body> +</html> diff --git a/tests/auto/core/origins/resources/serviceWorker.html b/tests/auto/core/origins/resources/serviceWorker.html new file mode 100644 index 000000000..27890c98f --- /dev/null +++ b/tests/auto/core/origins/resources/serviceWorker.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <title>serviceWorker</title> + <script> + var done = false; + var error; + try { + navigator.serviceWorker.register("serviceWorker.js") + .then((r) => { done = true; }) + .catch((e) => { done = true; error = e.message; }); + } catch (e) { + done = true; error = e.message; + } + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/core/origins/resources/serviceWorker.js b/tests/auto/core/origins/resources/serviceWorker.js new file mode 100644 index 000000000..40a8c178f --- /dev/null +++ b/tests/auto/core/origins/resources/serviceWorker.js @@ -0,0 +1 @@ +/* empty */ diff --git a/tests/auto/core/origins/resources/sharedWorker.html b/tests/auto/core/origins/resources/sharedWorker.html new file mode 100644 index 000000000..8b5a0a794 --- /dev/null +++ b/tests/auto/core/origins/resources/sharedWorker.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <title>sharedWorker</title> + <script> + var done; + var result; + var error; + try { + let worker = new SharedWorker("sharedWorker.js"); + worker.port.onmessage = (e) => { done = true; result = e.data; }; + worker.port.postMessage(41); + } catch (e) { + done = true; error = e.message; + } + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/core/origins/resources/sharedWorker.js b/tests/auto/core/origins/resources/sharedWorker.js new file mode 100644 index 000000000..60ef93a5f --- /dev/null +++ b/tests/auto/core/origins/resources/sharedWorker.js @@ -0,0 +1,6 @@ +onconnect = function(e) { + let port = e.ports[0]; + port.onmessage = function(e) { + port.postMessage(e.data + 1); + }; +}; diff --git a/tests/auto/core/origins/resources/subdir/frame2.html b/tests/auto/core/origins/resources/subdir/frame2.html new file mode 100644 index 000000000..3a2f664ca --- /dev/null +++ b/tests/auto/core/origins/resources/subdir/frame2.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <title>Subdir - Frame 2</title> + <script> + parent.msg[1] = "world"; + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/core/origins/resources/subdir/index.html b/tests/auto/core/origins/resources/subdir/index.html new file mode 100644 index 000000000..9c5d5d782 --- /dev/null +++ b/tests/auto/core/origins/resources/subdir/index.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> + <head> + <title>Subdir</title> + + <script> + var msg = []; + </script> + + <!-- for manual testing --> + <script> + window.addEventListener("load", () => { + for (let i of [0, 1]) { + let p = document.createElement("p"); + p.appendChild(document.createTextNode(`frame ${i+1} says: ${msg[i]}`)); + document.body.insertBefore(p, null); + } + }); + </script> + + </head> + <body> + <iframe src="../subdir_frame1.html"></iframe> + <iframe src="frame2.html"></iframe> + </body> +</html> diff --git a/tests/auto/core/origins/resources/subdir_frame1.html b/tests/auto/core/origins/resources/subdir_frame1.html new file mode 100644 index 000000000..63973f2f4 --- /dev/null +++ b/tests/auto/core/origins/resources/subdir_frame1.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <title>Subdir - Frame 1</title> + <script> + parent.msg[0] = "hello"; + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/core/origins/resources/viewSource.html b/tests/auto/core/origins/resources/viewSource.html new file mode 100644 index 000000000..977074c74 --- /dev/null +++ b/tests/auto/core/origins/resources/viewSource.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <title>viewSource</title> + </head> + <body> + <p>viewSource</p> + </body> +</html> diff --git a/tests/auto/core/origins/resources/websocket.html b/tests/auto/core/origins/resources/websocket.html new file mode 100644 index 000000000..31db66571 --- /dev/null +++ b/tests/auto/core/origins/resources/websocket.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> + <head> + <title>WebSocket</title> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> + <script> + var result; + new QWebChannel(qt.webChannelTransport, channel => { + const ws = new WebSocket(channel.objects.echoServer.url); + ws.addEventListener("open", event => { + ws.send("ok"); + }); + ws.addEventListener("message", event => { + result = event.data; + }); + ws.addEventListener("close", event => { + result = event.code; + }); + }) + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/core/origins/tst_origins.cpp b/tests/auto/core/origins/tst_origins.cpp new file mode 100644 index 000000000..a34404a7e --- /dev/null +++ b/tests/auto/core/origins/tst_origins.cpp @@ -0,0 +1,958 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <util.h> +#include "httpserver.h" + +#include <QtCore/qfile.h> +#include <QtTest/QtTest> +#include <QtWebEngineCore/qwebengineurlrequestjob.h> +#include <QtWebEngineCore/qwebengineurlscheme.h> +#include <QtWebEngineCore/qwebengineurlschemehandler.h> +#include <QtWebEngineCore/qwebenginesettings.h> +#include <QtWebEngineCore/qwebengineprofile.h> +#include <QtWebEngineCore/qwebenginepage.h> + +#if defined(WEBSOCKETS) +#include <QtWebSockets/qwebsocket.h> +#include <QtWebSockets/qwebsocketserver.h> +#include <QtWebChannel/qwebchannel.h> +#endif +#include <qaction.h> + +#define QSL QStringLiteral +#define QBAL QByteArrayLiteral + +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); + } + + { + QWebEngineUrlScheme scheme(QBAL("redirect1")); + scheme.setFlags(QWebEngineUrlScheme::CorsEnabled); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("redirect2")); + scheme.setFlags(QWebEngineUrlScheme::CorsEnabled); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("cors")); + scheme.setFlags(QWebEngineUrlScheme::CorsEnabled); + QWebEngineUrlScheme::registerScheme(scheme); + } + +} +Q_CONSTRUCTOR_FUNCTION(registerSchemes) + +class TstUrlSchemeHandler final : public QWebEngineUrlSchemeHandler { + Q_OBJECT + +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); + profile->installUrlSchemeHandler(QBAL("redirect1"), this); + profile->installUrlSchemeHandler(QBAL("redirect2"), this); + profile->installUrlSchemeHandler(QBAL("cors"), this); + } + + QList<QUrl> &requests() { return m_requests; } + +private: + void requestStarted(QWebEngineUrlRequestJob *job) override + { + QUrl url = job->requestUrl(); + m_requests << url; + + if (url.scheme() == QBAL("redirect1")) { + url.setScheme(QBAL("redirect2")); + job->redirect(url); + return; + } + + QString pathPrefix = QDir(QT_TESTCASE_SOURCEDIR).canonicalPath(); + QString pathSuffix = url.path(); + QFile *file = new QFile(pathPrefix + pathSuffix, job); + if (!file->open(QIODevice::ReadOnly)) { + job->fail(QWebEngineUrlRequestJob::RequestFailed); + return; + } + QByteArray mimeType = QBAL("text/html"); + if (pathSuffix.endsWith(QSL(".js"))) + mimeType = QBAL("application/javascript"); + else if (pathSuffix.endsWith(QSL(".css"))) + mimeType = QBAL("text/css"); + job->reply(mimeType, file); + } + + QList<QUrl> m_requests; +}; + +class tst_Origins final : public QObject { + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void jsUrlCanon(); + void jsUrlRelative(); + void jsUrlOrigin(); + void subdirWithAccess(); + void subdirWithoutAccess(); + void fileAccessRemoteUrl_data(); + void fileAccessRemoteUrl(); + void mixedSchemes(); + void mixedSchemesWithCsp(); + void mixedXHR_data(); + void mixedXHR(); +#if defined(WEBSOCKETS) + void webSocket(); +#endif + void dedicatedWorker(); + void sharedWorker(); + void serviceWorker(); + void viewSource(); + void createObjectURL(); + void redirect(); + +private: + bool verifyLoad(const QUrl &url) + { + QSignalSpy spy(m_page, &QWebEnginePage::loadFinished); + m_page->load(url); + [&spy]() { QTRY_VERIFY_WITH_TIMEOUT(!spy.isEmpty(), 90000); }(); + return !spy.isEmpty() && spy.front().value(0).toBool(); + } + + QVariant eval(const QString &code) + { + return evaluateJavaScriptSync(m_page, code); + } + + QWebEngineProfile m_profile; + QWebEnginePage *m_page = nullptr; + TstUrlSchemeHandler *m_handler = nullptr; +}; + +void tst_Origins::initTestCase() +{ + QTest::ignoreMessage( + QtWarningMsg, + QRegularExpression("Please register the custom scheme 'tst'.*")); + + m_handler = new TstUrlSchemeHandler(&m_profile); +} + +void tst_Origins::cleanupTestCase() +{ + QVERIFY(!m_page); + delete m_handler; +} + +void tst_Origins::init() +{ + m_page = new QWebEnginePage(&m_profile, nullptr); +} + +void tst_Origins::cleanup() +{ + delete m_page; + m_page = nullptr; + m_handler->requests().clear(); +} + +// Test URL parsing and canonicalization in Blink. The implementation of this +// part is mostly shared between Blink and Chromium proper. +void tst_Origins::jsUrlCanon() +{ + QVERIFY(verifyLoad(QSL("about:blank"))); + + // Standard schemes are biased towards the authority part. + QCOMPARE(eval(QSL("new URL(\"http:foo/bar\").href")), QVariant(QSL("http://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"http:/foo/bar\").href")), QVariant(QSL("http://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"http://foo/bar\").href")), QVariant(QSL("http://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"http:///foo/bar\").href")), QVariant(QSL("http://foo/bar"))); + + // The file scheme is however a (particularly) special case. +#ifdef Q_OS_WIN + QCOMPARE(eval(QSL("new URL(\"file:foo/bar\").href")), QVariant(QSL("file://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"file:/foo/bar\").href")), QVariant(QSL("file://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"file://foo/bar\").href")), QVariant(QSL("file://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"file:///foo/bar\").href")), QVariant(QSL("file:///foo/bar"))); +#else + QCOMPARE(eval(QSL("new URL(\"file:foo/bar\").href")), QVariant(QSL("file:///foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"file:/foo/bar\").href")), QVariant(QSL("file:///foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"file://foo/bar\").href")), QVariant(QSL("file://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"file:///foo/bar\").href")), QVariant(QSL("file:///foo/bar"))); +#endif + + // 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 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 relative URL resolution. +void tst_Origins::jsUrlRelative() +{ + QVERIFY(verifyLoad(QSL("about:blank"))); + + // Schemes with hosts, like http, work as expected. + QCOMPARE(eval(QSL("new URL('bar', 'http://foo').href")), QVariant(QSL("http://foo/bar"))); + QCOMPARE(eval(QSL("new URL('baz', 'http://foo/bar').href")), QVariant(QSL("http://foo/baz"))); + QCOMPARE(eval(QSL("new URL('baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/bar/baz"))); + QCOMPARE(eval(QSL("new URL('/baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/baz"))); + QCOMPARE(eval(QSL("new URL('./baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/bar/baz"))); + QCOMPARE(eval(QSL("new URL('../baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/baz"))); + QCOMPARE(eval(QSL("new URL('../../baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/baz"))); + QCOMPARE(eval(QSL("new URL('//baz', 'http://foo/bar/').href")), QVariant(QSL("http://baz/"))); + + // In the case of schemes without hosts, relative URLs only work if the URL + // starts with a single slash -- and canonicalization does not guarantee + // this. The following cases all fail with TypeErrors. + QCOMPARE(eval(QSL("new URL('bar', 'tst:foo').href")), QVariant()); + QCOMPARE(eval(QSL("new URL('baz', 'tst:foo/bar').href")), QVariant()); + QCOMPARE(eval(QSL("new URL('bar', 'tst://foo').href")), QVariant()); + QCOMPARE(eval(QSL("new URL('bar', 'tst:///foo').href")), QVariant()); + + // However, registered custom schemes have been patched to allow relative + // URLs even without an initial slash. + QCOMPARE(eval(QSL("new URL('bar', 'qrc:foo').href")), QVariant(QSL("qrc:bar"))); + QCOMPARE(eval(QSL("new URL('baz', 'qrc:foo/bar').href")), QVariant(QSL("qrc:foo/baz"))); + QCOMPARE(eval(QSL("new URL('bar', 'qrc://foo').href")), QVariant()); + QCOMPARE(eval(QSL("new URL('bar', 'qrc:///foo').href")), QVariant()); + + // With a slash it works the same as http except 'foo' is part of the path and not the host. + QCOMPARE(eval(QSL("new URL('bar', 'qrc:/foo').href")), QVariant(QSL("qrc:/bar"))); + QCOMPARE(eval(QSL("new URL('bar', 'qrc:/foo/').href")), QVariant(QSL("qrc:/foo/bar"))); + QCOMPARE(eval(QSL("new URL('baz', 'qrc:/foo/bar').href")), QVariant(QSL("qrc:/foo/baz"))); + QCOMPARE(eval(QSL("new URL('baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/foo/bar/baz"))); + QCOMPARE(eval(QSL("new URL('/baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/baz"))); + QCOMPARE(eval(QSL("new URL('./baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/foo/bar/baz"))); + QCOMPARE(eval(QSL("new URL('../baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/foo/baz"))); + QCOMPARE(eval(QSL("new URL('../../baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/baz"))); + QCOMPARE(eval(QSL("new URL('../../../baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/baz"))); + + // If the relative URL begins with >= 2 slashes, then the scheme is treated + // not as a Syntax::Path scheme but as a Syntax::HostPortAndUserInformation + // scheme. + QCOMPARE(eval(QSL("new URL('//baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc://baz/"))); + QCOMPARE(eval(QSL("new URL('///baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc://baz/"))); +} + +// Test origin serialization in Blink, implemented by blink::KURL and +// blink::SecurityOrigin as opposed to GURL and url::Origin. +void tst_Origins::jsUrlOrigin() +{ + QVERIFY(verifyLoad(QSL("about:blank"))); + + // For network protocols the origin string must include the domain and port. + QCOMPARE(eval(QSL("new URL(\"http://foo.com/page.html\").origin")), QVariant(QSL("http://foo.com"))); + QCOMPARE(eval(QSL("new URL(\"https://foo.com/page.html\").origin")), QVariant(QSL("https://foo.com"))); + + // Even though file URL can also have domains, these are not included in the + // origin string by Chromium. The standard does not specify a value here, + // but suggests 'null' (https://url.spec.whatwg.org/#origin). + QCOMPARE(eval(QSL("new URL(\"file:/etc/passwd\").origin")), QVariant(QSL("file://"))); + QCOMPARE(eval(QSL("new URL(\"file://foo.com/etc/passwd\").origin")), QVariant(QSL("file://"))); + + // Unregistered schemes behave like file. + 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:"))); + 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:"))); + + // The NoAccessAllowed flag forces opaque origins. + QCOMPARE(eval(QSL("new URL(\"PathSyntax-NoAccessAllowed:foo\").origin")), + QVariant(QSL("null"))); +} + +class ScopedAttribute { +public: + ScopedAttribute(QWebEngineSettings *settings, QWebEngineSettings::WebAttribute attribute, bool newValue) + : m_settings(settings) + , m_attribute(attribute) + , m_oldValue(m_settings->testAttribute(m_attribute)) + { + m_settings->setAttribute(m_attribute, newValue); + } + ~ScopedAttribute() + { + m_settings->setAttribute(m_attribute, m_oldValue); + } +private: + QWebEngineSettings *m_settings; + QWebEngineSettings::WebAttribute m_attribute; + bool m_oldValue; +}; + +// Test same-origin policy of file, qrc and custom schemes. +// +// Note the test case involves the main page trying to load an iframe from a +// file that resides in a parent directory. This is just a small detail to +// demonstrate the difference with Firefox where such access is not allowed. +void tst_Origins::subdirWithAccess() +{ + ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true); + + QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/subdir/index.html")); + QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); + QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); + + QVERIFY(verifyLoad(QSL("qrc:/resources/subdir/index.html"))); + QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); + QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); + + QVERIFY(verifyLoad(QSL("tst:/resources/subdir/index.html"))); + QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); + QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); +} + +// In this variation the LocalContentCanAccessFileUrls attribute is disabled. As +// a result all file URLs will be considered to have unique/opaque origins, that +// is, they are not the 'same origin as' any other origin. +// +// Note that this applies only to file URLs and not qrc or custom schemes. +// +// See also (in Blink): +// - the allow_file_access_from_file_urls option and +// - the blink::SecurityOrigin::BlockLocalAccessFromLocalOrigin() method. +void tst_Origins::subdirWithoutAccess() +{ + ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false); + + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/subdir/index.html")); + QCOMPARE(eval(QSL("msg[0]")), QVariant()); + QCOMPARE(eval(QSL("msg[1]")), QVariant()); + + QVERIFY(verifyLoad(QSL("qrc:/resources/subdir/index.html"))); + QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); + QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); + + QVERIFY(verifyLoad(QSL("tst:/resources/subdir/index.html"))); + QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); + QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); +} + +void tst_Origins::fileAccessRemoteUrl_data() +{ + QTest::addColumn<bool>("EnableAccess"); + QTest::addRow("enabled") << true; + QTest::addRow("disabled") << false; +} + +void tst_Origins::fileAccessRemoteUrl() +{ + QFETCH(bool, EnableAccess); + + HttpServer server; + server.setResourceDirs({ QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + "/resources" }); + QVERIFY(server.start()); + + ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessRemoteUrls, EnableAccess); + if (!EnableAccess) + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("blocked by CORS policy"))); + + QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedXHR.html")); + + eval("sendXHR('" + server.url("/mixedXHR.txt").toString() + "')"); + QTRY_COMPARE(eval("result"), (EnableAccess ? QString("ok") : QString("error"))); +} + +// 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(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedSchemes.html")); + eval("setIFrameUrl('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedSchemes_frame.html')"); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(verifyLoad(QSL("qrc:/resources/mixedSchemes.html"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + eval("setIFrameUrl('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/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"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(verifyLoad(QSL("tst:/resources/mixedSchemes.html"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); + eval("setIFrameUrl('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedSchemes_frame.html')"); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + 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(verifyLoad(QSL("PathSyntax:/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); + 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')")); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(verifyLoad(QSL("PathSyntax-LocalAccessAllowed:/resources/mixedSchemes.html"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + 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"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(verifyLoad(QSL("PathSyntax-NoAccessAllowed:/resources/mixedSchemes.html"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); + eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(verifyLoad(QSL("HostSyntax://a/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + 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(verifyLoad(QSL("HostSyntax://a/resources/mixedSchemesWithCsp.html"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("violates the following Content Security Policy"))); + eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("violates the following Content Security Policy"))); + eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(verifyLoad(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"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + 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. +// +// Cross-origin XMLHttpRequests can only be made to CORS-enabled schemes. These +// include the builtin schemes http, https, data, and chrome, as well as custom +// schemes with the CorsEnabled flag. +void tst_Origins::mixedXHR_data() +{ + QTest::addColumn<QString>("url"); + QTest::addColumn<QString>("command"); + QTest::addColumn<QVariant>("result"); + QTest::newRow("file->file") << QString("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedXHR.html") + << QString("sendXHR('file:" + + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedXHR.txt')") + << QVariant(QString("ok")); + QTest::newRow("file->qrc") << QString("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedXHR.html") + << QString("sendXHR('qrc:/resources/mixedXHR.txt')") + << QVariant(QString("error")); + QTest::newRow("file->tst") << QString("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedXHR.html") + << QString("sendXHR('tst:/resources/mixedXHR.txt')") + << QVariant(QString("error")); + QTest::newRow("file->data") << QString("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedXHR.html") + << QString("sendXHR('data:,ok')") << QVariant(QString("ok")); + QTest::newRow("file->cors") << QString("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedXHR.html") + << QString("sendXHR('cors:/resources/mixedXHR.txt')") + << QVariant(QString("ok")); + + QTest::newRow("qrc->file") << QString("qrc:/resources/mixedXHR.html") + << QString("sendXHR('file:" + + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedXHR.txt')") + << QVariant(QString("ok")); + QTest::newRow("qrc->qrc") << QString("qrc:/resources/mixedXHR.html") + << QString("sendXHR('qrc:/resources/mixedXHR.txt')") + << QVariant(QString("ok")); + QTest::newRow("qrc->tst") << QString("qrc:/resources/mixedXHR.html") + << QString("sendXHR('tst:/resources/mixedXHR.txt')") + << QVariant(QString("error")); + QTest::newRow("qrc->data") << QString("qrc:/resources/mixedXHR.html") + << QString("sendXHR('data:,ok')") + << QVariant(QString("ok")); + QTest::newRow("qrc->cors") << QString("qrc:/resources/mixedXHR.html") + << QString("sendXHR('cors:/resources/mixedXHR.txt')") + << QVariant(QString("ok")); + + QTest::newRow("tst->file") << QString("tst:/resources/mixedXHR.html") + << QString("sendXHR('file:" + + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedXHR.txt')") + << QVariant(QString("error")); + QTest::newRow("tst->qrc") << QString("tst:/resources/mixedXHR.html") + << QString("sendXHR('qrc:/resources/mixedXHR.txt')") + << QVariant(QString("error")); + QTest::newRow("tst->tst") << QString("tst:/resources/mixedXHR.html") + << QString("sendXHR('tst:/resources/mixedXHR.txt')") + << QVariant(QString("ok")); + QTest::newRow("tst->data") << QString("tst:/resources/mixedXHR.html") + << QString("sendXHR('data:,ok')") + << QVariant(QString("ok")); + QTest::newRow("tst->cors") << QString("tst:/resources/mixedXHR.html") + << QString("sendXHR('cors:/resources/mixedXHR.txt')") + << QVariant(QString("ok")); + +} + + +void tst_Origins::mixedXHR() +{ + QFETCH(QString, url); + QFETCH(QString, command); + QFETCH(QVariant, result); + + QVERIFY(verifyLoad(url)); + eval(command); + QTRY_COMPARE(eval(QString("result")), result); +} + +#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() +{ + EchoServer echoServer; + QWebChannel channel; + channel.registerObject(QSL("echoServer"), &echoServer); + m_page->setWebChannel(&channel); + QVERIFY(echoServer.listen()); + + QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/websocket.html")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); + + QVERIFY(verifyLoad(QSL("qrc:/resources/websocket.html"))); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); + + // Unregistered schemes can also open WebSockets (since Chromium 71) + QVERIFY(verifyLoad(QSL("tst:/resources/websocket.html"))); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); + + // Even an insecure registered scheme can open WebSockets. + QVERIFY(verifyLoad(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() +{ + QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/dedicatedWorker.html")); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); + + QVERIFY(verifyLoad(QSL("qrc:/resources/dedicatedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); + + // Unregistered schemes can also create Workers (since Chromium 71) + QVERIFY(verifyLoad(QSL("tst:/resources/dedicatedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); + + // Even an insecure registered scheme can create Workers. + QVERIFY(verifyLoad(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(verifyLoad(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, +// and therefore the same-origin policy applies. +void tst_Origins::sharedWorker() +{ + { + ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false); + QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/sharedWorker.html")); + QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("cannot be accessed from origin 'null'"))); + } + + { + ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true); + QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/sharedWorker.html")); + QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000); + QCOMPARE(eval(QSL("result")), QVariant(42)); + } + + QVERIFY(verifyLoad(QSL("qrc:/resources/sharedWorker.html"))); + QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000); + QCOMPARE(eval(QSL("result")), QVariant(42)); + + // Unregistered schemes should not create SharedWorkers. + + QVERIFY(verifyLoad(QSL("PathSyntax:/resources/sharedWorker.html"))); + QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000); + QCOMPARE(eval(QSL("result")), QVariant(42)); + + QVERIFY(verifyLoad(QSL("PathSyntax-NoAccessAllowed:/resources/sharedWorker.html"))); + QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("denied to origin 'null'"))); +} + +// Service workers have to be explicitly enabled for a scheme. +void tst_Origins::serviceWorker() +{ + QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/serviceWorker.html")); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("The URL protocol of the current origin ('file://') is not supported."))); + + QVERIFY(verifyLoad(QSL("qrc:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("The URL protocol of the current origin ('qrc:') is not supported."))); + + QVERIFY(verifyLoad(QSL("tst:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("Cannot read property 'register' of undefined"))); + + QVERIFY(verifyLoad(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(verifyLoad(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(verifyLoad(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(verifyLoad(QSL("PathSyntax-Secure-ServiceWorkersAllowed:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("error")), QVariant()); + + QVERIFY(verifyLoad(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(verifyLoad("view-source:file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/viewSource.html")); +#ifdef Q_OS_WIN + QCOMPARE(m_page->requestedUrl().toString(), + "file:///" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/viewSource.html"); +#else + QCOMPARE(m_page->requestedUrl().toString(), + "file://" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/viewSource.html"); +#endif + + QVERIFY(verifyLoad(QSL("view-source:qrc:/resources/viewSource.html"))); + QCOMPARE(m_page->requestedUrl().toString(), QSL("qrc:/resources/viewSource.html")); + + QVERIFY(verifyLoad(QSL("view-source:tst:/resources/viewSource.html"))); + QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank")); + + QVERIFY(verifyLoad(QSL("view-source:PathSyntax:/resources/viewSource.html"))); + QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank")); + + QVERIFY(verifyLoad(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(verifyLoad(QSL("qrc:/resources/createObjectURL.html"))); + QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:qrc:"))); + + // Also legal for unregistered schemes (since Chromium 71) + QVERIFY(verifyLoad(QSL("tst:/resources/createObjectURL.html"))); + QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:tst:"))); +} + +void tst_Origins::redirect() +{ + QVERIFY(verifyLoad(QSL("redirect1:/resources/redirect.html"))); + QTRY_COMPARE(m_handler->requests().size(), 7); + QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("redirect1:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("redirect2:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[2], QUrl(QStringLiteral("redirect1:/resources/redirect.css"))); + QCOMPARE(m_handler->requests()[3], QUrl(QStringLiteral("redirect2:/resources/redirect.css"))); + QCOMPARE(m_handler->requests()[4], QUrl(QStringLiteral("redirect1:/resources/Akronim-Regular.woff2"))); + QCOMPARE(m_handler->requests()[5], QUrl(QStringLiteral("redirect1:/resources/Akronim-Regular.woff2"))); + QCOMPARE(m_handler->requests()[6], QUrl(QStringLiteral("redirect2:/resources/Akronim-Regular.woff2"))); +} + +QTEST_MAIN(tst_Origins) +#include "tst_origins.moc" diff --git a/tests/auto/core/origins/tst_origins.qrc b/tests/auto/core/origins/tst_origins.qrc new file mode 100644 index 000000000..fcf54aaea --- /dev/null +++ b/tests/auto/core/origins/tst_origins.qrc @@ -0,0 +1,22 @@ +<!DOCTYPE RCC> +<RCC version="1.0"> +<qresource> + <file>resources/createObjectURL.html</file> + <file>resources/dedicatedWorker.html</file> + <file>resources/dedicatedWorker.js</file> + <file>resources/mixedSchemes.html</file> + <file>resources/mixedSchemesWithCsp.html</file> + <file>resources/mixedSchemes_frame.html</file> + <file>resources/mixedXHR.html</file> + <file>resources/mixedXHR.txt</file> + <file>resources/serviceWorker.html</file> + <file>resources/serviceWorker.js</file> + <file>resources/sharedWorker.html</file> + <file>resources/sharedWorker.js</file> + <file>resources/subdir/frame2.html</file> + <file>resources/subdir/index.html</file> + <file>resources/subdir_frame1.html</file> + <file>resources/viewSource.html</file> + <file>resources/websocket.html</file> +</qresource> +</RCC> |