diff options
21 files changed, 946 insertions, 58 deletions
diff --git a/src/core/api/core_api.pro b/src/core/api/core_api.pro index 1d66b47d0..e0600e6fe 100644 --- a/src/core/api/core_api.pro +++ b/src/core/api/core_api.pro @@ -43,6 +43,7 @@ HEADERS = \ qwebengineurlrequestinfo.h \ qwebengineurlrequestinfo_p.h \ qwebengineurlrequestjob.h \ + qwebengineurlscheme.h \ qwebengineurlschemehandler.h SOURCES = \ @@ -53,6 +54,7 @@ SOURCES = \ qwebengineregisterprotocolhandlerrequest.cpp \ qwebengineurlrequestinfo.cpp \ qwebengineurlrequestjob.cpp \ + qwebengineurlscheme.cpp \ qwebengineurlschemehandler.cpp ### Qt6 Remove this workaround diff --git a/src/core/api/qwebengineurlscheme.cpp b/src/core/api/qwebengineurlscheme.cpp new file mode 100644 index 000000000..24bcae195 --- /dev/null +++ b/src/core/api/qwebengineurlscheme.cpp @@ -0,0 +1,375 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwebengineurlscheme.h" + +#include <url/url_util_qt.h> + +QT_BEGIN_NAMESPACE + +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::PathSyntax, url::SCHEME_WITHOUT_AUTHORITY) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::HostSyntax, url::SCHEME_WITH_HOST) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::HostAndPortSyntax, url::SCHEME_WITH_HOST_AND_PORT) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::HostPortAndUserInformationSyntax, + url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION) + +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::PortUnspecified, url::PORT_UNSPECIFIED); + +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::Secure, url::CustomScheme::Secure) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::Local, url::CustomScheme::Local) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::LocalAccessAllowed, url::CustomScheme::LocalAccessAllowed) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::NoAccessAllowed, url::CustomScheme::NoAccessAllowed) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::ServiceWorkersAllowed, url::CustomScheme::ServiceWorkersAllowed) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::ViewSourceAllowed, url::CustomScheme::ViewSourceAllowed) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::ContentSecurityPolicyIgnored, url::CustomScheme::ContentSecurityPolicyIgnored) + +class QWebEngineUrlSchemePrivate : public QSharedData + , public url::CustomScheme +{ +public: + QWebEngineUrlSchemePrivate() {} + QWebEngineUrlSchemePrivate(const url::CustomScheme &cs) : url::CustomScheme(cs) {} + static QSharedDataPointer<QWebEngineUrlSchemePrivate> defaultConstructed() + { + static QSharedDataPointer<QWebEngineUrlSchemePrivate> instance(new QWebEngineUrlSchemePrivate); + return instance; + } +}; + +/*! + \class QWebEngineUrlScheme + \inmodule QtWebEngineCore + \since 5.12 + \brief The QWebEngineUrlScheme class configures a custom URL scheme. + + A web engine URL scheme describes a URL scheme from the web engine's + perspective, specifying how URLs of this scheme should be parsed, and which + security restrictions should be placed on resources originating from such + URLs. + + Custom URL schemes must be configured early at application startup, before + creating any Qt WebEngine classes. The configuration applies globally to all + profiles. + + \code + int main(int argc, char **argv) + { + QWebEngineUrlScheme scheme("myscheme"); + scheme.setSyntax(QWebEngineUrlScheme::HostAndPortSyntax); + scheme.setDefaultPort(2345); + scheme.setFlags(QWebEngineUrlScheme::Secure); + QWebEngineUrlScheme::addScheme(scheme); + ... + } + \endcode + + To actually make use of the custom URL scheme, a \l QWebEngineUrlSchemeHandler + must be created and registered in a profile. + + \sa QWebEngineUrlSchemeHandler +*/ + +/*! + \enum QWebEngineUrlScheme::Syntax + + This enum type lists types of URL syntax. + + To apply the same-origin policy to a custom URL scheme, WebEngine must be able + to compute the origin (host and port combination) of a URL. The \c {Host...} + options indicate that the URL scheme conforms to the standard URL syntax (like + \c http) and automatically enable the same-origin policy. The \c {PathSyntax} + option indicates that the URL scheme uses a non-standard syntax and that the + same-origin policy cannot be applied. + + \value HostPortAndUserInformationSyntax + The authority component of a URL of this type has all of the standard + elements: host, port, user name, and password. A URL without a port will use + the \l defaultPort (which \e must not be \l PortUnspecified). + + \value HostAndPortSyntax + The authority component of a URL of this type has only the host and port + elements. A URL without a port will use the \l defaultPort (which \e must not + be \l PortUnspecified). + + \value HostSyntax + The authority component of a URL of this type has only the host part and no + port. The \l defaultPort \e must be set to \l PortUnspecified. + + \value PathSyntax + A URL of this type has no authority component at all. Everything after scheme + name and separator character (:) will be preserved as is without validation + or canonicalization. All URLs of such a scheme will be considered as having + the same origin (unless the \c NoAccessAllowed flag is used). +*/ + +/*! + \enum QWebEngineUrlScheme::SpecialPort + + This enum type defines special values for \l defaultPort. + + \value PortUnspecified + Indicates that the URL scheme does not have a port element. +*/ + +/*! + \enum QWebEngineUrlScheme::Flag + + This enum type specifies security options that should apply to a URL scheme. + + \value Secure + Indicates that the URL scheme is + \l{https://www.w3.org/TR/powerful-features/#is-origin-trustworthy}{potentially + trustworthy}. This flag should only be applied to URL schemes which ensure + data authenticity, confidentiality, and integrity, either through encryption + or other means. Examples of secure builtin schemes include \c https + (authenticated and encrypted) and \c qrc (local resources only), whereas \c + http is an example of an insecure scheme. + + \value Local + Indicates that the URL scheme provides access to local resources. The purpose + of this flag is to prevent network content from accessing local resources. + Only schemes with the \c LocalAccessAllowed flag may load resources from a + scheme with the \c Local flag. The only builtin schemes with this flag are \c + file and \c qrc. + + \value LocalAccessAllowed + Indicates that content from this scheme should be allowed to load resources + from schemes with the \c Local flag. + + \value NoAccessAllowed + Indicates that all content from this scheme should be forced to have unique + opaque origins: no two resources will have the same origin. + + \value ServiceWorkersAllowed + Indicates that the Service Workers API should be enabled. + + \value ViewSourceAllowed + Indicates that the View Source feature should be enabled. + + \value ContentSecurityPolicyIgnored + Indicates that accesses to this scheme should bypass all + Content-Security-Policy checks. +*/ + +QWebEngineUrlScheme::QWebEngineUrlScheme(QWebEngineUrlSchemePrivate *d) + : d(d) +{ +} + +/*! + Constructs a web engine URL scheme with default values. +*/ +QWebEngineUrlScheme::QWebEngineUrlScheme() + : QWebEngineUrlScheme(QWebEngineUrlSchemePrivate::defaultConstructed()) +{ +} + +/*! + Constructs a web engine URL scheme with given \a name. +*/ +QWebEngineUrlScheme::QWebEngineUrlScheme(const QByteArray &name) + : QWebEngineUrlScheme() +{ + setName(name); +} + +/*! + Copies \a that. +*/ +QWebEngineUrlScheme::QWebEngineUrlScheme(const QWebEngineUrlScheme &that) = default; + +/*! + Copies \a that. +*/ +QWebEngineUrlScheme &QWebEngineUrlScheme::operator=(const QWebEngineUrlScheme &that) = default; + +/*! + Moves \a that. +*/ +QWebEngineUrlScheme::QWebEngineUrlScheme(QWebEngineUrlScheme &&that) = default; + +/*! + Moves \a that. +*/ +QWebEngineUrlScheme &QWebEngineUrlScheme::operator=(QWebEngineUrlScheme &&that) = default; + +/*! + Destructs this object. +*/ +QWebEngineUrlScheme::~QWebEngineUrlScheme() = default; + +/*! + Returns \c true if this and \a that object are equal. +*/ +bool QWebEngineUrlScheme::operator==(const QWebEngineUrlScheme &that) +{ + return (d == that.d) + || (d->name == that.d->name + && d->type == that.d->type + && d->default_port == that.d->default_port + && d->flags == that.d->flags); +} + +/*! + \fn bool QWebEngineUrlScheme::operator!=(const QWebEngineUrlScheme &that) + + Returns \c true if this and \a that object are not equal. +*/ + +/*! + Returns the name of this URL scheme. + + The default value is an empty string. + + \sa setName() +*/ +QByteArray QWebEngineUrlScheme::name() const +{ + return QByteArray::fromStdString(d->name); +} + +/*! + Sets the name of this URL scheme to \a newValue. + + \note The name is automatically converted to lower case. + + \sa name() +*/ +void QWebEngineUrlScheme::setName(const QByteArray &newValue) +{ + d->name = newValue.toLower().toStdString(); +} + +/*! + Returns the syntax type of this URL scheme. + + The default value is \c PathSyntax. + + \sa Syntax, setSyntax() +*/ +QWebEngineUrlScheme::Syntax QWebEngineUrlScheme::syntax() const +{ + return static_cast<Syntax>(d->type); +} + +/*! + Sets the syntax type of this URL scheme to \a newValue. + + \sa Syntax, syntax() +*/ +void QWebEngineUrlScheme::setSyntax(Syntax newValue) +{ + d->type = static_cast<url::SchemeType>(newValue); +} + +/*! + Returns the default port of this URL scheme. + + The default value is \c PortUnspecified. + + \sa setDefaultPort() +*/ +int QWebEngineUrlScheme::defaultPort() const +{ + return d->default_port; +} + +/*! + Sets the default port of this URL scheme to \a newValue. + + \sa defaultPort() +*/ +void QWebEngineUrlScheme::setDefaultPort(int newValue) +{ + d->default_port = newValue; +} + +/*! + Returns the flags for this URL scheme. + + The default value is an empty set of flags. + + \sa Flags, setFlags() +*/ +QWebEngineUrlScheme::Flags QWebEngineUrlScheme::flags() const +{ + return Flags(d->flags); +} + +/*! + Sets the flags for this URL scheme to \a newValue. + + \sa Flags, flags() +*/ +void QWebEngineUrlScheme::setFlags(Flags newValue) +{ + d->flags = newValue; +} + +/*! + Registers \a scheme with the web engine's URL parser and security model. + + It is recommended that all custom URL schemes are first registered with this + function at application startup, even if the default options are to be used. + + \warning This function must be called early at application startup, before + creating any WebEngine classes. Late calls will be ignored. + + \sa findScheme() +*/ +void QWebEngineUrlScheme::addScheme(const QWebEngineUrlScheme &scheme) +{ + url::CustomScheme::AddScheme(*scheme.d); +} + +/*! + Returns the web engine URL scheme with the given \a name or the + default-constructed scheme. + + \sa addScheme() +*/ +QWebEngineUrlScheme QWebEngineUrlScheme::findScheme(const QByteArray &name) +{ + base::StringPiece namePiece{name.data(), static_cast<size_t>(name.size())}; + if (const url::CustomScheme *cs = url::CustomScheme::FindScheme(namePiece)) + return QWebEngineUrlScheme(new QWebEngineUrlSchemePrivate(*cs)); + return QWebEngineUrlScheme(); +} + +QT_END_NAMESPACE diff --git a/src/core/api/qwebengineurlscheme.h b/src/core/api/qwebengineurlscheme.h new file mode 100644 index 000000000..ee5bf3c3c --- /dev/null +++ b/src/core/api/qwebengineurlscheme.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWEBENGINEURLSCHEME_H +#define QWEBENGINEURLSCHEME_H + +#include <QtWebEngineCore/qtwebenginecoreglobal.h> + +#include <QtCore/qbytearray.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_NAMESPACE + +class QWebEngineUrlSchemePrivate; + +class QWEBENGINECORE_EXPORT QWebEngineUrlScheme { +public: + enum Syntax { + HostPortAndUserInformationSyntax, + HostAndPortSyntax, + HostSyntax, + PathSyntax, + }; + + enum SpecialPort { + PortUnspecified = -1 + }; + + enum Flag { + Secure = 0x1, + Local = 0x2, + LocalAccessAllowed = 0x4, + NoAccessAllowed = 0x8, + ServiceWorkersAllowed = 0x10, + ViewSourceAllowed = 0x20, + ContentSecurityPolicyIgnored = 0x40, + }; + Q_DECLARE_FLAGS(Flags, Flag) + + QWebEngineUrlScheme(); + QWebEngineUrlScheme(const QByteArray &name); + + QWebEngineUrlScheme(const QWebEngineUrlScheme &that); + QWebEngineUrlScheme &operator=(const QWebEngineUrlScheme &that); + + QWebEngineUrlScheme(QWebEngineUrlScheme &&that); + QWebEngineUrlScheme &operator=(QWebEngineUrlScheme &&that); + + ~QWebEngineUrlScheme(); + + bool operator==(const QWebEngineUrlScheme &that); + bool operator!=(const QWebEngineUrlScheme &that) { return !(*this == that); } + + QByteArray name() const; + void setName(const QByteArray &newValue); + + Syntax syntax() const; + void setSyntax(Syntax newValue); + + int defaultPort() const; + void setDefaultPort(int newValue); + + Flags flags() const; + void setFlags(Flags newValue); + + static void addScheme(const QWebEngineUrlScheme &scheme); + static QWebEngineUrlScheme findScheme(const QByteArray &name); + +private: + QWebEngineUrlScheme(QWebEngineUrlSchemePrivate *d); + QSharedDataPointer<QWebEngineUrlSchemePrivate> d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QWebEngineUrlScheme::Flags) + +QT_END_NAMESPACE + +#endif // !QWEBENGINEURLSCHEME_H diff --git a/src/core/content_browser_client_qt.cpp b/src/core/content_browser_client_qt.cpp index 2d4ac3552..59d9a4a84 100644 --- a/src/core/content_browser_client_qt.cpp +++ b/src/core/content_browser_client_qt.cpp @@ -86,6 +86,7 @@ #include "ui/gl/gl_implementation.h" #include "ui/gl/gl_share_group.h" #include "ui/gl/gpu_timing.h" +#include "url/url_util_qt.h" #include "service/service_qt.h" #include "qtwebengine/grit/qt_webengine_resources.h" @@ -575,6 +576,8 @@ void ContentBrowserClientQt::AppendExtraCommandLineSwitches(base::CommandLine* c { Q_UNUSED(child_process_id); + url::CustomScheme::SaveSchemes(command_line); + std::string processType = command_line->GetSwitchValueASCII(switches::kProcessType); if (processType == switches::kZygoteProcess) command_line->AppendSwitchASCII(switches::kLang, GetApplicationLocale()); @@ -585,12 +588,6 @@ void ContentBrowserClientQt::GetAdditionalWebUISchemes(std::vector<std::string>* additional_schemes->push_back(content::kChromeDevToolsScheme); } -void ContentBrowserClientQt::GetAdditionalViewSourceSchemes(std::vector<std::string>* additional_schemes) -{ - GetAdditionalWebUISchemes(additional_schemes); - additional_schemes->push_back(kQrcSchemeQt); -} - #if defined(Q_OS_LINUX) void ContentBrowserClientQt::GetAdditionalMappedFilesForChildProcess(const base::CommandLine& command_line, int child_process_id, content::PosixFileDescriptorInfo* mappings) { diff --git a/src/core/content_browser_client_qt.h b/src/core/content_browser_client_qt.h index a7f4313ea..842cf513d 100644 --- a/src/core/content_browser_client_qt.h +++ b/src/core/content_browser_client_qt.h @@ -112,7 +112,6 @@ public: std::string GetApplicationLocale() override; std::string GetAcceptLangs(content::BrowserContext* context) override; void AppendExtraCommandLineSwitches(base::CommandLine* command_line, int child_process_id) override; - void GetAdditionalViewSourceSchemes(std::vector<std::string>* additional_schemes) override; void GetAdditionalWebUISchemes(std::vector<std::string>* additional_schemes) override; void BindInterfaceRequestFromFrame(content::RenderFrameHost* render_frame_host, diff --git a/src/core/content_client_qt.cpp b/src/core/content_client_qt.cpp index 20d9a51fb..fe0ea6a0d 100644 --- a/src/core/content_client_qt.cpp +++ b/src/core/content_client_qt.cpp @@ -393,9 +393,4 @@ std::string ContentClientQt::GetProduct() const return productName.toStdString(); } -void ContentClientQt::AddAdditionalSchemes(Schemes* schemes) -{ - schemes->secure_schemes.push_back(kQrcSchemeQt); -} - } // namespace QtWebEngineCore diff --git a/src/core/content_client_qt.h b/src/core/content_client_qt.h index 35aa21968..fbafa6a4b 100644 --- a/src/core/content_client_qt.h +++ b/src/core/content_client_qt.h @@ -54,7 +54,6 @@ public: #if QT_CONFIG(webengine_pepper_plugins) void AddPepperPlugins(std::vector<content::PepperPluginInfo>* plugins) override; #endif - void AddAdditionalSchemes(Schemes* schemes) override; void AddContentDecryptionModules(std::vector<content::CdmInfo> *cdms, std::vector<media::CdmHostFilePath> *cdm_host_file_paths) override; diff --git a/src/core/content_main_delegate_qt.cpp b/src/core/content_main_delegate_qt.cpp index 666fa848f..3989170a0 100644 --- a/src/core/content_main_delegate_qt.cpp +++ b/src/core/content_main_delegate_qt.cpp @@ -53,6 +53,7 @@ #include <ui/base/webui/jstemplate_builder.h> #include "net/grit/net_resources.h" #include "net/base/net_module.h" +#include "url/url_util_qt.h" #include "content_client_qt.h" #include "renderer/content_renderer_client_qt.h" @@ -214,6 +215,9 @@ bool ContentMainDelegateQt::BasicStartupComplete(int *exit_code) SafeOverridePath(base::DIR_APP_DICTIONARIES, WebEngineLibraryInfo::getPath(base::DIR_APP_DICTIONARIES)); #endif SetContentClient(new ContentClientQt); + + url::CustomScheme::LoadSchemes(base::CommandLine::ForCurrentProcess()); + return false; } diff --git a/src/core/web_engine_context.cpp b/src/core/web_engine_context.cpp index 23302e8d2..c691e0c6e 100644 --- a/src/core/web_engine_context.cpp +++ b/src/core/web_engine_context.cpp @@ -83,6 +83,7 @@ #include "content/public/app/sandbox_helper_win.h" #endif // OS_WIN +#include "api/qwebengineurlscheme.h" #include "profile_adapter.h" #include "content_browser_client_qt.h" #include "content_client_qt.h" @@ -322,6 +323,12 @@ WebEngineContext::WebEngineContext() qputenv("force_s3tc_enable", "true"); #endif + QWebEngineUrlScheme qrcScheme(QByteArrayLiteral("qrc")); + qrcScheme.setFlags(QWebEngineUrlScheme::Secure + | QWebEngineUrlScheme::LocalAccessAllowed + | QWebEngineUrlScheme::ViewSourceAllowed); + QWebEngineUrlScheme::addScheme(qrcScheme); + // Allow us to inject javascript like any webview toolkit. content::RenderFrameHost::AllowInjectingJavaScriptForAndroidWebView(); diff --git a/src/webengine/api/qquickwebengineprofile.cpp b/src/webengine/api/qquickwebengineprofile.cpp index d11214716..48e5175ca 100644 --- a/src/webengine/api/qquickwebengineprofile.cpp +++ b/src/webengine/api/qquickwebengineprofile.cpp @@ -836,22 +836,26 @@ static bool checkInternalScheme(const QByteArray &scheme) /*! Registers a handler \a handler for custom URL scheme \a scheme in the profile. + + It is recommended to first register the scheme with \l + QWebEngineUrlScheme::addScheme at application startup. */ void QQuickWebEngineProfile::installUrlSchemeHandler(const QByteArray &scheme, QWebEngineUrlSchemeHandler *handler) { Q_D(QQuickWebEngineProfile); Q_ASSERT(handler); - if (checkInternalScheme(scheme)) { + QByteArray canonicalScheme = scheme.toLower(); + if (checkInternalScheme(canonicalScheme)) { qWarning("Cannot install a URL scheme handler overriding internal scheme: %s", scheme.constData()); return; } - if (d->profileAdapter()->customUrlSchemeHandlers().contains(scheme)) { - if (d->profileAdapter()->customUrlSchemeHandlers().value(scheme) != handler) + if (d->profileAdapter()->customUrlSchemeHandlers().contains(canonicalScheme)) { + if (d->profileAdapter()->customUrlSchemeHandlers().value(canonicalScheme) != handler) qWarning("URL scheme handler already installed for the scheme: %s", scheme.constData()); return; } - d->profileAdapter()->addCustomUrlSchemeHandler(scheme, handler); + d->profileAdapter()->addCustomUrlSchemeHandler(canonicalScheme, handler); connect(handler, SIGNAL(_q_destroyedUrlSchemeHandler(QWebEngineUrlSchemeHandler*)), this, SLOT(destroyedUrlSchemeHandler(QWebEngineUrlSchemeHandler*))); } diff --git a/src/webengine/doc/src/qtwebengine-features.qdoc b/src/webengine/doc/src/qtwebengine-features.qdoc index d900e551b..41d0581e5 100644 --- a/src/webengine/doc/src/qtwebengine-features.qdoc +++ b/src/webengine/doc/src/qtwebengine-features.qdoc @@ -111,6 +111,19 @@ For more information, see \l {Qt WebEngine Debugging and Profiling}. + \section1 Custom Schemes + + Qt WebEngine makes it possible for the application to define its own custom + URL schemes with specialized security policies and transport mechanisms. + + Custom schemes can be used to implement alternative network protocols with + all the usual web security policies, privileged internal schemes for + displaying user interface compoments or debugging information, sandboxed + schemes with extra restrictions, and so on. + + For more information, see \l QWebEngineUrlScheme and \l + QWebEngineUrlSchemeHandler. + \section1 Drag and Drop Qt WebEngine supports HTML5 drag and drop. diff --git a/src/webenginewidgets/api/qwebengineprofile.cpp b/src/webenginewidgets/api/qwebengineprofile.cpp index 4523a425a..6969000a8 100644 --- a/src/webenginewidgets/api/qwebengineprofile.cpp +++ b/src/webenginewidgets/api/qwebengineprofile.cpp @@ -680,22 +680,26 @@ static bool checkInternalScheme(const QByteArray &scheme) \since 5.6 Registers a handler \a handler for custom URL scheme \a scheme in the profile. + + It is recommended to first register the scheme with \l + QWebEngineUrlScheme::addScheme at application startup. */ void QWebEngineProfile::installUrlSchemeHandler(const QByteArray &scheme, QWebEngineUrlSchemeHandler *handler) { Q_D(QWebEngineProfile); Q_ASSERT(handler); - if (checkInternalScheme(scheme)) { + QByteArray canonicalScheme = scheme.toLower(); + if (checkInternalScheme(canonicalScheme)) { qWarning("Cannot install a URL scheme handler overriding internal scheme: %s", scheme.constData()); return; } - if (d->profileAdapter()->customUrlSchemeHandlers().contains(scheme)) { - if (d->profileAdapter()->customUrlSchemeHandlers().value(scheme) != handler) + if (d->profileAdapter()->customUrlSchemeHandlers().contains(canonicalScheme)) { + if (d->profileAdapter()->customUrlSchemeHandlers().value(canonicalScheme) != handler) qWarning("URL scheme handler already installed for the scheme: %s", scheme.constData()); return; } - d->profileAdapter()->addCustomUrlSchemeHandler(scheme, handler); + d->profileAdapter()->addCustomUrlSchemeHandler(canonicalScheme, handler); connect(handler, SIGNAL(_q_destroyedUrlSchemeHandler(QWebEngineUrlSchemeHandler*)), this, SLOT(destroyedUrlSchemeHandler(QWebEngineUrlSchemeHandler*))); } diff --git a/tests/auto/widgets/origins/origins.pro b/tests/auto/widgets/origins/origins.pro index 566666e0a..b569547d0 100644 --- a/tests/auto/widgets/origins/origins.pro +++ b/tests/auto/widgets/origins/origins.pro @@ -1,2 +1,3 @@ include(../tests.pri) CONFIG += c++14 +QT += websockets diff --git a/tests/auto/widgets/origins/resources/mixed.html b/tests/auto/widgets/origins/resources/mixedSchemes.html index c73e9ecdc..c73e9ecdc 100644 --- a/tests/auto/widgets/origins/resources/mixed.html +++ b/tests/auto/widgets/origins/resources/mixedSchemes.html diff --git a/tests/auto/widgets/origins/resources/mixedSchemesWithCsp.html b/tests/auto/widgets/origins/resources/mixedSchemesWithCsp.html new file mode 100644 index 000000000..ad7cbeeb7 --- /dev/null +++ b/tests/auto/widgets/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/widgets/origins/resources/mixed_frame.html b/tests/auto/widgets/origins/resources/mixedSchemes_frame.html index 00c20ba37..00c20ba37 100644 --- a/tests/auto/widgets/origins/resources/mixed_frame.html +++ b/tests/auto/widgets/origins/resources/mixedSchemes_frame.html diff --git a/tests/auto/widgets/origins/resources/serviceWorker.html b/tests/auto/widgets/origins/resources/serviceWorker.html index b2bdc8c60..27890c98f 100644 --- a/tests/auto/widgets/origins/resources/serviceWorker.html +++ b/tests/auto/widgets/origins/resources/serviceWorker.html @@ -5,9 +5,13 @@ <script> var done = false; var error; - navigator.serviceWorker.register("serviceWorker.js") - .then((r) => { done = true; }) - .catch((e) => { done = true; error = e.message; }); + 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> diff --git a/tests/auto/widgets/origins/resources/viewSource.html b/tests/auto/widgets/origins/resources/viewSource.html new file mode 100644 index 000000000..977074c74 --- /dev/null +++ b/tests/auto/widgets/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/widgets/origins/resources/websocket.html b/tests/auto/widgets/origins/resources/websocket.html index 949596d1c..31db66571 100644 --- a/tests/auto/widgets/origins/resources/websocket.html +++ b/tests/auto/widgets/origins/resources/websocket.html @@ -2,10 +2,21 @@ <html> <head> <title>WebSocket</title> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> <script> - var err; - const ws = new WebSocket("ws://example.invalid"); - ws.addEventListener("close", () => err = event.code); + 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> diff --git a/tests/auto/widgets/origins/tst_origins.cpp b/tests/auto/widgets/origins/tst_origins.cpp index 083975d71..19353e8bf 100644 --- a/tests/auto/widgets/origins/tst_origins.cpp +++ b/tests/auto/widgets/origins/tst_origins.cpp @@ -31,15 +31,98 @@ #include <QtCore/qfile.h> #include <QtTest/QtTest> #include <QtWebEngineCore/qwebengineurlrequestjob.h> +#include <QtWebEngineCore/qwebengineurlscheme.h> #include <QtWebEngineCore/qwebengineurlschemehandler.h> #include <QtWebEngineWidgets/qwebenginepage.h> #include <QtWebEngineWidgets/qwebengineprofile.h> #include <QtWebEngineWidgets/qwebenginesettings.h> +#include <QtWebChannel/qwebchannel.h> +#include <QtWebSockets/qwebsocket.h> +#include <QtWebSockets/qwebsocketserver.h> +#include <QtWidgets/qaction.h> #define QSL QStringLiteral #define QBAL QByteArrayLiteral #define THIS_DIR TESTS_SOURCE_DIR "origins/" +void registerSchemes() +{ + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax")); + QWebEngineUrlScheme::addScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-Secure")); + scheme.setFlags(QWebEngineUrlScheme::Secure); + QWebEngineUrlScheme::addScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-Secure-ServiceWorkersAllowed")); + scheme.setFlags(QWebEngineUrlScheme::Secure | QWebEngineUrlScheme::ServiceWorkersAllowed); + QWebEngineUrlScheme::addScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-Local")); + scheme.setFlags(QWebEngineUrlScheme::Local); + QWebEngineUrlScheme::addScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-LocalAccessAllowed")); + scheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed); + QWebEngineUrlScheme::addScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-NoAccessAllowed")); + scheme.setFlags(QWebEngineUrlScheme::NoAccessAllowed); + QWebEngineUrlScheme::addScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-ServiceWorkersAllowed")); + scheme.setFlags(QWebEngineUrlScheme::ServiceWorkersAllowed); + QWebEngineUrlScheme::addScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("PathSyntax-ViewSourceAllowed")); + scheme.setFlags(QWebEngineUrlScheme::ViewSourceAllowed); + QWebEngineUrlScheme::addScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("HostSyntax")); + scheme.setSyntax(QWebEngineUrlScheme::HostSyntax); + QWebEngineUrlScheme::addScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("HostSyntax-ContentSecurityPolicyIgnored")); + scheme.setSyntax(QWebEngineUrlScheme::HostSyntax); + scheme.setFlags(QWebEngineUrlScheme::ContentSecurityPolicyIgnored); + QWebEngineUrlScheme::addScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("HostAndPortSyntax")); + scheme.setSyntax(QWebEngineUrlScheme::HostAndPortSyntax); + scheme.setDefaultPort(42); + QWebEngineUrlScheme::addScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("HostPortAndUserInformationSyntax")); + scheme.setSyntax(QWebEngineUrlScheme::HostPortAndUserInformationSyntax); + scheme.setDefaultPort(42); + QWebEngineUrlScheme::addScheme(scheme); + } +} +Q_CONSTRUCTOR_FUNCTION(registerSchemes) + class TstUrlSchemeHandler final : public QWebEngineUrlSchemeHandler { Q_OBJECT @@ -47,6 +130,19 @@ public: TstUrlSchemeHandler(QWebEngineProfile *profile) { profile->installUrlSchemeHandler(QBAL("tst"), this); + + profile->installUrlSchemeHandler(QBAL("PathSyntax"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-Secure"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-Secure-ServiceWorkersAllowed"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-Local"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-LocalAccessAllowed"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-NoAccessAllowed"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-ServiceWorkersAllowed"), this); + profile->installUrlSchemeHandler(QBAL("PathSyntax-ViewSourceAllowed"), this); + profile->installUrlSchemeHandler(QBAL("HostSyntax"), this); + profile->installUrlSchemeHandler(QBAL("HostSyntax-ContentSecurityPolicyIgnored"), this); + profile->installUrlSchemeHandler(QBAL("HostAndPortSyntax"), this); + profile->installUrlSchemeHandler(QBAL("HostPortAndUserInformationSyntax"), this); } private: @@ -59,7 +155,10 @@ private: job->fail(QWebEngineUrlRequestJob::RequestFailed); return; } - job->reply(QBAL("text/html"), file); + QByteArray mimeType = QBAL("text/html"); + if (pathSuffix.endsWith(QSL(".js"))) + mimeType = QBAL("application/javascript"); + job->reply(mimeType, file); } }; @@ -75,10 +174,12 @@ private Q_SLOTS: void subdirWithAccess(); void subdirWithoutAccess(); void mixedSchemes(); + void mixedSchemesWithCsp(); void webSocket(); void dedicatedWorker(); void sharedWorker(); void serviceWorker(); + void viewSource(); private: bool load(const QUrl &url) @@ -136,17 +237,42 @@ void tst_Origins::jsUrlCanon() QCOMPARE(eval(QSL("new URL(\"file:///foo/bar\").href")), QVariant(QSL("file:///foo/bar"))); #endif - // The qrc scheme is a 'dumb' URL, having only a path and nothing else. + // The qrc scheme is a PathSyntax scheme, having only a path and nothing else. QCOMPARE(eval(QSL("new URL(\"qrc:foo/bar\").href")), QVariant(QSL("qrc:foo/bar"))); QCOMPARE(eval(QSL("new URL(\"qrc:/foo/bar\").href")), QVariant(QSL("qrc:/foo/bar"))); QCOMPARE(eval(QSL("new URL(\"qrc://foo/bar\").href")), QVariant(QSL("qrc://foo/bar"))); QCOMPARE(eval(QSL("new URL(\"qrc:///foo/bar\").href")), QVariant(QSL("qrc:///foo/bar"))); - // Same for custom schemes. + // Same for unregistered schemes. QCOMPARE(eval(QSL("new URL(\"tst:foo/bar\").href")), QVariant(QSL("tst:foo/bar"))); QCOMPARE(eval(QSL("new URL(\"tst:/foo/bar\").href")), QVariant(QSL("tst:/foo/bar"))); QCOMPARE(eval(QSL("new URL(\"tst://foo/bar\").href")), QVariant(QSL("tst://foo/bar"))); QCOMPARE(eval(QSL("new URL(\"tst:///foo/bar\").href")), QVariant(QSL("tst:///foo/bar"))); + + // A HostSyntax scheme is like http without the port & user information. + QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo/bar\").href")), QVariant(QSL("hostsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo:42/bar\").href")), QVariant(QSL("hostsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostSyntax:a:b@foo/bar\").href")), QVariant(QSL("hostsyntax://foo/bar"))); + + // A HostAndPortSyntax scheme is like http without the user information. + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo/bar\").href")), + QVariant(QSL("hostandportsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:41/bar\").href")), + QVariant(QSL("hostandportsyntax://foo:41/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:42/bar\").href")), + QVariant(QSL("hostandportsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:a:b@foo/bar\").href")), + QVariant(QSL("hostandportsyntax://foo/bar"))); + + // A HostPortAndUserInformationSyntax scheme is exactly like http. + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo/bar\").href")), + QVariant(QSL("hostportanduserinformationsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:41/bar\").href")), + QVariant(QSL("hostportanduserinformationsyntax://foo:41/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:42/bar\").href")), + QVariant(QSL("hostportanduserinformationsyntax://foo/bar"))); + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:a:b@foo/bar\").href")), + QVariant(QSL("hostportanduserinformationsyntax://a:b@foo/bar"))); } // Test origin serialization in Blink, implemented by blink::KURL and @@ -169,9 +295,29 @@ void tst_Origins::jsUrlOrigin() QCOMPARE(eval(QSL("new URL(\"qrc:/crysis.css\").origin")), QVariant(QSL("qrc://"))); QCOMPARE(eval(QSL("new URL(\"qrc://foo.com/crysis.css\").origin")), QVariant(QSL("qrc://"))); - // Same with custom schemes. + // Same with unregistered schemes. QCOMPARE(eval(QSL("new URL(\"tst:/banana\").origin")), QVariant(QSL("tst://"))); QCOMPARE(eval(QSL("new URL(\"tst://foo.com/banana\").origin")), QVariant(QSL("tst://"))); + + // The non-PathSyntax schemes should have hosts and potentially ports. + QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo:41/bar\").origin")), + QVariant(QSL("hostsyntax://foo"))); + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:41/bar\").origin")), + QVariant(QSL("hostandportsyntax://foo:41"))); + QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:42/bar\").origin")), + QVariant(QSL("hostandportsyntax://foo"))); + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:41/bar\").origin")), + QVariant(QSL("hostportanduserinformationsyntax://foo:41"))); + QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:42/bar\").origin")), + QVariant(QSL("hostportanduserinformationsyntax://foo"))); + + // A PathSyntax scheme should have a 'universal' origin. + QCOMPARE(eval(QSL("new URL(\"PathSyntax:foo\").origin")), + QVariant(QSL("pathsyntax://"))); + + // The NoAccessAllowed flag forces opaque origins. + QCOMPARE(eval(QSL("new URL(\"PathSyntax-NoAccessAllowed:foo\").origin")), + QVariant(QSL("null"))); } class ScopedAttribute { @@ -246,53 +392,155 @@ void tst_Origins::subdirWithoutAccess() // For file and qrc schemes, the iframe should load but it should not be // possible for scripts in different frames to interact. // -// Additionally for custom schemes access to local content is forbidden, so it -// should not be possible to load an iframe over the file: scheme. +// Additionally for unregistered custom schemes and custom schemes without +// LocalAccessAllowed it should not be possible to load an iframe over the +// file: scheme. void tst_Origins::mixedSchemes() { - QVERIFY(load(QSL("file:" THIS_DIR "resources/mixed.html"))); - eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixed_frame.html')")); + QVERIFY(load(QSL("file:" THIS_DIR "resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(load(QSL("qrc:/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(load(QSL("tst:/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); + eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + + QVERIFY(load(QSL("PathSyntax:/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - eval(QSL("setIFrameUrl('qrc:/resources/mixed_frame.html')")); + eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); + eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('tst:/resources/mixed_frame.html')")); + eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QVERIFY(load(QSL("qrc:/resources/mixed.html"))); - eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixed_frame.html')")); + QVERIFY(load(QSL("PathSyntax-LocalAccessAllowed:/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('qrc:/resources/mixed_frame.html')")); + eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - eval(QSL("setIFrameUrl('tst:/resources/mixed_frame.html')")); + eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QVERIFY(load(QSL("tst:/resources/mixed.html"))); - eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixed_frame.html')")); + QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); - eval(QSL("setIFrameUrl('qrc:/resources/mixed_frame.html')")); + eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('tst:/resources/mixed_frame.html')")); + + QVERIFY(load(QSL("HostSyntax://a/resources/mixedSchemes.html"))); + eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); } +// Like mixedSchemes but adds a Content-Security-Policy: frame-src 'none' header. +void tst_Origins::mixedSchemesWithCsp() +{ + QVERIFY(load(QSL("HostSyntax://a/resources/mixedSchemesWithCsp.html"))); + eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + + QVERIFY(load(QSL("HostSyntax-ContentSecurityPolicyIgnored://a/resources/mixedSchemesWithCsp.html"))); + eval(QSL("setIFrameUrl('HostSyntax-ContentSecurityPolicyIgnored://a/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + eval(QSL("setIFrameUrl('HostSyntax-ContentSecurityPolicyIgnored://b/resources/mixedSchemes_frame.html')")); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); +} + +class EchoServer : public QObject { + Q_OBJECT + Q_PROPERTY(QUrl url READ url NOTIFY urlChanged) +public: + EchoServer() : webSocketServer(QSL("EchoServer"), QWebSocketServer::NonSecureMode) + { + connect(&webSocketServer, &QWebSocketServer::newConnection, this, &EchoServer::onNewConnection); + } + + bool listen() + { + if (webSocketServer.listen(QHostAddress::Any)) { + Q_EMIT urlChanged(); + return true; + } + return false; + } + + QUrl url() const + { + return webSocketServer.serverUrl(); + } + +Q_SIGNALS: + void urlChanged(); + +private: + void onNewConnection() + { + QWebSocket *socket = webSocketServer.nextPendingConnection(); + connect(socket, &QWebSocket::textMessageReceived, this, &EchoServer::onTextMessageReceived); + connect(socket, &QWebSocket::disconnected, socket, &QObject::deleteLater); + } + + void onTextMessageReceived(const QString &message) + { + QWebSocket *socket = qobject_cast<QWebSocket *>(sender()); + socket->sendTextMessage(message); + } + + QWebSocketServer webSocketServer; +}; + // Try opening a WebSocket from pages loaded over various URL schemes. void tst_Origins::webSocket() { - // 1006 indicates 'Abnormal Closure'. - // - // The example page is passing a URL with a non-existent domain to the - // WebSocket constructor, so we expect the connection to fail. This is - // enough though to trigger the origin checks. - const int expected = 1006; + const int kAbnormalClosure = 1006; + + EchoServer echoServer; + QWebChannel channel; + channel.registerObject(QSL("echoServer"), &echoServer); + m_page->setWebChannel(&channel); + QVERIFY(echoServer.listen()); QVERIFY(load(QSL("file:" THIS_DIR "resources/websocket.html"))); - QTRY_COMPARE_WITH_TIMEOUT(eval(QSL("err")), QVariant(expected), 20000); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); QVERIFY(load(QSL("qrc:/resources/websocket.html"))); - QTRY_COMPARE_WITH_TIMEOUT(eval(QSL("err")), QVariant(expected), 20000); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); + // Only registered schemes can open WebSockets. QVERIFY(load(QSL("tst:/resources/websocket.html"))); - QTRY_VERIFY(eval(QSL("err")) == QVariant(expected)); + QTRY_COMPARE(eval(QSL("result")), QVariant(kAbnormalClosure)); + + // Even an insecure registered scheme can open WebSockets. + QVERIFY(load(QSL("PathSyntax:/resources/websocket.html"))); + QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); } // Create a (Dedicated)Worker. Since dedicated workers can only be accessed from @@ -307,11 +555,22 @@ void tst_Origins::dedicatedWorker() QTRY_VERIFY(eval(QSL("done")).toBool()); QCOMPARE(eval(QSL("result")), QVariant(42)); - // FIXME(juvaldma): QTBUG-62536 + // Unregistered schemes cannot create Workers. QVERIFY(load(QSL("tst:/resources/dedicatedWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QVERIFY(eval(QSL("error")).toString() .contains(QSL("Access to dedicated workers is denied to origin 'tst://'"))); + + // Even an insecure registered scheme can create Workers. + QVERIFY(load(QSL("PathSyntax:/resources/dedicatedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); + + // But not if the NoAccessAllowed flag is set. + QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/dedicatedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("cannot be accessed from origin 'null'"))); } // Create a SharedWorker. Shared workers can be accessed from multiple pages, @@ -337,12 +596,22 @@ void tst_Origins::sharedWorker() QTRY_VERIFY(eval(QSL("done")).toBool()); QCOMPARE(eval(QSL("result")), QVariant(42)); + // Even unregistered schemes can create SharedWorkers. QVERIFY(load(QSL("tst:/resources/sharedWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QCOMPARE(eval(QSL("result")), QVariant(42)); + + QVERIFY(load(QSL("PathSyntax:/resources/sharedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); + + QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/sharedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("denied to origin 'null'"))); } -// Service workers don't work. +// Service workers have to be explicitly enabled for a scheme. void tst_Origins::serviceWorker() { QVERIFY(load(QSL("file:" THIS_DIR "resources/serviceWorker.html"))); @@ -359,6 +628,53 @@ void tst_Origins::serviceWorker() QTRY_VERIFY(eval(QSL("done")).toBool()); QVERIFY(eval(QSL("error")).toString() .contains(QSL("Only secure origins are allowed"))); + + QVERIFY(load(QSL("PathSyntax:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("Only secure origins are allowed"))); + + QVERIFY(load(QSL("PathSyntax-Secure:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("The URL protocol of the current origin ('pathsyntax-secure://') is not supported."))); + + QVERIFY(load(QSL("PathSyntax-ServiceWorkersAllowed:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("Only secure origins are allowed"))); + + QVERIFY(load(QSL("PathSyntax-Secure-ServiceWorkersAllowed:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("error")), QVariant()); + + QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("denied in this document origin"))); +} + +// Support for view-source must be enabled explicitly. +void tst_Origins::viewSource() +{ + QVERIFY(load(QSL("view-source:file:" THIS_DIR "resources/viewSource.html"))); +#ifdef Q_OS_WIN + QCOMPARE(m_page->requestedUrl(), QSL("file:///" THIS_DIR "resources/viewSource.html")); +#else + QCOMPARE(m_page->requestedUrl(), QSL("file:" THIS_DIR "resources/viewSource.html")); +#endif + + QVERIFY(load(QSL("view-source:qrc:/resources/viewSource.html"))); + QCOMPARE(m_page->requestedUrl(), QSL("qrc:/resources/viewSource.html")); + + QVERIFY(load(QSL("view-source:tst:/resources/viewSource.html"))); + QCOMPARE(m_page->requestedUrl(), QSL("about:blank")); + + QVERIFY(load(QSL("view-source:PathSyntax:/resources/viewSource.html"))); + QCOMPARE(m_page->requestedUrl(), QSL("about:blank")); + + QVERIFY(load(QSL("view-source:PathSyntax-ViewSourceAllowed:/resources/viewSource.html"))); + QCOMPARE(m_page->requestedUrl(), QSL("pathsyntax-viewsourceallowed:/resources/viewSource.html")); } QTEST_MAIN(tst_Origins) diff --git a/tests/auto/widgets/origins/tst_origins.qrc b/tests/auto/widgets/origins/tst_origins.qrc index 410cdc8a7..438fd10ee 100644 --- a/tests/auto/widgets/origins/tst_origins.qrc +++ b/tests/auto/widgets/origins/tst_origins.qrc @@ -3,8 +3,9 @@ <qresource> <file>resources/dedicatedWorker.html</file> <file>resources/dedicatedWorker.js</file> - <file>resources/mixed.html</file> - <file>resources/mixed_frame.html</file> + <file>resources/mixedSchemes.html</file> + <file>resources/mixedSchemesWithCsp.html</file> + <file>resources/mixedSchemes_frame.html</file> <file>resources/serviceWorker.html</file> <file>resources/serviceWorker.js</file> <file>resources/sharedWorker.html</file> @@ -12,6 +13,7 @@ <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> |