diff options
Diffstat (limited to 'tests/auto/core/origins/tst_origins.cpp')
-rw-r--r-- | tests/auto/core/origins/tst_origins.cpp | 1077 |
1 files changed, 896 insertions, 181 deletions
diff --git a/tests/auto/core/origins/tst_origins.cpp b/tests/auto/core/origins/tst_origins.cpp index 08f9d097f..81385701f 100644 --- a/tests/auto/core/origins/tst_origins.cpp +++ b/tests/auto/core/origins/tst_origins.cpp @@ -1,42 +1,19 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <util.h> #include "httpserver.h" #include <QtCore/qfile.h> #include <QtTest/QtTest> +#include <QtWebEngineCore/qwebengineurlrequestinterceptor.h> #include <QtWebEngineCore/qwebengineurlrequestjob.h> #include <QtWebEngineCore/qwebengineurlscheme.h> #include <QtWebEngineCore/qwebengineurlschemehandler.h> #include <QtWebEngineCore/qwebenginesettings.h> #include <QtWebEngineCore/qwebengineprofile.h> #include <QtWebEngineCore/qwebenginepage.h> +#include <QtWebEngineWidgets/qwebengineview.h> #if defined(WEBSOCKETS) #include <QtWebSockets/qwebsocket.h> @@ -48,6 +25,8 @@ #define QSL QStringLiteral #define QBAL QByteArrayLiteral +Q_LOGGING_CATEGORY(lc, "qt.webengine.tests") + void registerSchemes() { { @@ -125,14 +104,20 @@ void registerSchemes() } { - QWebEngineUrlScheme scheme(QBAL("redirect1")); + QWebEngineUrlScheme scheme(QBAL("redirect")); scheme.setFlags(QWebEngineUrlScheme::CorsEnabled); QWebEngineUrlScheme::registerScheme(scheme); } { - QWebEngineUrlScheme scheme(QBAL("redirect2")); - scheme.setFlags(QWebEngineUrlScheme::CorsEnabled); + QWebEngineUrlScheme scheme(QBAL("redirect-secure")); + scheme.setFlags(QWebEngineUrlScheme::SecureScheme); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("redirect-local")); + scheme.setFlags(QWebEngineUrlScheme::LocalScheme | QWebEngineUrlScheme::LocalAccessAllowed); QWebEngineUrlScheme::registerScheme(scheme); } @@ -142,7 +127,22 @@ void registerSchemes() QWebEngineUrlScheme::registerScheme(scheme); } { + QWebEngineUrlScheme scheme(QBAL("secure-cors")); + scheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::CorsEnabled); + QWebEngineUrlScheme::registerScheme(scheme); + } + { + QWebEngineUrlScheme scheme(QBAL("localaccess")); + scheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed); + QWebEngineUrlScheme::registerScheme(scheme); + } + { QWebEngineUrlScheme scheme(QBAL("local")); + scheme.setFlags(QWebEngineUrlScheme::LocalScheme); + QWebEngineUrlScheme::registerScheme(scheme); + } + { + QWebEngineUrlScheme scheme(QBAL("local-localaccess")); scheme.setFlags(QWebEngineUrlScheme::LocalScheme | QWebEngineUrlScheme::LocalAccessAllowed); QWebEngineUrlScheme::registerScheme(scheme); } @@ -151,7 +151,15 @@ void registerSchemes() scheme.setFlags(QWebEngineUrlScheme::LocalScheme | QWebEngineUrlScheme::CorsEnabled); QWebEngineUrlScheme::registerScheme(scheme); } - + { + QWebEngineUrlScheme scheme("fetchapi-allowed"); + scheme.setFlags(QWebEngineUrlScheme::CorsEnabled | QWebEngineUrlScheme::FetchApiAllowed); + QWebEngineUrlScheme::registerScheme(scheme); + } + { + QWebEngineUrlScheme scheme("fetchapi-not-allowed"); + QWebEngineUrlScheme::registerScheme(scheme); + } } Q_CONSTRUCTOR_FUNCTION(registerSchemes) @@ -175,10 +183,14 @@ public: 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("redirect"), this); + profile->installUrlSchemeHandler(QBAL("redirect-secure"), this); + profile->installUrlSchemeHandler(QBAL("redirect-local"), this); profile->installUrlSchemeHandler(QBAL("cors"), this); + profile->installUrlSchemeHandler(QBAL("secure-cors"), this); + profile->installUrlSchemeHandler(QBAL("localaccess"), this); profile->installUrlSchemeHandler(QBAL("local"), this); + profile->installUrlSchemeHandler(QBAL("local-localaccess"), this); profile->installUrlSchemeHandler(QBAL("local-cors"), this); } @@ -190,18 +202,24 @@ private: QUrl url = job->requestUrl(); m_requests << url; - if (url.scheme() == QBAL("redirect1")) { - url.setScheme(QBAL("redirect2")); - job->redirect(url); - return; + if (url.scheme().startsWith("redirect")) { + QString path = url.path(); + int idx = path.indexOf(QChar('/')); + if (idx > 0) { + url.setScheme(path.first(idx)); + url.setPath(path.mid(idx, -1)); + job->redirect(url); + return; + } } QString pathPrefix = QDir(QT_TESTCASE_SOURCEDIR).canonicalPath(); if (url.path().startsWith("/qtwebchannel/")) pathPrefix = QSL(":"); QString pathSuffix = url.path(); - QFile *file = new QFile(pathPrefix + pathSuffix, job); + auto file = std::make_unique<QFile>(pathPrefix + pathSuffix, job); if (!file->open(QIODevice::ReadOnly)) { + qWarning() << "Failed to read data for:" << url << file->errorString(); job->fail(QWebEngineUrlRequestJob::RequestFailed); return; } @@ -210,12 +228,69 @@ private: mimeType = QBAL("application/javascript"); else if (pathSuffix.endsWith(QSL(".css"))) mimeType = QBAL("text/css"); - job->reply(mimeType, file); + job->reply(mimeType, file.release()); } QList<QUrl> m_requests; }; +class TestRequestInterceptor : public QWebEngineUrlRequestInterceptor +{ +public: + TestRequestInterceptor() = default; + void interceptRequest(QWebEngineUrlRequestInfo &info) override + { + qCDebug(lc) << this << "Type:" << info.resourceType() << info.requestMethod() << "Navigation:" << info.navigationType() + << info.requestUrl() << "Initiator:" << info.initiator(); + + QUrl url = info.requestUrl(); + requests << url; + if (url.scheme().startsWith("redirect")) { + QString path = url.path(); + int idx = path.indexOf(QChar('/')); + if (idx > 0) { + url.setScheme(path.first(idx)); + url.setPath(path.mid(idx, -1)); + info.redirect(url); + } + } + } + QList<QUrl> requests; +}; + +class TestPage : public QWebEnginePage +{ +public: + TestPage(QWebEngineProfile *profile) : QWebEnginePage(profile, nullptr) + { + } + void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel, + const QString &message, int, + const QString &) override + { + messages << message; + qCDebug(lc) << message; + } + + bool logContainsDoneMarker() const { return messages.contains("TEST:done"); } + + QString findResultInLog() const + { + // make sure we do not have some extra logs from blink + for (auto message : messages) { + QStringList s = message.split(':'); + if (s.size() > 1 && s[0] == "TEST") + return s[1]; + } + return QString(); + } + + void clearLog() { messages.clear(); } + +private: + QStringList messages; +}; + class tst_Origins final : public QObject { Q_OBJECT @@ -232,10 +307,17 @@ private Q_SLOTS: void subdirWithoutAccess(); void fileAccessRemoteUrl_data(); void fileAccessRemoteUrl(); + void fileAccessLocalUrl_data(); + void fileAccessLocalUrl(); + void mixedSchemes_data(); void mixedSchemes(); void mixedSchemesWithCsp(); void mixedXHR_data(); void mixedXHR(); + void mixedContent_data(); + void mixedContent(); + void localMediaBlock_data(); + void localMediaBlock(); #if defined(WEBSOCKETS) void webSocket(); #endif @@ -244,7 +326,17 @@ private Q_SLOTS: void serviceWorker(); void viewSource(); void createObjectURL(); - void redirect(); + void redirectScheme(); + void redirectSchemeLocal(); + void redirectSchemeSecure(); + void redirectInterceptor(); + void redirectInterceptorLocal(); + void redirectInterceptorSecure(); + void redirectInterceptorFile(); + void redirectInterceptorHttp(); + void fetchApiCustomUrl_data(); + void fetchApiCustomUrl(); + void fetchApiHttpUrl(); private: bool verifyLoad(const QUrl &url) @@ -261,7 +353,7 @@ private: } QWebEngineProfile m_profile; - QWebEnginePage *m_page = nullptr; + TestPage *m_page = nullptr; TstUrlSchemeHandler *m_handler = nullptr; }; @@ -282,7 +374,7 @@ void tst_Origins::cleanupTestCase() void tst_Origins::init() { - m_page = new QWebEnginePage(&m_profile, nullptr); + m_page = new TestPage(&m_profile); } void tst_Origins::cleanup() @@ -382,8 +474,8 @@ void tst_Origins::jsUrlRelative() // 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()); + 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:///bar"))); // 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"))); @@ -500,8 +592,6 @@ 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()); @@ -519,27 +609,112 @@ void tst_Origins::subdirWithoutAccess() void tst_Origins::fileAccessRemoteUrl_data() { QTest::addColumn<bool>("EnableAccess"); - QTest::addRow("enabled") << true; - QTest::addRow("disabled") << false; + QTest::addColumn<bool>("UserGesture"); + QTest::addRow("enabled, XHR") << true << false; + QTest::addRow("enabled, link click") << true << true; + QTest::addRow("disabled, XHR") << false << false; + QTest::addRow("disabled, link click") << false << true; } void tst_Origins::fileAccessRemoteUrl() { QFETCH(bool, EnableAccess); + QFETCH(bool, UserGesture); + + QWebEngineView view; + view.setPage(m_page); + view.resize(800, 600); + view.show(); 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"))); + ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessRemoteUrls, EnableAccess); + ScopedAttribute sa2(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false); - QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedXHR.html")); + if (UserGesture) { + QString remoteUrl(server.url("/link.html").toString()); +#ifdef Q_OS_WIN + QString localUrl("file:///" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/link.html?linkLocation=" + remoteUrl); +#else + QString localUrl("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/link.html?linkLocation=" + remoteUrl); +#endif + + QVERIFY(verifyLoad(localUrl)); + + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(m_page, "link")); + // Succeed independently of EnableAccess == false + QTRY_COMPARE(m_page->url(), remoteUrl); + + // Back/forward navigation is also allowed, however they are not user gesture + m_page->triggerAction(QWebEnginePage::Back); + QTRY_COMPARE(m_page->url(), localUrl); + m_page->triggerAction(QWebEnginePage::Forward); + QTRY_COMPARE(m_page->url(), remoteUrl); + } else { + 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"))); + } +} + +void tst_Origins::fileAccessLocalUrl_data() +{ + QTest::addColumn<bool>("EnableAccess"); + QTest::addColumn<bool>("UserGesture"); + QTest::addRow("enabled, XHR") << true << false; + QTest::addRow("enabled, link click") << true << true; + QTest::addRow("disabled, XHR") << false << false; + QTest::addRow("disabled, link click") << false << true; +} + +void tst_Origins::fileAccessLocalUrl() +{ + QFETCH(bool, EnableAccess); + QFETCH(bool, UserGesture); + + QWebEngineView view; + view.setPage(m_page); + view.resize(800, 600); + view.show(); + + ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, EnableAccess); + ScopedAttribute sa2(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false); + + if (UserGesture) { +#ifdef Q_OS_WIN + QString localUrl1("file:///" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/link.html?linkLocation=link.html"); + QString localUrl2("file:///" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/link.html"); +#else + QString localUrl1("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/link.html?linkLocation=link.html"); + QString localUrl2("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/link.html"); +#endif - eval("sendXHR('" + server.url("/mixedXHR.txt").toString() + "')"); - QTRY_COMPARE(eval("result"), (EnableAccess ? QString("ok") : QString("error"))); + QVERIFY(verifyLoad(localUrl1)); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(m_page, "link")); + // Succeed independently of EnableAccess == false + QTRY_COMPARE(m_page->url(), localUrl2); + + // Back/forward navigation is also allowed, however they are not user gesture + m_page->triggerAction(QWebEnginePage::Back); + QTRY_COMPARE(m_page->url(), localUrl1); + m_page->triggerAction(QWebEnginePage::Forward); + QTRY_COMPARE(m_page->url(), localUrl2); + } else { + QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedXHR.html")); + eval("sendXHR('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/mixedXHR.txt" + "')"); + QTRY_COMPARE(eval("result"), (EnableAccess ? QString("ok") : QString("error"))); + } } // Load the main page over one scheme with an iframe over another scheme. @@ -550,105 +725,149 @@ void tst_Origins::fileAccessRemoteUrl() // 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() +void tst_Origins::mixedSchemes_data() { - 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"))); + QTest::addColumn<QString>("schemeFrom"); + QTest::addColumn<QVariantMap>("testPairs"); - QVERIFY(verifyLoad(QSL("qrc:/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"))); - 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"))); + QVariant SLF = QVariant(QSL("canLoadAndAccess")), OK = QVariant(QSL("canLoadButNotAccess")), + ERR = QVariant(QSL("cannotLoad")); + std::vector<std::pair<const char *, std::vector<std::pair<const char *, QVariant>>>> data = { + { "file", + { + { "file", SLF }, + { "qrc", OK }, + { "tst", ERR }, + } }, + { "qrc", + { + { "file", ERR }, + { "qrc", SLF }, + { "tst", OK }, + } }, + { "tst", + { + { "file", ERR }, + { "qrc", OK }, + { "tst", SLF }, + } }, + { "PathSyntax", + { + { "PathSyntax", SLF }, + { "PathSyntax-Local", ERR }, + { "PathSyntax-LocalAccessAllowed", OK }, + { "PathSyntax-NoAccessAllowed", OK }, + } }, + { "PathSyntax-LocalAccessAllowed", + { + { "PathSyntax", OK }, + { "PathSyntax-Local", OK }, + { "PathSyntax-LocalAccessAllowed", SLF }, + { "PathSyntax-NoAccessAllowed", OK }, + } }, + { "PathSyntax-NoAccessAllowed", + { + { "PathSyntax", OK }, + { "PathSyntax-Local", ERR }, + { "PathSyntax-LocalAccessAllowed", OK }, + { "PathSyntax-NoAccessAllowed", OK }, + } }, + { "HostSyntax://a", + { + { "HostSyntax://a", SLF }, + { "HostSyntax://b", OK }, + } }, + { "local-localaccess", + { + { "local-cors", OK }, + { "local-localaccess", SLF }, + { "local", OK }, + } }, + { "local-cors", + { + { "local", OK }, + { "local-cors", SLF }, + } }, + }; - 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"))); + for (auto &&d : data) { + auto schemeFrom = d.first; + QVariantMap testPairs; + for (auto &&destSchemes : d.second) { + auto &&destScheme = destSchemes.first; + testPairs[destScheme] = destSchemes.second; + } + QTest::addRow("%s", schemeFrom) << schemeFrom << testPairs; + } +} - 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"))); +static QStringList protocolAndHost(const QString scheme) +{ + static QString srcDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()); + QStringList result; + if (scheme == QSL("file")) { + return QStringList{ scheme, srcDir }; + } + if (scheme.contains(QSL("HostSyntax:"))) { + const QStringList &res = scheme.split(':'); + Q_ASSERT(res.size() == 2); + return res; + } + return QStringList{ scheme, "" }; +} - 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"))); +void tst_Origins::mixedSchemes() +{ + QFETCH(QString, schemeFrom); + QFETCH(QVariantMap, testPairs); - 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"))); + ScopedAttribute sa(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false); + QString srcDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()); + QString host; + auto pah = protocolAndHost(schemeFrom); + auto loadUrl = QString("%1:%2/resources/mixedSchemes.html").arg(pah[0]).arg(pah[1]); + QVERIFY(verifyLoad(loadUrl)); - 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"))); + QStringList schemesTo, expected, results; + for (auto it = testPairs.begin(), end = testPairs.end(); it != end; ++it) { + + auto schemeTo = it.key(); + auto pah = protocolAndHost(schemeTo); + auto expectedResult = it.value().toString(); + auto frameUrl = QString("%1:%2/resources/mixedSchemes_frame.html").arg(pah[0]).arg(pah[1]); + auto imgUrl = QString("%1:%2/resources/red.png").arg(pah[0]).arg(pah[1]); + + eval(QString("setIFrameUrl('%1','%2')").arg(frameUrl).arg(imgUrl)); + + // wait for token in the log + QTRY_VERIFY(m_page->logContainsDoneMarker()); + const QString result = m_page->findResultInLog(); + m_page->clearLog(); + schemesTo.append(schemeTo.rightJustified(20)); + results.append(result.rightJustified(20)); + expected.append(expectedResult.rightJustified(20)); + } + + QVERIFY2(results == expected, + qPrintable(QString("\nFrom '%1' to:\n\tScheme: %2\n\tActual: %3\n\tExpect: %4") + .arg(schemeFrom) + .arg(schemesTo.join(' ')) + .arg(results.join(' ')) + .arg(expected.join(' ')))); } // 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"))); } @@ -682,55 +901,90 @@ void tst_Origins::mixedXHR_data() std::pair<const char *, std::vector< std::pair<const char *, std::vector<QVariant>>>>> data = { { "file", { - { "file", { OK, OK, ERR, OK } }, - { "qrc", { ERR, ERR, ERR, ERR } }, - { "tst", { ERR, ERR, ERR, ERR } }, - { "data", { OK, OK, OK, OK } }, - { "cors", { ERR, OK, ERR, OK } }, - { "local", { ERR, ERR, ERR, ERR } }, - { "local-cors", { OK, OK, OK, OK } }, } }, + { "file", { OK, OK, ERR, ERR } }, + { "qrc", { ERR, ERR, ERR, ERR } }, + { "tst", { ERR, ERR, ERR, ERR } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { ERR, OK, ERR, OK } }, + { "local-localaccess", { OK, OK, ERR, ERR } }, + { "local-cors", { OK, OK, ERR, ERR } }, } }, { "qrc", { - { "file", { ERR, ERR, ERR, ERR } }, - { "qrc", { OK, OK, OK, OK } }, - { "tst", { ERR, ERR, ERR, ERR } }, - { "data", { OK, OK, OK, OK } }, - { "cors", { OK, OK, OK, OK } }, - { "local", { ERR, ERR, ERR, ERR } }, - { "local-cors", { ERR, ERR, ERR, ERR } }, } }, + { "file", { ERR, ERR, ERR, ERR } }, + { "qrc", { OK, OK, OK, OK } }, + { "tst", { ERR, ERR, ERR, ERR } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { OK, OK, OK, OK } }, + { "local-localaccess", { ERR, ERR, ERR, ERR } }, + { "local-cors", { ERR, ERR, ERR, ERR } }, } }, { "tst", { - { "file", { ERR, ERR, ERR, ERR } }, - { "qrc", { ERR, ERR, ERR, ERR } }, - { "tst", { OK, OK, OK, OK } }, - { "data", { OK, OK, OK, OK } }, - { "cors", { OK, OK, OK, OK } }, - { "local", { ERR, ERR, ERR, ERR } }, - { "local-cors", { ERR, ERR, ERR, ERR } }, } }, - - { "local", { - { "file", { ERR, ERR, ERR, ERR } }, - { "qrc", { ERR, ERR, ERR, ERR } }, - { "tst", { ERR, ERR, ERR, ERR } }, - { "data", { OK, OK, OK, OK } }, - { "cors", { ERR, OK, ERR, OK } }, - { "local", { OK, OK, ERR, OK } }, - { "local-cors", { OK, OK, OK, OK } }, } }, + { "file", { ERR, ERR, ERR, ERR } }, + { "qrc", { ERR, ERR, ERR, ERR } }, + { "tst", { OK, OK, OK, OK } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { OK, OK, OK, OK } }, + { "local-localaccess", { ERR, ERR, ERR, ERR } }, + { "local-cors", { ERR, ERR, ERR, ERR } }, } }, + + { "cors", { // -local +cors -local-access + { "file", { ERR, ERR, ERR, ERR } }, + { "qrc", { ERR, ERR, ERR, ERR } }, + { "tst", { ERR, ERR, ERR, ERR } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { OK, OK, OK, OK } }, + { "local-localaccess", { ERR, ERR, ERR, ERR } }, + { "local-cors", { ERR, ERR, ERR, ERR } }, } }, + + { "local", { // +local -cors -local-access + { "file", { OK, OK, ERR, ERR } }, + { "qrc", { ERR, ERR, ERR, ERR } }, + { "tst", { ERR, ERR, ERR, ERR } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { ERR, OK, ERR, OK } }, + { "local-localaccess", { OK, OK, ERR, ERR } }, + { "local-cors", { OK, OK, ERR, ERR } }, } }, + + { "local-cors", { // +local +cors -local-access + { "file", { OK, OK, ERR, ERR } }, + { "qrc", { ERR, ERR, ERR, ERR } }, + { "tst", { ERR, ERR, ERR, ERR } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { ERR, OK, ERR, OK } }, + { "local-localaccess", { OK, OK, ERR, ERR } }, + { "local-cors", { OK, OK, ERR, ERR } }, } }, + + { "local-localaccess", { // +local -cors +local-access + { "file", { OK, OK, OK, OK } }, + { "qrc", { ERR, ERR, ERR, ERR } }, + { "tst", { ERR, ERR, ERR, ERR } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { ERR, OK, ERR, OK } }, + { "local-localaccess", { OK, OK, OK, OK } }, + { "local-cors", { OK, OK, OK, OK } }, } }, + + { "localaccess", { // -local -cors +local-access + { "file", { OK, OK, OK, OK } }, + { "qrc", { ERR, ERR, ERR, ERR } }, + { "tst", { ERR, ERR, ERR, ERR } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { OK, OK, OK, OK } }, + { "local-localaccess", { OK, OK, OK, OK } }, + { "local-cors", { OK, OK, OK, OK } }, } }, }; for (auto &&d : data) { auto schemeFrom = d.first; for (int i = 0; i < 4; ++i) { - auto it = settingCombinations.begin() + i; - bool canAccessFileUrls = it->first, canAccessRemoteUrl = it->second; + const auto &it = settingCombinations[i]; + bool canAccessFileUrls = it.first, canAccessRemoteUrl = it.second; QVariantMap testPairs; for (auto &&destSchemes : d.second) { auto &&destScheme = destSchemes.first; auto &&expectedResults = destSchemes.second; - auto rit = expectedResults.begin() + i; - testPairs[destScheme] = *rit; + testPairs[destScheme] = expectedResults[i]; } QTest::addRow("%s_%s_%s", schemeFrom, (canAccessFileUrls ? "local" : "nolocal"), (canAccessRemoteUrl ? "remote" : "noremote")) @@ -779,6 +1033,174 @@ void tst_Origins::mixedXHR() .arg(schemeFrom).arg(schemesTo.join(' ')).arg(results.join(' ')).arg(expected.join(' ')))); } +// Load the main page over one scheme, then load an iframe over a different scheme. This load is not considered CORS. +void tst_Origins::mixedContent_data() +{ + QTest::addColumn<QString>("schemeFrom"); + QTest::addColumn<bool>("canAccessFileUrls"); + QTest::addColumn<bool>("canAccessRemoteUrl"); + QTest::addColumn<QVariantMap>("testPairs"); + + bool defaultFileAccess = true; + bool defaultRemoteAccess = false; + std::vector<std::pair<bool, bool>> settingCombinations = { + { defaultFileAccess, defaultRemoteAccess }, // tag: *schemeFrom*_local_noremote + { defaultFileAccess, !defaultRemoteAccess }, // tag: *schemeFrom*_local_remote + { !defaultFileAccess, defaultRemoteAccess }, // tag: *schemeFrom*_nolocal_noremote + { !defaultFileAccess, !defaultRemoteAccess } // tag: *schemeFrom*_nolocal_remote + }; + + QVariant SLF = QVariant(QSL("canLoadAndAccess")), OK = QVariant(QSL("canLoadButNotAccess")), ERR = QVariant(QSL("cannotLoad")); + std::vector< + std::pair<const char *, std::vector< + std::pair<const char *, std::vector<QVariant>>>>> data = { + { "file", { + { "file", { SLF, SLF, ERR, ERR } }, + { "qrc", { OK, OK, OK, OK } }, + { "tst", { ERR, OK, ERR, OK } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { ERR, OK, ERR, OK } }, + { "local-localaccess", { OK, OK, ERR, ERR } }, + { "local-cors", { OK, OK, ERR, ERR } }, + } }, + + { "qrc", { + { "file", { ERR, ERR, ERR, ERR } }, + { "qrc", { SLF, SLF, SLF, SLF } }, + { "tst", { OK, OK, OK, OK } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { OK, OK, OK, OK } }, + { "local-localaccess", { ERR, ERR, ERR, ERR } }, + { "local-cors", { ERR, ERR, ERR, ERR } }, } }, + + { "tst", { + { "file", { ERR, ERR, ERR, ERR } }, + { "qrc", { OK, OK, OK, OK } }, + { "tst", { SLF, SLF, SLF, SLF } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { OK, OK, OK, OK } }, + { "local-localaccess", { ERR, ERR, ERR, ERR } }, + { "local-cors", { ERR, ERR, ERR, ERR } }, } }, + + { "cors", { // -local +cors -local-access + { "file", { ERR, ERR, ERR, ERR } }, + { "qrc", { OK, OK, OK, OK } }, + { "tst", { OK, OK, OK, OK } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { SLF, SLF, SLF, SLF } }, + { "local-localaccess", { ERR, ERR, ERR, ERR } }, + { "local-cors", { ERR, ERR, ERR, ERR } }, } }, + + { "local", { // +local -cors -local-access + { "file", { OK, OK, ERR, ERR } }, + { "qrc", { OK, OK, OK, OK } }, + { "tst", { ERR, OK, ERR, OK } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { ERR, OK, ERR, OK } }, + { "local-localaccess", { OK, OK, ERR, ERR } }, + { "local-cors", { OK, OK, ERR, ERR } }, + } }, + + { "local-cors", { // +local +cors -local-access + { "file", { OK, OK, ERR, ERR } }, + { "qrc", { OK, OK, OK, OK } }, + { "tst", { ERR, OK, ERR, OK } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { ERR, OK, ERR, OK } }, + { "local-localaccess", { OK, OK, ERR, ERR } }, + { "local-cors", { SLF, SLF, ERR, ERR } }, + } }, + + { "local-localaccess", { // +local -cors + OK-access + { "file", { OK, OK, OK, OK } }, + { "qrc", { OK, OK, OK, OK } }, + { "tst", { ERR, OK, ERR, OK } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { ERR, OK, ERR, OK } }, + { "local-localaccess", { SLF, SLF, OK, OK } }, // ### should probably be: SLF, SLF, SLF, SLF + { "local-cors", { OK, OK, OK, OK } }, + } }, + + { "localaccess", { // -local -cors +local-access + { "file", { OK, OK, OK, OK } }, + { "qrc", { OK, OK, OK, OK } }, + { "tst", { OK, OK, OK, OK } }, + { "data", { OK, OK, OK, OK } }, + { "cors", { OK, OK, OK, OK } }, + { "local-localaccess", { OK, OK, OK, OK } }, + { "local-cors", { OK, OK, OK, OK } }, } }, + }; + + for (auto &&d : data) { + auto schemeFrom = d.first; + + for (int i = 0; i < 4; ++i) { + const auto &it = settingCombinations[i]; + bool canAccessFileUrls = it.first, canAccessRemoteUrl = it.second; + + QVariantMap testPairs; + for (auto &&destSchemes : d.second) { + auto &&destScheme = destSchemes.first; + auto &&expectedResults = destSchemes.second; + testPairs[destScheme] = expectedResults[i]; + } + + QTest::addRow("%s_%s_%s", schemeFrom, (canAccessFileUrls ? "local" : "nolocal"), (canAccessRemoteUrl ? "remote" : "noremote")) + << schemeFrom << canAccessFileUrls << canAccessRemoteUrl << testPairs; + } + } +} + +void tst_Origins::mixedContent() +{ + QFETCH(QString, schemeFrom); + QFETCH(bool, canAccessFileUrls); + QFETCH(bool, canAccessRemoteUrl); + QFETCH(QVariantMap, testPairs); + + QString srcDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()); + auto loadUrl = QString("%1:%2/resources/mixedSchemes.html").arg(schemeFrom).arg(schemeFrom == "file" ? srcDir : ""); + + QCOMPARE(testPairs.size(), 7); + ScopedAttribute sa2(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false); + ScopedAttribute sa0(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, canAccessFileUrls); + ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessRemoteUrls, canAccessRemoteUrl); + QVERIFY(verifyLoad(loadUrl)); + + auto setIFrameUrl = [&] (const QString &scheme) { + if (scheme == "data") + return QString("setIFrameUrl('data:,<script>var canary = true; parent.canary = " + "true</script>','data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/" + "w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==')"); + auto frameUrl = QString("%1:%2/resources/mixedSchemes_frame.html").arg(scheme).arg(scheme == "file" ? srcDir : ""); + auto imgUrl = + QString("%1:%2/resources/red.png").arg(scheme).arg(scheme == "file" ? srcDir : ""); + return QString("setIFrameUrl('%1','%2')").arg(frameUrl).arg(imgUrl); + }; + + m_page->clearLog(); + QStringList schemesTo, expected, results; + for (auto it = testPairs.begin(), end = testPairs.end(); it != end; ++it) { + + auto schemeTo = it.key(); + auto expectedResult = it.value().toString(); + + eval(setIFrameUrl(schemeTo)); + + // wait for token in the log + QTRY_VERIFY(m_page->logContainsDoneMarker()); + const QString result = m_page->findResultInLog(); + m_page->clearLog(); + schemesTo.append(schemeTo.rightJustified(20)); + results.append(result.rightJustified(20)); + expected.append(expectedResult.rightJustified(20)); + } + QVERIFY2(results == expected, + qPrintable(QString("\nFrom '%1' to:\n\tScheme: %2\n\tActual: %3\n\tExpect: %4") + .arg(schemeFrom).arg(schemesTo.join(' ')).arg(results.join(' ')).arg(expected.join(' ')))); +} + #if defined(WEBSOCKETS) class EchoServer : public QObject { Q_OBJECT @@ -932,12 +1354,12 @@ void tst_Origins::serviceWorker() 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"))); + .contains(QSL("Cannot read properties 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"))); + .contains(QSL("Cannot read properties of undefined"))); QVERIFY(verifyLoad(QSL("PathSyntax-Secure:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); @@ -947,7 +1369,7 @@ void tst_Origins::serviceWorker() 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"))); + .contains(QSL("Cannot read properties of undefined"))); QVERIFY(verifyLoad(QSL("PathSyntax-Secure-ServiceWorkersAllowed:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); @@ -956,7 +1378,7 @@ void tst_Origins::serviceWorker() 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"))); + .contains(QSL("Cannot read properties of undefined"))); } // Support for view-source must be enabled explicitly. @@ -998,17 +1420,310 @@ void tst_Origins::createObjectURL() QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:tst:"))); } -void tst_Origins::redirect() +void tst_Origins::redirectScheme() +{ + QVERIFY(verifyLoad(QSL("redirect:cors/resources/redirect.html"))); + eval("addStylesheetLink('redirect:cors/resources/redirect.css')"); + QTRY_COMPARE(m_handler->requests().size(), 4); + QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("redirect:cors/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("cors:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[2], QUrl(QStringLiteral("redirect:cors/resources/redirect.css"))); + QCOMPARE(m_handler->requests()[3], QUrl(QStringLiteral("cors:/resources/redirect.css"))); + + QVERIFY(!verifyLoad(QSL("redirect:file/resources/redirect.html"))); + QVERIFY(!verifyLoad(QSL("redirect:local/resources/redirect.html"))); + QVERIFY(!verifyLoad(QSL("redirect:local-cors/resources/redirect.html"))); +} + +void tst_Origins::redirectSchemeLocal() +{ + QVERIFY(verifyLoad(QSL("redirect-local:local/resources/redirect.html"))); + eval("addStylesheetLink('redirect-local:local/resources/redirect.css')"); + QTRY_COMPARE(m_handler->requests().size(), 4); + QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("redirect-local:local/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("local:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[2], QUrl(QStringLiteral("redirect-local:local/resources/redirect.css"))); + QCOMPARE(m_handler->requests()[3], QUrl(QStringLiteral("local:/resources/redirect.css"))); +} + +void tst_Origins::redirectSchemeSecure() +{ + QVERIFY(verifyLoad(QSL("redirect-secure:secure-cors/resources/redirect.html"))); + eval("addStylesheetLink('redirect-secure:secure-cors/resources/redirect.css')"); + QTRY_COMPARE(m_handler->requests().size(), 4); + QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("redirect-secure:secure-cors/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("secure-cors:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[2], QUrl(QStringLiteral("redirect-secure:secure-cors/resources/redirect.css"))); + QCOMPARE(m_handler->requests()[3], QUrl(QStringLiteral("secure-cors:/resources/redirect.css"))); +} + +void tst_Origins::redirectInterceptor() +{ + TestRequestInterceptor interceptor; + m_profile.setUrlRequestInterceptor(&interceptor); + + QVERIFY(verifyLoad(QSL("redirect:cors/resources/redirect.html"))); + eval("addStylesheetLink('redirect:cors/resources/redirect.css')"); + + QTRY_COMPARE(interceptor.requests.size(), 4); + QTRY_COMPARE(m_handler->requests().size(), 2); + QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("cors:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("cors:/resources/redirect.css"))); + + QCOMPARE(interceptor.requests[0], QUrl(QStringLiteral("redirect:cors/resources/redirect.html"))); + QCOMPARE(interceptor.requests[1], QUrl(QStringLiteral("cors:/resources/redirect.html"))); + QCOMPARE(interceptor.requests[2], QUrl(QStringLiteral("redirect:cors/resources/redirect.css"))); + QCOMPARE(interceptor.requests[3], QUrl(QStringLiteral("cors:/resources/redirect.css"))); + + QVERIFY(!verifyLoad(QSL("redirect:file/resources/redirect.html"))); + QVERIFY(!verifyLoad(QSL("redirect:local/resources/redirect.html"))); + QVERIFY(!verifyLoad(QSL("redirect:local-cors/resources/redirect.html"))); +} + +void tst_Origins::redirectInterceptorLocal() +{ + TestRequestInterceptor interceptor; + m_profile.setUrlRequestInterceptor(&interceptor); + + QVERIFY(verifyLoad(QSL("redirect-local:local/resources/redirect.html"))); + eval("addStylesheetLink('redirect-local:local/resources/redirect.css')"); + + QTRY_COMPARE(interceptor.requests.size(), 4); + QTRY_COMPARE(m_handler->requests().size(), 2); + QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("local:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("local:/resources/redirect.css"))); + + QCOMPARE(interceptor.requests[0], QUrl(QStringLiteral("redirect-local:local/resources/redirect.html"))); + QCOMPARE(interceptor.requests[1], QUrl(QStringLiteral("local:/resources/redirect.html"))); + QCOMPARE(interceptor.requests[2], QUrl(QStringLiteral("redirect-local:local/resources/redirect.css"))); + QCOMPARE(interceptor.requests[3], QUrl(QStringLiteral("local:/resources/redirect.css"))); +} + +void tst_Origins::redirectInterceptorSecure() { - 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"))); + TestRequestInterceptor interceptor; + m_profile.setUrlRequestInterceptor(&interceptor); + + QVERIFY(verifyLoad(QSL("redirect-secure:secure-cors/resources/redirect.html"))); + eval("addStylesheetLink('redirect-secure:secure-cors/resources/redirect.css')"); + + QTRY_COMPARE(interceptor.requests.size(), 4); + QTRY_COMPARE(m_handler->requests().size(), 2); + QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("secure-cors:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("secure-cors:/resources/redirect.css"))); + + QCOMPARE(interceptor.requests[0], QUrl(QStringLiteral("redirect-secure:secure-cors/resources/redirect.html"))); + QCOMPARE(interceptor.requests[1], QUrl(QStringLiteral("secure-cors:/resources/redirect.html"))); + QCOMPARE(interceptor.requests[2], QUrl(QStringLiteral("redirect-secure:secure-cors/resources/redirect.css"))); + QCOMPARE(interceptor.requests[3], QUrl(QStringLiteral("secure-cors:/resources/redirect.css"))); +} + +class TestRedirectInterceptor : public QWebEngineUrlRequestInterceptor +{ +public: + TestRedirectInterceptor() = default; + void interceptRequest(QWebEngineUrlRequestInfo &info) override + { + qCDebug(lc) << this << "Type:" << info.resourceType() << info.requestMethod() << "Navigation:" << info.navigationType() + << info.requestUrl() << "Initiator:" << info.initiator(); + + QUrl url = info.requestUrl(); + requests << url; + if (url.path().startsWith("/redirect")) { + QString path = url.path(); + int idx = path.indexOf(QChar('/'), 10); + if (idx > 0) { + url.setScheme(path.mid(10, idx - 10)); + url.setPath(path.mid(idx, -1)); + url.setHost({}); + info.redirect(url); + } + } + } + QList<QUrl> requests; +}; + +void tst_Origins::redirectInterceptorFile() +{ + TestRedirectInterceptor interceptor; + m_profile.setUrlRequestInterceptor(&interceptor); + + QVERIFY(verifyLoad(QSL("file:///redirect/local-cors/resources/redirect.html"))); + eval("addStylesheetLink('file:///redirect/local-cors/resources/redirect.css')"); + + QTRY_COMPARE(interceptor.requests.size(), 4); + QTRY_COMPARE(m_handler->requests().size(), 2); + QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("local-cors:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("local-cors:/resources/redirect.css"))); + + QCOMPARE(interceptor.requests[0], QUrl(QStringLiteral("file:///redirect/local-cors/resources/redirect.html"))); + QCOMPARE(interceptor.requests[1], QUrl(QStringLiteral("local-cors:/resources/redirect.html"))); + QCOMPARE(interceptor.requests[2], QUrl(QStringLiteral("file:///redirect/local-cors/resources/redirect.css"))); + QCOMPARE(interceptor.requests[3], QUrl(QStringLiteral("local-cors:/resources/redirect.css"))); +} + +void tst_Origins::redirectInterceptorHttp() +{ + TestRedirectInterceptor interceptor; + m_profile.setUrlRequestInterceptor(&interceptor); + + QVERIFY(verifyLoad(QSL("http://hallo/redirect/cors/resources/redirect.html"))); + eval("addStylesheetLink('http://hallo/redirect/cors/resources/redirect.css')"); + + QTRY_COMPARE(interceptor.requests.size(), 4); + QTRY_COMPARE(m_handler->requests().size(), 2); + QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("cors:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("cors:/resources/redirect.css"))); + + QCOMPARE(interceptor.requests[0], QUrl(QStringLiteral("http://hallo/redirect/cors/resources/redirect.html"))); + QCOMPARE(interceptor.requests[1], QUrl(QStringLiteral("cors:/resources/redirect.html"))); + QCOMPARE(interceptor.requests[2], QUrl(QStringLiteral("http://hallo/redirect/cors/resources/redirect.css"))); + QCOMPARE(interceptor.requests[3], QUrl(QStringLiteral("cors:/resources/redirect.css"))); +} + +void tst_Origins::localMediaBlock_data() +{ + QTest::addColumn<bool>("enableAccess"); + QTest::addRow("enabled") << true; + QTest::addRow("disabled") << false; +} + +void tst_Origins::localMediaBlock() +{ + QFETCH(bool, enableAccess); + + std::atomic<bool> accessed = false; + HttpServer server; + server.setResourceDirs({ QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + "/resources" }); + connect(&server, &HttpServer::newRequest, [&](HttpReqRep *) { accessed.store(true); }); + QVERIFY(server.start()); + + ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessRemoteUrls, enableAccess); + + QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + "/resources/media.html")); + eval("addAudio('" + server.url("/mixedXHR.txt").toString() + "')"); + + // Give it a chance to avoid a false positive on the default value of accessed. + if (!enableAccess) + QTest::qSleep(500); + QTRY_COMPARE(accessed.load(), enableAccess); + +} + +class FetchApiHandler : public QWebEngineUrlSchemeHandler +{ + Q_OBJECT +public: + FetchApiHandler(QByteArray schemeName, QObject *parent = nullptr) + : QWebEngineUrlSchemeHandler(parent), m_schemeName(schemeName) + { + } + + void requestStarted(QWebEngineUrlRequestJob *job) override + { + QCOMPARE(job->requestUrl(), QUrl(m_schemeName + ":about")); + fetchWasAllowed = true; + } + + bool fetchWasAllowed = false; + +private: + QByteArray m_schemeName; +}; + +class FetchApiPage : public QWebEnginePage +{ + Q_OBJECT + +signals: + void jsCalled(); + +public: + FetchApiPage(QWebEngineProfile *profile, QObject *parent = nullptr) + : QWebEnginePage(profile, parent) + { + } + +protected: + void javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel, + const QString &message, int, const QString &) override + { + qCritical() << "js:" << message; + emit jsCalled(); + } +}; + +void tst_Origins::fetchApiCustomUrl_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QByteArray>("fetchApiScheme"); + QTest::addColumn<bool>("expectedFetchWasAllowed"); + + QTest::newRow("custom url with fetch allowed flag") + << QUrl("qrc:///resources/fetchApi.html?printRes=false&url=fetchapi-allowed:about") + << QBAL("fetchapi-allowed") << true; + QTest::newRow("custom url without fetch allowed flag") + << QUrl("qrc:///resources/fetchApi.html?printRes=false&url=fetchapi-not-allowed:about") + << QBAL("fetchapi-not-allowed") << false; +} + +void tst_Origins::fetchApiCustomUrl() +{ + QFETCH(QUrl, url); + QFETCH(QByteArray, fetchApiScheme); + QFETCH(bool, expectedFetchWasAllowed); + + QWebEngineProfile profile; + FetchApiHandler handler(fetchApiScheme); + + profile.installUrlSchemeHandler(fetchApiScheme, &handler); + + FetchApiPage page(&profile); + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + QSignalSpy jsSpy(&page, SIGNAL(jsCalled())); + + if (fetchApiScheme == "fetchapi-not-allowed") { + QTest::ignoreMessage(QtCriticalMsg, QRegularExpression("Failed to fetch")); + QTest::ignoreMessage( + QtCriticalMsg, + QRegularExpression("Fetch API cannot load fetchapi-not-allowed:about.")); + } + + page.load(url); + QTRY_VERIFY(loadSpy.count() > 0); + QTRY_COMPARE(handler.fetchWasAllowed, expectedFetchWasAllowed); + + if (fetchApiScheme == "fetchapi-not-allowed") { + QTRY_VERIFY(jsSpy.count() > 0); + } +} + +void tst_Origins::fetchApiHttpUrl() +{ + HttpServer httpServer; + QObject::connect(&httpServer, &HttpServer::newRequest, this, [](HttpReqRep *rr) { + rr->setResponseBody(QBAL("Fetch Was Allowed")); + rr->setResponseHeader(QBAL("Access-Control-Allow-Origin"), QBAL("*")); + rr->sendResponse(); + }); + QVERIFY(httpServer.start()); + + QWebEngineProfile profile; + FetchApiPage page(&profile); + + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + QSignalSpy jsSpy(&page, SIGNAL(jsCalled())); + + QTest::ignoreMessage(QtCriticalMsg, QRegularExpression("Fetch Was Allowed")); + + const QByteArray fullUrl = QByteArray("qrc:///resources/fetchApi.html?printRes=true&url=") + + httpServer.url("/somepage.html").toEncoded(); + page.load(QUrl(fullUrl)); + + QTRY_VERIFY(loadSpy.count() > 0); + QTRY_VERIFY(jsSpy.count() > 0); + QVERIFY(httpServer.stop()); } QTEST_MAIN(tst_Origins) |