diff options
20 files changed, 556 insertions, 4 deletions
diff --git a/src/core/api/CMakeLists.txt b/src/core/api/CMakeLists.txt index 0ee286031..4be37d856 100644 --- a/src/core/api/CMakeLists.txt +++ b/src/core/api/CMakeLists.txt @@ -33,7 +33,8 @@ qt_internal_add_module(WebEngineCore qwebenginescriptcollection.cpp qwebenginescriptcollection.h qwebenginescriptcollection_p.h qwebenginesettings.cpp qwebenginesettings.h qwebengineurlrequestinfo.cpp qwebengineurlrequestinfo.h qwebengineurlrequestinfo_p.h - qwebengineurlrequestinterceptor.h + qwebengineurlrequestinterceptor.h qwebengineurlresponseinterceptor.h + qwebengineurlresponseinfo.cpp qwebengineurlresponseinfo.h qwebengineurlresponseinfo_p.h qwebengineurlrequestjob.cpp qwebengineurlrequestjob.h qwebengineurlscheme.cpp qwebengineurlscheme.h qwebengineurlschemehandler.cpp qwebengineurlschemehandler.h diff --git a/src/core/api/qwebenginepage.cpp b/src/core/api/qwebenginepage.cpp index db795f846..21007f852 100644 --- a/src/core/api/qwebenginepage.cpp +++ b/src/core/api/qwebenginepage.cpp @@ -1730,6 +1730,26 @@ void QWebEnginePage::setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *i d->adapter->setRequestInterceptor(interceptor); } +/*! + \since 6.6 + + Registers the response interceptor \a interceptor to intercept URL response headers. + + The page does not take ownership of the pointer. This interceptor is called + after any interceptors on the profile, and unlike profile interceptors, only + URL responses to this page are intercepted. + + To unset the response interceptor, set a \c nullptr. + + \sa QWebEngineUrlResponseInterceptor, QWebEngineProfile::setUrlResponseInterceptor() +*/ + +void QWebEnginePage::setUrlResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor) +{ + Q_D(QWebEnginePage); + d->adapter->setResponseInterceptor(interceptor); +} + void QWebEnginePage::setFeaturePermission(const QUrl &securityOrigin, QWebEnginePage::Feature feature, QWebEnginePage::PermissionPolicy policy) { Q_D(QWebEnginePage); diff --git a/src/core/api/qwebenginepage.h b/src/core/api/qwebenginepage.h index e58cb3a84..e10b78d26 100644 --- a/src/core/api/qwebenginepage.h +++ b/src/core/api/qwebenginepage.h @@ -40,6 +40,7 @@ class QWebEngineRegisterProtocolHandlerRequest; class QWebEngineScriptCollection; class QWebEngineSettings; class QWebEngineUrlRequestInterceptor; +class QWebEngineUrlResponseInterceptor; class Q_WEBENGINECORE_EXPORT QWebEnginePage : public QObject { @@ -287,6 +288,7 @@ public: QWebEnginePage *devToolsPage() const; void setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor); + void setUrlResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor); LifecycleState lifecycleState() const; void setLifecycleState(LifecycleState state); diff --git a/src/core/api/qwebengineprofile.cpp b/src/core/api/qwebengineprofile.cpp index ab873e345..3a4c899d9 100644 --- a/src/core/api/qwebengineprofile.cpp +++ b/src/core/api/qwebengineprofile.cpp @@ -62,9 +62,12 @@ using QtWebEngineCore::ProfileAdapter; The default profile can be accessed by defaultProfile(). It is a built-in profile that all web pages not specifically created with another profile belong to. - Implementing the QWebEngineUrlRequestInterceptor interface and registering the interceptor on a - profile by setUrlRequestInterceptor() enables intercepting, blocking, and modifying URL - requests (QWebEngineUrlRequestInfo) before they reach the networking stack of Chromium. + You can implement interceptor interfaces for URL requests and responses and register them + on a profile to intercept, block, or modify URL requests before they reach the networking + stack of Chromium or response headers right after they come off the networking stack of + Chromium. For requests, implement QWebEngineUrlRequestInterceptor and register it via + setUrlRequestInterceptor(). For response headers, implement QWebEngineUrlResponseInterceptor + and register it via setUrlResponseInterceptor(). A QWebEngineUrlSchemeHandler can be registered for a profile by installUrlSchemeHandler() to add support for custom URL schemes. Requests for the scheme are then issued to @@ -603,6 +606,21 @@ void QWebEngineProfile::setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor } /*! + Registers a response interceptor singleton \a interceptor to intercept URL response headers. + + The profile does not take ownership of the pointer. + + \since 6.6 + \sa QWebEngineUrlResponseInterceptor +*/ + +void QWebEngineProfile::setUrlResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor) +{ + Q_D(QWebEngineProfile); + d->profileAdapter()->setResponseInterceptor(interceptor); +} + +/*! Clears all links from the visited links database. \sa clearVisitedLinks() diff --git a/src/core/api/qwebengineprofile.h b/src/core/api/qwebengineprofile.h index 9fb4c8e74..bd051c784 100644 --- a/src/core/api/qwebengineprofile.h +++ b/src/core/api/qwebengineprofile.h @@ -24,6 +24,7 @@ class QWebEngineProfilePrivate; class QWebEngineSettings; class QWebEngineScriptCollection; class QWebEngineUrlRequestInterceptor; +class QWebEngineUrlResponseInterceptor; class QWebEngineUrlSchemeHandler; class Q_WEBENGINECORE_EXPORT QWebEngineProfile : public QObject @@ -74,6 +75,7 @@ public: QWebEngineCookieStore *cookieStore(); void setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor); + void setUrlResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor); void clearAllVisitedLinks(); void clearVisitedLinks(const QList<QUrl> &urls); diff --git a/src/core/api/qwebengineurlresponseinfo.cpp b/src/core/api/qwebengineurlresponseinfo.cpp new file mode 100644 index 000000000..8ec1024da --- /dev/null +++ b/src/core/api/qwebengineurlresponseinfo.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwebengineurlresponseinfo.h" +#include "qwebengineurlresponseinfo_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QWebEngineUrlResponseInfo + \brief A utility type for the QWebEngineUrlResponseInterceptor. + \inmodule QtWebEngineCore + \since 6.6 + + Contains information about the request that has caused the response + intercepted by a QWebEngineUrlResponseInterceptor. + + \sa QWebEngineUrlResponseInterceptor +*/ +QWebEngineUrlResponseInfo::QWebEngineUrlResponseInfo( + const QUrl &requestUrl, const QHash<QByteArray, QByteArray> &requestHeaders, + const QHash<QByteArray, QByteArray> &responseHeaders, QObject *p) + : QObject(p) + , d_ptr(new QWebEngineUrlResponseInfoPrivate(requestUrl, requestHeaders, responseHeaders)) +{ +} + +/*! + \property QWebEngineUrlResponseInfo::requestUrl + \brief Holds the URL of the URL load request. +*/ +QUrl QWebEngineUrlResponseInfo::requestUrl() const +{ + Q_D(const QWebEngineUrlResponseInfo); + return d->requestUrl; +} + +/*! + \property QWebEngineUrlResponseInfo::requestHeaders + \brief Holds the request headers of the URL load request. +*/ +QHash<QByteArray, QByteArray> QWebEngineUrlResponseInfo::requestHeaders() const +{ + Q_D(const QWebEngineUrlResponseInfo); + return d->requestHeaders; +} + +/*! + \property QWebEngineUrlResponseInfo::responseHeaders + \brief Holds the response headers of the URL load request. +*/ +QHash<QByteArray, QByteArray> QWebEngineUrlResponseInfo::responseHeaders() const +{ + Q_D(const QWebEngineUrlResponseInfo); + return d->responseHeaders; +} + +/*! + \fn void QWebEngineUrlResponseInfo::setResponseHeaders( + const QHash<QByteArray, QByteArray> &newResponseHeaders) + \brief Sets the response headers to \a newResponseHeaders. + + Sets the response headers to \a newResponseHeaders. If \a newResponseHeaders + differ from the current response headers then + QWebEngineUrlResponseInfo::isModified() will now return \c true. +*/ +void QWebEngineUrlResponseInfo::setResponseHeaders( + const QHash<QByteArray, QByteArray> &newResponseHeaders) +{ + Q_D(QWebEngineUrlResponseInfo); + if (d->responseHeaders != newResponseHeaders) { + d->responseHeaders = newResponseHeaders; + d->isModified = true; + } +} + +QT_END_NAMESPACE + +#include "moc_qwebengineurlresponseinfo.cpp" diff --git a/src/core/api/qwebengineurlresponseinfo.h b/src/core/api/qwebengineurlresponseinfo.h new file mode 100644 index 000000000..e8286b313 --- /dev/null +++ b/src/core/api/qwebengineurlresponseinfo.h @@ -0,0 +1,49 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWEBENGINEURLRESPONSEINFO_H +#define QWEBENGINEURLRESPONSEINFO_H + +#include <QtWebEngineCore/qtwebenginecoreglobal.h> + +#include <QtCore/QUrl> +#include <QtCore/QHash> +#include <QtCore/QObject> + +namespace QtWebEngineCore { +class InterceptedRequest; +} + +QT_BEGIN_NAMESPACE + +class QWebEngineUrlResponseInfoPrivate; + +class Q_WEBENGINECORE_EXPORT QWebEngineUrlResponseInfo : public QObject +{ + Q_OBJECT + Q_PROPERTY(QUrl requestUrl READ requestUrl CONSTANT FINAL) + Q_PROPERTY(QHash<QByteArray,QByteArray> requestHeaders READ requestHeaders CONSTANT FINAL) + Q_PROPERTY(QHash<QByteArray,QByteArray> responseHeaders READ responseHeaders WRITE + setResponseHeaders) + +public: + QWebEngineUrlResponseInfo(const QUrl &requestUrl, + const QHash<QByteArray, QByteArray> &requestHeaders, + const QHash<QByteArray, QByteArray> &responseHeaders, + QObject *p = nullptr); + + QUrl requestUrl() const; + QHash<QByteArray, QByteArray> requestHeaders() const; + QHash<QByteArray, QByteArray> responseHeaders() const; + + void setResponseHeaders(const QHash<QByteArray, QByteArray> &newResponseHeaders); + +private: + friend class QtWebEngineCore::InterceptedRequest; + Q_DECLARE_PRIVATE(QWebEngineUrlResponseInfo) + QWebEngineUrlResponseInfoPrivate *d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QWEBENGINEURLRESPONSEINFO_H diff --git a/src/core/api/qwebengineurlresponseinfo_p.h b/src/core/api/qwebengineurlresponseinfo_p.h new file mode 100644 index 000000000..0e500e3f0 --- /dev/null +++ b/src/core/api/qwebengineurlresponseinfo_p.h @@ -0,0 +1,46 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWEBENGINEURLRESPONSEINFO_P_H +#define QWEBENGINEURLRESPONSEINFO_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qtwebenginecoreglobal_p.h" + +#include <QtCore/QHash> +#include <QtCore/QUrl> + +QT_BEGIN_NAMESPACE + +class Q_WEBENGINECORE_PRIVATE_EXPORT QWebEngineUrlResponseInfoPrivate +{ +public: + QWebEngineUrlResponseInfoPrivate(const QUrl &requestUrl, + const QHash<QByteArray, QByteArray> &requestHeaders, + const QHash<QByteArray, QByteArray> &responseHeaders) + : requestUrl(requestUrl) + , requestHeaders(requestHeaders) + , responseHeaders(responseHeaders) + , isModified(false) + { + } + + QUrl requestUrl; + QHash<QByteArray, QByteArray> requestHeaders; + QHash<QByteArray, QByteArray> responseHeaders; + bool isModified; +}; + +QT_END_NAMESPACE + +#endif // QWEBENGINEURLRESPONSEINFO_P_H diff --git a/src/core/api/qwebengineurlresponseinterceptor.h b/src/core/api/qwebengineurlresponseinterceptor.h new file mode 100644 index 000000000..505acce57 --- /dev/null +++ b/src/core/api/qwebengineurlresponseinterceptor.h @@ -0,0 +1,27 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWEBENGINEURLRESPONSEINTERCEPTOR_H +#define QWEBENGINEURLRESPONSEINTERCEPTOR_H + +#include <QtWebEngineCore/qtwebenginecoreglobal.h> + +#include <QtCore/QObject> + +QT_BEGIN_NAMESPACE + +class QWebEngineUrlResponseInfo; + +class Q_WEBENGINECORE_EXPORT QWebEngineUrlResponseInterceptor : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(QWebEngineUrlResponseInterceptor) +public: + explicit QWebEngineUrlResponseInterceptor(QObject *p = nullptr) : QObject(p) { } + + virtual void interceptResponseHeaders(QWebEngineUrlResponseInfo &info) = 0; +}; + +QT_END_NAMESPACE + +#endif // QWEBENGINEURLRESPONSEINTERCEPTOR_H diff --git a/src/core/doc/src/qwebengineurlresponseinterceptor.qdoc b/src/core/doc/src/qwebengineurlresponseinterceptor.qdoc new file mode 100644 index 000000000..cbbd88bcf --- /dev/null +++ b/src/core/doc/src/qwebengineurlresponseinterceptor.qdoc @@ -0,0 +1,45 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/*! + \class QWebEngineUrlResponseInterceptor + \since 6.6 + \brief The QWebEngineUrlResponseInterceptor class provides an abstract base class for + URL response interception. + + \inmodule QtWebEngineCore + + Implementing the \l{QWebEngineUrlResponseInterceptor} interface and installing the + interceptor on the profile or the page enables intercepting, blocking, and modifying + URL responses right as they come off the networking stack. Currently, only the response + headers can be accessed and modified. + + You can install the interceptor on a profile via + QWebEngineProfile::setUrlResponseInterceptor() or + QQuickWebEngineProfile::setUrlResponseInterceptor() and on a page via + QWebEnginePage::setUrlResponseInterceptor(). + + \sa QWebEngineUrlResponseInterceptor::interceptResponseHeaders(), + QWebEngineProfile::setUrlResponseInterceptor(), + QQuickWebEngineProfile::setUrlResponseInterceptor(), + QWebEnginePage::setUrlResponseInterceptor() +*/ + +/*! + \fn QWebEngineUrlResponseInterceptor::QWebEngineUrlResponseInterceptor(QObject *p = nullptr) + + Creates a new response interceptor object with \a p as parent. +*/ + +/*! + \fn void QWebEngineUrlResponseInterceptor::interceptResponseHeaders( + QWebEngineUrlResponseInfo &info) + + Reimplement this virtual function to intercept URL + response headers. This method stalls the URL load until handled. + + \a info contains the request and response data. Call + info.setResponseHeaders() to modify the response headers. + + \sa QWebEngineUrlResponseInfo +*/ diff --git a/src/core/net/proxying_url_loader_factory_qt.cpp b/src/core/net/proxying_url_loader_factory_qt.cpp index 70ec61b34..aa333ae02 100644 --- a/src/core/net/proxying_url_loader_factory_qt.cpp +++ b/src/core/net/proxying_url_loader_factory_qt.cpp @@ -20,11 +20,14 @@ #include "url/url_util_qt.h" #include "api/qwebengineurlrequestinfo_p.h" +#include "api/qwebengineurlresponseinfo_p.h" #include "type_conversion.h" #include "web_contents_adapter.h" #include "web_contents_adapter_client.h" #include "web_contents_view_qt.h" +#include <QtWebEngineCore/QWebEngineUrlResponseInfo> + // originally based on aw_proxying_url_loader_factory.cc: // Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -148,6 +151,11 @@ private: content::WebContents* webContents(); QWebEngineUrlRequestInterceptor* getProfileInterceptor(); QWebEngineUrlRequestInterceptor* getPageInterceptor(); + QWebEngineUrlResponseInterceptor *getProfileResponseInterceptor(); + QWebEngineUrlResponseInterceptor *getPageResponseInterceptor(); + + void interceptResponseHeaders(QWebEngineUrlResponseInterceptor *const interceptor, + net::HttpResponseHeaders *const responseHeadersPtr); QPointer<ProfileAdapter> profile_adapter_; const int frame_tree_node_id_; @@ -252,6 +260,59 @@ QWebEngineUrlRequestInterceptor* InterceptedRequest::getPageInterceptor() return nullptr; } +QWebEngineUrlResponseInterceptor* InterceptedRequest::getProfileResponseInterceptor() +{ + return profile_adapter_ ? profile_adapter_->responseInterceptor() : nullptr; +} + +QWebEngineUrlResponseInterceptor* InterceptedRequest::getPageResponseInterceptor() +{ + if (auto wc = webContents()) { + auto view = static_cast<content::WebContentsImpl *>(wc)->GetView(); + if (WebContentsAdapterClient *client = WebContentsViewQt::from(view)->client()) + return client->webContentsAdapter()->responseInterceptor(); + } + return nullptr; +} + +void InterceptedRequest::interceptResponseHeaders( + QWebEngineUrlResponseInterceptor *const interceptor, + net::HttpResponseHeaders *const responseHeadersPtr) +{ + QHash<QByteArray, QByteArray> responseHeaders; + std::unordered_set<std::string> headersToRemove; + { + std::size_t iter = 0; + std::string name; + std::string value; + while (responseHeadersPtr->EnumerateHeaderLines(&iter, &name, &value)) { + responseHeaders.insert(QByteArray::fromStdString(name), + QByteArray::fromStdString(value)); + headersToRemove.insert(name); + } + } + + const QUrl requestUrl = QUrl::fromEncoded(QByteArray::fromStdString(request_.url.spec())); + const QHash<QByteArray, QByteArray> requestHeaders = + [](const net::HttpRequestHeaders &headers) { + QHash<QByteArray, QByteArray> result; + for (const auto &header : headers.GetHeaderVector()) { + result.insert(QByteArray::fromStdString(header.key), + QByteArray::fromStdString(header.value)); + } + return result; + }(request_.headers); + + QWebEngineUrlResponseInfo info(requestUrl, requestHeaders, responseHeaders); + interceptor->interceptResponseHeaders(info); + + if (info.d_ptr->isModified) { + responseHeadersPtr->RemoveHeaders(headersToRemove); + for (auto it = info.responseHeaders().cbegin(); it != info.responseHeaders().cend(); ++it) + responseHeadersPtr->AddHeader(it.key().toStdString(), it.value().toStdString()); + } +} + void InterceptedRequest::Restart() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -404,6 +465,12 @@ void InterceptedRequest::ContinueAfterIntercept() void InterceptedRequest::OnReceiveResponse(network::mojom::URLResponseHeadPtr head, mojo::ScopedDataPipeConsumerHandle handle, absl::optional<mojo_base::BigBuffer> buffer) { + QWebEngineUrlResponseInterceptor *const responseInterceptor = getProfileResponseInterceptor() + ? getProfileResponseInterceptor() + : getPageResponseInterceptor(); + if (responseInterceptor) + interceptResponseHeaders(responseInterceptor, head->headers.get()); + current_response_ = head.Clone(); target_client_->OnReceiveResponse(std::move(head), std::move(handle), std::move(buffer)); diff --git a/src/core/profile_adapter.cpp b/src/core/profile_adapter.cpp index e9e9aaeda..1b71744c9 100644 --- a/src/core/profile_adapter.cpp +++ b/src/core/profile_adapter.cpp @@ -191,6 +191,16 @@ void ProfileAdapter::setRequestInterceptor(QWebEngineUrlRequestInterceptor *inte m_requestInterceptor = interceptor; } +QWebEngineUrlResponseInterceptor *ProfileAdapter::responseInterceptor() +{ + return m_responseInterceptor.data(); +} + +void ProfileAdapter::setResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor) +{ + m_responseInterceptor = interceptor; +} + void ProfileAdapter::addClient(ProfileAdapterClient *adapterClient) { m_clients.append(adapterClient); diff --git a/src/core/profile_adapter.h b/src/core/profile_adapter.h index ab4622a4b..a45a12e5c 100644 --- a/src/core/profile_adapter.h +++ b/src/core/profile_adapter.h @@ -27,6 +27,7 @@ #include <QtWebEngineCore/qwebengineclientcertificatestore.h> #include <QtWebEngineCore/qwebenginecookiestore.h> #include <QtWebEngineCore/qwebengineurlrequestinterceptor.h> +#include <QtWebEngineCore/qwebengineurlresponseinterceptor.h> #include <QtWebEngineCore/qwebengineurlschemehandler.h> #include "net/qrc_url_scheme_handler.h" @@ -64,6 +65,9 @@ public: QWebEngineUrlRequestInterceptor* requestInterceptor(); void setRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor); + QWebEngineUrlResponseInterceptor *responseInterceptor(); + void setResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor); + QList<ProfileAdapterClient*> clients() { return m_clients; } void addClient(ProfileAdapterClient *adapterClient); void removeClient(ProfileAdapterClient *adapterClient); @@ -209,6 +213,7 @@ private: QWebEngineClientCertificateStore *m_clientCertificateStore = nullptr; #endif QPointer<QWebEngineUrlRequestInterceptor> m_requestInterceptor; + QPointer<QWebEngineUrlResponseInterceptor> m_responseInterceptor; QString m_dataPath; QString m_downloadPath; diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index 5b880d31a..997019b03 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -1013,6 +1013,16 @@ QWebEngineUrlRequestInterceptor* WebContentsAdapter::requestInterceptor() const return m_requestInterceptor; } +void WebContentsAdapter::setResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor) +{ + m_responseInterceptor = interceptor; +} + +QWebEngineUrlResponseInterceptor *WebContentsAdapter::responseInterceptor() const +{ + return m_responseInterceptor; +} + #if QT_CONFIG(accessibility) QAccessibleInterface *WebContentsAdapter::browserAccessible() { diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h index 0a97fde56..1e1b8bdc4 100644 --- a/src/core/web_contents_adapter.h +++ b/src/core/web_contents_adapter.h @@ -210,6 +210,8 @@ public: void updateRecommendedState(); void setRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor); QWebEngineUrlRequestInterceptor* requestInterceptor() const; + void setResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor); + QWebEngineUrlResponseInterceptor *responseInterceptor() const; private: Q_DISABLE_COPY(WebContentsAdapter) @@ -251,6 +253,7 @@ private: LifecycleState m_recommendedState = LifecycleState::Active; bool m_inspector = false; QPointer<QWebEngineUrlRequestInterceptor> m_requestInterceptor; + QPointer<QWebEngineUrlResponseInterceptor> m_responseInterceptor; }; } // namespace QtWebEngineCore diff --git a/src/webenginequick/api/qquickwebengineprofile.cpp b/src/webenginequick/api/qquickwebengineprofile.cpp index 8292da894..a830e969e 100644 --- a/src/webenginequick/api/qquickwebengineprofile.cpp +++ b/src/webenginequick/api/qquickwebengineprofile.cpp @@ -902,6 +902,19 @@ void QQuickWebEngineProfile::setUrlRequestInterceptor(QWebEngineUrlRequestInterc d->profileAdapter()->setRequestInterceptor(interceptor); } +/*! + Registers a response interceptor singleton \a interceptor to intercept URL responses. + + The profile does not take ownership of the pointer. + + \sa QWebEngineUrlResponseInterceptor +*/ +void QQuickWebEngineProfile::setUrlResponseInterceptor( + QWebEngineUrlResponseInterceptor *interceptor) +{ + Q_D(QQuickWebEngineProfile); + d->profileAdapter()->setResponseInterceptor(interceptor); +} /*! Returns the custom URL scheme handler register for the URL scheme \a scheme. diff --git a/src/webenginequick/api/qquickwebengineprofile.h b/src/webenginequick/api/qquickwebengineprofile.h index 29d6ee0b2..43b02e45a 100644 --- a/src/webenginequick/api/qquickwebengineprofile.h +++ b/src/webenginequick/api/qquickwebengineprofile.h @@ -8,6 +8,7 @@ #include <QtCore/qobject.h> #include <QtCore/qscopedpointer.h> #include <QtCore/qstring.h> +#include <QtWebEngineCore/qwebengineurlresponseinterceptor.h> #include <QtQml/qqmlregistration.h> QT_BEGIN_NAMESPACE @@ -90,6 +91,7 @@ public: QWebEngineCookieStore *cookieStore() const; void setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor); + void setUrlResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor); const QWebEngineUrlSchemeHandler *urlSchemeHandler(const QByteArray &) const; void installUrlSchemeHandler(const QByteArray &scheme, QWebEngineUrlSchemeHandler *); diff --git a/tests/auto/core/CMakeLists.txt b/tests/auto/core/CMakeLists.txt index 6ba6ffd92..9d131da56 100644 --- a/tests/auto/core/CMakeLists.txt +++ b/tests/auto/core/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(qwebenginecookiestore) add_subdirectory(qwebengineloadinginfo) add_subdirectory(qwebenginesettings) add_subdirectory(qwebengineurlrequestinterceptor) +add_subdirectory(qwebengineurlresponseinterceptor) add_subdirectory(qwebengineurlrequestjob) add_subdirectory(origins) add_subdirectory(devtools) diff --git a/tests/auto/core/qwebengineurlresponseinterceptor/CMakeLists.txt b/tests/auto/core/qwebengineurlresponseinterceptor/CMakeLists.txt new file mode 100644 index 000000000..08a5b9699 --- /dev/null +++ b/tests/auto/core/qwebengineurlresponseinterceptor/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../httpserver/httpserver.cmake) + +qt_internal_add_test(tst_qwebengineurlresponseinterceptor + SOURCES + tst_qwebengineurlresponseinterceptor.cpp + LIBRARIES + Qt::WebEngineCore + Test::HttpServer +) diff --git a/tests/auto/core/qwebengineurlresponseinterceptor/tst_qwebengineurlresponseinterceptor.cpp b/tests/auto/core/qwebengineurlresponseinterceptor/tst_qwebengineurlresponseinterceptor.cpp new file mode 100644 index 000000000..4d08853ef --- /dev/null +++ b/tests/auto/core/qwebengineurlresponseinterceptor/tst_qwebengineurlresponseinterceptor.cpp @@ -0,0 +1,140 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QtTest/QtTest> +#include <QtWebEngineCore/QWebEngineUrlResponseInterceptor> +#include <QtWebEngineCore/QWebEngineProfile> +#include <QtWebEngineCore/QWebEnginePage> +#include <QtWebEngineCore/QWebEngineLoadingInfo> +#include <QtWebEngineCore/QWebEngineUrlResponseInfo> + +#include <httpserver.h> +#include <httpreqrep.h> + +class tst_QWebEngineUrlResponseInterceptor : public QObject +{ + Q_OBJECT + +public: + tst_QWebEngineUrlResponseInterceptor() { } + ~tst_QWebEngineUrlResponseInterceptor() { } + +public Q_SLOTS: + void init() { } + void cleanup() { } + +private Q_SLOTS: + void initTestCase() { } + void cleanupTestCase() { } + void interceptRequest_data(); + void interceptRequest(); +}; + +Q_LOGGING_CATEGORY(lc, "qt.webengine.tests") + +class Interceptor : public QWebEngineUrlResponseInterceptor +{ + Q_OBJECT + + QUrl m_receivedRequestUrl; + QHash<QByteArray, QByteArray> m_receivedRequestHeaders; + +public: + void interceptResponseHeaders(QWebEngineUrlResponseInfo &info) override + { + m_receivedRequestUrl = info.requestUrl(); + m_receivedRequestHeaders = info.requestHeaders(); + QHash<QByteArray, QByteArray> responseHeaders = info.responseHeaders(); + + responseHeaders.insert(QByteArrayLiteral("ADDEDHEADER"), QByteArrayLiteral("ADDEDVALUE")); + *(responseHeaders.find(QByteArrayLiteral("content-length"))) = QByteArrayLiteral("57"); + + info.setResponseHeaders(responseHeaders); + } + + void getReceivedRequest(QUrl *receivedRequestUrl, + QHash<QByteArray, QByteArray> *receivedRequestHeaders) + { + *receivedRequestUrl = m_receivedRequestUrl; + *receivedRequestHeaders = m_receivedRequestHeaders; + } +}; + +void tst_QWebEngineUrlResponseInterceptor::interceptRequest_data() +{ + QTest::addColumn<bool>("withProfileInterceptor"); + QTest::newRow("with profile interceptor") << true; + QTest::newRow("with page interceptor") << false; +} + +void tst_QWebEngineUrlResponseInterceptor::interceptRequest() +{ + QFETCH(bool, withProfileInterceptor); + + HttpServer httpServer; + QObject::connect(&httpServer, &HttpServer::newRequest, this, [&](HttpReqRep *rr) { + if (rr->requestPath() == QByteArrayLiteral("/okay.html")) { + rr->setResponseBody(QByteArrayLiteral( + "<html><script>console.log('hello world js!');</script></html>")); + rr->sendResponse(); + } + }); + QVERIFY(httpServer.start()); + + const QUrl requestUrl = httpServer.url(QStringLiteral("/okay.html")); + + Interceptor interceptor; + QWebEngineProfile profile; + QWebEnginePage page(&profile); + if (withProfileInterceptor) + profile.setUrlResponseInterceptor(&interceptor); + else + page.setUrlResponseInterceptor(&interceptor); + + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + + bool headersWereChanged = false; + QObject::connect( + &page, &QWebEnginePage::loadingChanged, this, [&](QWebEngineLoadingInfo loadingInfo) { + const QHash<QByteArray, QByteArray> responseHeaders = loadingInfo.responseHeaders(); + bool contentLengthSizeChanged = false; + bool additionalHeaderAdded = false; + for (auto it = responseHeaders.constBegin(); it != responseHeaders.constEnd(); + ++it) { + if (it.key() == QByteArrayLiteral("content-length") + && it.value() == QByteArrayLiteral("57")) + contentLengthSizeChanged = true; + if (it.key() == QByteArrayLiteral("ADDEDHEADER") + && it.value() == QByteArrayLiteral("ADDEDVALUE")) + additionalHeaderAdded = true; + } + + if (contentLengthSizeChanged && additionalHeaderAdded) + headersWereChanged = true; + }); + + page.load(requestUrl); + + QVERIFY(loadSpy.wait()); + + QUrl receivedRequestUrl; + QHash<QByteArray, QByteArray> receivedRequestHeaders; + interceptor.getReceivedRequest(&receivedRequestUrl, &receivedRequestHeaders); + + bool receivedRequestHeadersContainsQtWebEngine = false; + for (auto it = receivedRequestHeaders.cbegin(); it != receivedRequestHeaders.cend(); ++it) { + if (it.value().contains("QtWebEngine/")) { + receivedRequestHeadersContainsQtWebEngine = true; + break; + } + } + + QVERIFY(headersWereChanged); + QCOMPARE_EQ(receivedRequestUrl, requestUrl); + QVERIFY(receivedRequestHeaders.size() != 0); + QVERIFY(receivedRequestHeadersContainsQtWebEngine); + QVERIFY(httpServer.stop()); +} + +QTEST_MAIN(tst_QWebEngineUrlResponseInterceptor) +#include "tst_qwebengineurlresponseinterceptor.moc" |