diff options
-rw-r--r-- | tests/auto/widgets/origins/origins.pro | 2 | ||||
-rw-r--r-- | tests/auto/widgets/origins/resources/mixed_frame.html | 10 | ||||
-rw-r--r-- | tests/auto/widgets/origins/resources/mixed_qrc.html | 12 | ||||
-rw-r--r-- | tests/auto/widgets/origins/resources/mixed_tst.html | 12 | ||||
-rw-r--r-- | tests/auto/widgets/origins/resources/subdir/frame2.html | 10 | ||||
-rw-r--r-- | tests/auto/widgets/origins/resources/subdir/index.html | 26 | ||||
-rw-r--r-- | tests/auto/widgets/origins/resources/subdir_frame1.html | 10 | ||||
-rw-r--r-- | tests/auto/widgets/origins/resources/websocket.html | 12 | ||||
-rw-r--r-- | tests/auto/widgets/origins/tst_origins.cpp | 268 | ||||
-rw-r--r-- | tests/auto/widgets/origins/tst_origins.qrc | 12 | ||||
-rw-r--r-- | tests/auto/widgets/widgets.pro | 5 |
11 files changed, 377 insertions, 2 deletions
diff --git a/tests/auto/widgets/origins/origins.pro b/tests/auto/widgets/origins/origins.pro new file mode 100644 index 000000000..566666e0a --- /dev/null +++ b/tests/auto/widgets/origins/origins.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +CONFIG += c++14 diff --git a/tests/auto/widgets/origins/resources/mixed_frame.html b/tests/auto/widgets/origins/resources/mixed_frame.html new file mode 100644 index 000000000..53d341b93 --- /dev/null +++ b/tests/auto/widgets/origins/resources/mixed_frame.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <title>Mixed - Frame</title> + <script> + parent.msg = "mixed"; + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/widgets/origins/resources/mixed_qrc.html b/tests/auto/widgets/origins/resources/mixed_qrc.html new file mode 100644 index 000000000..664f7af6f --- /dev/null +++ b/tests/auto/widgets/origins/resources/mixed_qrc.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> + <head> + <title>Mixed</title> + <script> + var msg; + </script> + </head> + <body> + <iframe src="qrc:///resources/mixed_frame.html"></iframe> + </body> +</html> diff --git a/tests/auto/widgets/origins/resources/mixed_tst.html b/tests/auto/widgets/origins/resources/mixed_tst.html new file mode 100644 index 000000000..627e58098 --- /dev/null +++ b/tests/auto/widgets/origins/resources/mixed_tst.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> + <head> + <title>Mixed</title> + <script> + var msg; + </script> + </head> + <body> + <iframe src="tst:///resources/mixed_frame.html"></iframe> + </body> +</html> diff --git a/tests/auto/widgets/origins/resources/subdir/frame2.html b/tests/auto/widgets/origins/resources/subdir/frame2.html new file mode 100644 index 000000000..3a2f664ca --- /dev/null +++ b/tests/auto/widgets/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/widgets/origins/resources/subdir/index.html b/tests/auto/widgets/origins/resources/subdir/index.html new file mode 100644 index 000000000..9c5d5d782 --- /dev/null +++ b/tests/auto/widgets/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/widgets/origins/resources/subdir_frame1.html b/tests/auto/widgets/origins/resources/subdir_frame1.html new file mode 100644 index 000000000..63973f2f4 --- /dev/null +++ b/tests/auto/widgets/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/widgets/origins/resources/websocket.html b/tests/auto/widgets/origins/resources/websocket.html new file mode 100644 index 000000000..949596d1c --- /dev/null +++ b/tests/auto/widgets/origins/resources/websocket.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> + <head> + <title>WebSocket</title> + <script> + var err; + const ws = new WebSocket("ws://example.invalid"); + ws.addEventListener("close", () => err = event.code); + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/widgets/origins/tst_origins.cpp b/tests/auto/widgets/origins/tst_origins.cpp new file mode 100644 index 000000000..5c798ddc2 --- /dev/null +++ b/tests/auto/widgets/origins/tst_origins.cpp @@ -0,0 +1,268 @@ +/**************************************************************************** +** +** 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 <QtCore/qfile.h> +#include <QtTest/QtTest> +#include <QtWebEngineCore/qwebengineurlrequestjob.h> +#include <QtWebEngineCore/qwebengineurlschemehandler.h> +#include <QtWebEngineWidgets/qwebenginepage.h> +#include <QtWebEngineWidgets/qwebengineprofile.h> +#include <QtWebEngineWidgets/qwebenginesettings.h> + +#define QSL QStringLiteral +#define QBAL QByteArrayLiteral +#define THIS_DIR TESTS_SOURCE_DIR "origins/" + +class TstUrlSchemeHandler final : public QWebEngineUrlSchemeHandler { + Q_OBJECT + +public: + TstUrlSchemeHandler(QWebEngineProfile *profile) + { + profile->installUrlSchemeHandler(QBAL("tst"), this); + } + +private: + void requestStarted(QWebEngineUrlRequestJob *job) override + { + QString pathPrefix = QSL(THIS_DIR); + QString pathSuffix = job->requestUrl().path(); + QFile *file = new QFile(pathPrefix + pathSuffix, job); + if (!file->open(QIODevice::ReadOnly)) { + job->fail(QWebEngineUrlRequestJob::RequestFailed); + return; + } + job->reply(QBAL("text/html"), file); + } +}; + +class tst_Origins final : public QObject { + Q_OBJECT + +private Q_SLOTS: + void jsUrlCanon(); + void jsUrlOrigin(); + void subdirWithAccess(); + void subdirWithoutAccess(); + void mixedSchemes(); + void webSocket(); + +private: + bool load(const QUrl &url) + { + QSignalSpy spy(&m_page, &QWebEnginePage::loadFinished); + m_page.load(url); + return (!spy.empty() || spy.wait()) + && spy.front().value(0).toBool(); + } + + QVariant eval(const QString &code) + { + return evaluateJavaScriptSync(&m_page, code); + } + + QWebEngineProfile m_profile; + QWebEnginePage m_page{&m_profile}; + TstUrlSchemeHandler m_handler{&m_profile}; +}; + +// 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(load(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 'dumb' URL, 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. + 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"))); +} + +// Test origin serialization in Blink, implemented by blink::KURL and +// blink::SecurityOrigin as opposed to GURL and url::Origin. +void tst_Origins::jsUrlOrigin() +{ + QVERIFY(load(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://"))); + + // The qrc scheme should behave like file. + 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. + QCOMPARE(eval(QSL("new URL(\"tst:/banana\").origin")), QVariant(QSL("tst://"))); + QCOMPARE(eval(QSL("new URL(\"tst://foo.com/banana\").origin")), QVariant(QSL("tst://"))); +} + +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(load(QSL("file:" THIS_DIR "resources/subdir/index.html"))); + QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); + QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); + + QVERIFY(load(QSL("qrc:/resources/subdir/index.html"))); + QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); + QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); + + QVERIFY(load(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); + + QVERIFY(load(QSL("file:" THIS_DIR "resources/subdir/index.html"))); + QCOMPARE(eval(QSL("msg[0]")), QVariant()); + QCOMPARE(eval(QSL("msg[1]")), QVariant()); + + QVERIFY(load(QSL("qrc:/resources/subdir/index.html"))); + QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); + QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); + + QVERIFY(load(QSL("tst:/resources/subdir/index.html"))); + QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); + 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. +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"))); +} + +// 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; + + QVERIFY(load(QSL("file:" THIS_DIR "resources/websocket.html"))); + QTRY_VERIFY(eval(QSL("err")) == QVariant(expected)); + + QVERIFY(load(QSL("qrc:/resources/websocket.html"))); + QTRY_VERIFY(eval(QSL("err")) == QVariant(expected)); + + // FIXME(juvaldma): QTBUG-62536 + // QVERIFY(load(QSL("tst:/resources/websocket.html"))); + // QTRY_VERIFY(eval(QSL("err")) == QVariant(expected)); +} + +QTEST_MAIN(tst_Origins) +#include "tst_origins.moc" diff --git a/tests/auto/widgets/origins/tst_origins.qrc b/tests/auto/widgets/origins/tst_origins.qrc new file mode 100644 index 000000000..47be3bd0d --- /dev/null +++ b/tests/auto/widgets/origins/tst_origins.qrc @@ -0,0 +1,12 @@ +<!DOCTYPE RCC> +<RCC version="1.0"> +<qresource> + <file>resources/mixed_frame.html</file> + <file>resources/mixed_qrc.html</file> + <file>resources/mixed_tst.html</file> + <file>resources/subdir/frame2.html</file> + <file>resources/subdir/index.html</file> + <file>resources/subdir_frame1.html</file> + <file>resources/websocket.html</file> +</qresource> +</RCC> diff --git a/tests/auto/widgets/widgets.pro b/tests/auto/widgets/widgets.pro index 36dfaba9f..5a13e8075 100644 --- a/tests/auto/widgets/widgets.pro +++ b/tests/auto/widgets/widgets.pro @@ -3,6 +3,7 @@ QT_FOR_CONFIG += webengine TEMPLATE = subdirs SUBDIRS += \ + origins \ qwebenginedefaultsurfaceformat \ qwebenginedownloads \ qwebenginefaviconmanager \ @@ -32,5 +33,5 @@ qtConfig(webengine-spellchecker):!cross_compile { boot2qt: SUBDIRS -= qwebengineaccessibility qwebenginedefaultsurfaceformat \ qwebenginefaviconmanager qwebenginepage qwebenginehistory \ qwebengineprofile qwebengineschemes qwebenginescript \ - qwebengineview qwebenginedownloads qwebenginesettings - + qwebengineview qwebenginedownloads qwebenginesettings \ + origins |