blob: 8e084a40bc8e7e31457c0fb0ab73ed6bb94e55f5 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2019 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 "proxying_url_loader_factory_qt.h"
#include <utility>
#include "base/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "components/safe_browsing/common/safebrowsing_constants.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_constants.h"
#include "content/public/common/url_utils.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "api/qwebengineurlrequestinfo_p.h"
#include "profile_qt.h"
#include "type_conversion.h"
#include "web_contents_adapter_client.h"
#include "web_contents_view_qt.h"
#include <QVariant>
// 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
// found in the LICENSE file.
namespace QtWebEngineCore {
extern WebContentsAdapterClient::NavigationType pageTransitionToNavigationType(ui::PageTransition transition);
static QWebEngineUrlRequestInfo::ResourceType toQt(content::ResourceType resourceType)
{
if (resourceType >= content::ResourceType::kMainFrame && resourceType <= content::ResourceType::kMaxValue)
return static_cast<QWebEngineUrlRequestInfo::ResourceType>(resourceType);
return QWebEngineUrlRequestInfo::ResourceTypeUnknown;
}
static QWebEngineUrlRequestInfo::NavigationType toQt(WebContentsAdapterClient::NavigationType navigationType)
{
return static_cast<QWebEngineUrlRequestInfo::NavigationType>(navigationType);
}
// Handles intercepted, in-progress requests/responses, so that they can be
// controlled and modified accordingly.
class InterceptedRequest : public network::mojom::URLLoader
, public network::mojom::URLLoaderClient
{
public:
InterceptedRequest(int process_id, uint64_t request_id, int32_t routing_id, uint32_t options,
const network::ResourceRequest &request,
const net::MutableNetworkTrafficAnnotationTag &traffic_annotation,
QWebEngineUrlRequestInterceptor *profile_request_interceptor,
QWebEngineUrlRequestInterceptor *page_request_interceptor,
mojo::PendingReceiver<network::mojom::URLLoader> loader,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory);
~InterceptedRequest() override;
void Restart();
// network::mojom::URLLoaderClient
void OnReceiveResponse(network::mojom::URLResponseHeadPtr head) override;
void OnReceiveRedirect(const net::RedirectInfo &redirect_info, network::mojom::URLResponseHeadPtr head) override;
void OnUploadProgress(int64_t current_position, int64_t total_size, OnUploadProgressCallback callback) override;
void OnReceiveCachedMetadata(mojo_base::BigBuffer data) override;
void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
void OnStartLoadingResponseBody(mojo::ScopedDataPipeConsumerHandle body) override;
void OnComplete(const network::URLLoaderCompletionStatus &status) override;
// network::mojom::URLLoader
void FollowRedirect(const std::vector<std::string> &removed_headers,
const net::HttpRequestHeaders &modified_headers, const base::Optional<GURL> &new_url) override;
void SetPriority(net::RequestPriority priority, int32_t intra_priority_value) override;
void PauseReadingBodyFromNet() override;
void ResumeReadingBodyFromNet() override;
private:
void InterceptOnUIThread();
void InterceptOnIOThread(base::WaitableEvent *event);
void ContinueAfterIntercept();
// This is called when the original URLLoaderClient has a connection error.
void OnURLLoaderClientError();
// This is called when the original URLLoader has a connection error.
void OnURLLoaderError(uint32_t custom_reason, const std::string &description);
// Call OnComplete on |target_client_|. If |wait_for_loader_error| is true
// then this object will wait for |proxied_loader_binding_| to have a
// connection error before destructing.
void CallOnComplete(const network::URLLoaderCompletionStatus &status, bool wait_for_loader_error);
void SendErrorAndCompleteImmediately(int error_code);
const int process_id_;
const uint64_t request_id_;
const int32_t routing_id_;
const uint32_t options_;
bool input_stream_previously_failed_ = false;
bool request_was_redirected_ = false;
// If the |target_loader_| called OnComplete with an error this stores it.
// That way the destructor can send it to OnReceivedError if safe browsing
// error didn't occur.
int error_status_ = net::OK;
network::ResourceRequest request_;
network::ResourceResponseHead current_response_;
const net::MutableNetworkTrafficAnnotationTag traffic_annotation_;
QWebEngineUrlRequestInfo request_info_;
QPointer<QWebEngineUrlRequestInterceptor> profile_request_interceptor_;
QPointer<QWebEngineUrlRequestInterceptor> page_request_interceptor_;
mojo::Receiver<network::mojom::URLLoader> proxied_loader_receiver_;
mojo::Remote<network::mojom::URLLoaderClient> target_client_;
mojo::Receiver<network::mojom::URLLoaderClient> proxied_client_receiver_{this};
mojo::Remote<network::mojom::URLLoader> target_loader_;
mojo::Remote<network::mojom::URLLoaderFactory> target_factory_;
base::WeakPtrFactory<InterceptedRequest> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(InterceptedRequest);
};
InterceptedRequest::InterceptedRequest(int process_id, uint64_t request_id, int32_t routing_id, uint32_t options,
const network::ResourceRequest &request,
const net::MutableNetworkTrafficAnnotationTag &traffic_annotation,
QWebEngineUrlRequestInterceptor *profile_request_interceptor,
QWebEngineUrlRequestInterceptor *page_request_interceptor,
mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory)
: process_id_(process_id)
, request_id_(request_id)
, routing_id_(routing_id)
, options_(options)
, request_(request)
, traffic_annotation_(traffic_annotation)
, profile_request_interceptor_(profile_request_interceptor)
, page_request_interceptor_(page_request_interceptor)
, proxied_loader_receiver_(this, std::move(loader_receiver))
, target_client_(std::move(client))
, target_factory_(std::move(target_factory))
, weak_factory_(this)
{
// If there is a client error, clean up the request.
target_client_.set_disconnect_handler(
base::BindOnce(&InterceptedRequest::OnURLLoaderClientError, weak_factory_.GetWeakPtr()));
proxied_loader_receiver_.set_disconnect_with_reason_handler(
base::BindOnce(&InterceptedRequest::OnURLLoaderError, weak_factory_.GetWeakPtr()));
}
InterceptedRequest::~InterceptedRequest()
{
weak_factory_.InvalidateWeakPtrs();
}
void InterceptedRequest::Restart()
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::ResourceType resourceType = content::ResourceType(request_.resource_type);
WebContentsAdapterClient::NavigationType navigationType =
pageTransitionToNavigationType(ui::PageTransition(request_.transition_type));
const QUrl originalUrl = toQt(request_.url);
const QUrl initiator = request_.request_initiator.has_value() ? toQt(request_.request_initiator->GetURL()) : QUrl();
content::WebContents *webContents = nullptr;
if (process_id_) {
content::RenderFrameHost *frameHost = content::RenderFrameHost::FromID(process_id_, request_.render_frame_id);
webContents = content::WebContents::FromRenderFrameHost(frameHost);
} else {
webContents = content::WebContents::FromFrameTreeNodeId(request_.render_frame_id);
}
GURL top_document_url = webContents ? webContents->GetLastCommittedURL() : GURL();
QUrl firstPartyUrl;
if (!top_document_url.is_empty())
firstPartyUrl = toQt(top_document_url);
else
firstPartyUrl = toQt(request_.site_for_cookies); // m_topDocumentUrl can be empty for the main-frame.
QWebEngineUrlRequestInfoPrivate *infoPrivate =
new QWebEngineUrlRequestInfoPrivate(toQt(resourceType), toQt(navigationType), originalUrl, firstPartyUrl,
initiator, QByteArray::fromStdString(request_.method));
request_info_ = QWebEngineUrlRequestInfo(infoPrivate);
// TODO: remove for Qt6
if (profile_request_interceptor_ && profile_request_interceptor_->property("deprecated").toBool()) {
// sync call supports depracated call of an interceptor on io thread
base::WaitableEvent event;
base::PostTask(FROM_HERE, { content::BrowserThread::IO },
base::BindOnce(&InterceptedRequest::InterceptOnIOThread, base::Unretained(this), &event));
event.Wait();
if (request_info_.changed()) {
ContinueAfterIntercept();
return;
}
}
InterceptOnUIThread();
ContinueAfterIntercept();
}
void InterceptedRequest::InterceptOnIOThread(base::WaitableEvent *event)
{
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (profile_request_interceptor_)
profile_request_interceptor_->interceptRequest(request_info_);
event->Signal();
}
void InterceptedRequest::InterceptOnUIThread()
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (profile_request_interceptor_ && !profile_request_interceptor_->property("deprecated").toBool())
profile_request_interceptor_->interceptRequest(request_info_);
if (!request_info_.changed() && page_request_interceptor_)
page_request_interceptor_->interceptRequest(request_info_);
}
void InterceptedRequest::ContinueAfterIntercept()
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (request_info_.changed()) {
if (request_info_.d_ptr->shouldBlockRequest)
return SendErrorAndCompleteImmediately(net::ERR_BLOCKED_BY_CLIENT);
if (request_info_.d_ptr->shouldRedirectRequest) {
net::URLRequest::FirstPartyURLPolicy first_party_url_policy =
request_.update_first_party_url_on_redirect ? net::URLRequest::UPDATE_FIRST_PARTY_URL_ON_REDIRECT
: net::URLRequest::NEVER_CHANGE_FIRST_PARTY_URL;
net::RedirectInfo redirectInfo = net::RedirectInfo::ComputeRedirectInfo(
request_.method, request_.url, request_.site_for_cookies,
first_party_url_policy, request_.referrer_policy, request_.referrer.spec(),
net::HTTP_TEMPORARY_REDIRECT, toGurl(request_info_.requestUrl()), base::nullopt,
false /*insecure_scheme_was_upgraded*/);
// FIXME: Should probably create a new header.
current_response_.encoded_data_length = 0;
request_.method = redirectInfo.new_method;
request_.url = redirectInfo.new_url;
request_.site_for_cookies = redirectInfo.new_site_for_cookies;
request_.referrer = GURL(redirectInfo.new_referrer);
request_.referrer_policy = redirectInfo.new_referrer_policy;
if (request_.method == net::HttpRequestHeaders::kGetMethod)
request_.request_body = nullptr;
target_client_->OnReceiveRedirect(redirectInfo, current_response_);
return;
}
if (!request_info_.d_ptr->extraHeaders.isEmpty()) {
auto end = request_info_.d_ptr->extraHeaders.constEnd();
for (auto header = request_info_.d_ptr->extraHeaders.constBegin(); header != end; ++header) {
std::string h = header.key().toStdString();
if (base::LowerCaseEqualsASCII(h, "referer")) {
request_.referrer = GURL(header.value().toStdString());
} else {
request_.headers.SetHeader(h, header.value().toStdString());
}
}
}
}
if (!target_loader_ && target_factory_) {
target_factory_->CreateLoaderAndStart(target_loader_.BindNewPipeAndPassReceiver(), routing_id_, request_id_,
options_, request_, proxied_client_receiver_.BindNewPipeAndPassRemote(),
traffic_annotation_);
}
}
// URLLoaderClient methods.
void InterceptedRequest::OnReceiveResponse(network::mojom::URLResponseHeadPtr head)
{
current_response_ = head;
target_client_->OnReceiveResponse(std::move(head));
}
void InterceptedRequest::OnReceiveRedirect(const net::RedirectInfo &redirect_info, network::mojom::URLResponseHeadPtr head)
{
// TODO(timvolodine): handle redirect override.
request_was_redirected_ = true;
current_response_ = head;
target_client_->OnReceiveRedirect(redirect_info, std::move(head));
request_.url = redirect_info.new_url;
request_.method = redirect_info.new_method;
request_.site_for_cookies = redirect_info.new_site_for_cookies;
request_.referrer = GURL(redirect_info.new_referrer);
request_.referrer_policy = redirect_info.new_referrer_policy;
}
void InterceptedRequest::OnUploadProgress(int64_t current_position, int64_t total_size, OnUploadProgressCallback callback)
{
target_client_->OnUploadProgress(current_position, total_size, std::move(callback));
}
void InterceptedRequest::OnReceiveCachedMetadata(mojo_base::BigBuffer data)
{
target_client_->OnReceiveCachedMetadata(std::move(data));
}
void InterceptedRequest::OnTransferSizeUpdated(int32_t transfer_size_diff)
{
target_client_->OnTransferSizeUpdated(transfer_size_diff);
}
void InterceptedRequest::OnStartLoadingResponseBody(mojo::ScopedDataPipeConsumerHandle body)
{
target_client_->OnStartLoadingResponseBody(std::move(body));
}
void InterceptedRequest::OnComplete(const network::URLLoaderCompletionStatus &status)
{
// Only wait for the original loader to possibly have a custom error if the
// target loader succeeded. If the target loader failed, then it was a race as
// to whether that error or the safe browsing error would be reported.
CallOnComplete(status, status.error_code == net::OK);
}
// URLLoader methods.
void InterceptedRequest::FollowRedirect(const std::vector<std::string> &removed_headers,
const net::HttpRequestHeaders &modified_headers,
const base::Optional<GURL> &new_url)
{
if (target_loader_)
target_loader_->FollowRedirect(removed_headers, modified_headers, new_url);
// If |OnURLLoaderClientError| was called then we're just waiting for the
// connection error handler of |proxied_loader_binding_|. Don't restart the
// job since that'll create another URLLoader
if (!target_client_)
return;
Restart();
}
void InterceptedRequest::SetPriority(net::RequestPriority priority, int32_t intra_priority_value)
{
if (target_loader_)
target_loader_->SetPriority(priority, intra_priority_value);
}
void InterceptedRequest::PauseReadingBodyFromNet()
{
if (target_loader_)
target_loader_->PauseReadingBodyFromNet();
}
void InterceptedRequest::ResumeReadingBodyFromNet()
{
if (target_loader_)
target_loader_->ResumeReadingBodyFromNet();
}
void InterceptedRequest::OnURLLoaderClientError()
{
// We set |wait_for_loader_error| to true because if the loader did have a
// custom_reason error then the client would be reset as well and it would be
// a race as to which connection error we saw first.
CallOnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED), true /* wait_for_loader_error */);
}
void InterceptedRequest::OnURLLoaderError(uint32_t custom_reason, const std::string &description)
{
// If CallOnComplete was already called, then this object is ready to be deleted.
if (!target_client_)
delete this;
}
void InterceptedRequest::CallOnComplete(const network::URLLoaderCompletionStatus &status, bool wait_for_loader_error)
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Save an error status so that we call onReceiveError at destruction if there
// was no safe browsing error.
if (status.error_code != net::OK)
error_status_ = status.error_code;
if (target_client_)
target_client_->OnComplete(status);
if (proxied_loader_receiver_.is_bound() && wait_for_loader_error) {
// Since the original client is gone no need to continue loading the
// request.
proxied_client_receiver_.reset();
target_loader_.reset();
// Don't delete |this| yet, in case the |proxied_loader_receiver_|'s
// error_handler is called with a reason to indicate an error which we want
// to send to the client bridge. Also reset |target_client_| so we don't
// get its error_handler called and then delete |this|.
target_client_.reset();
// In case there are pending checks as to whether this request should be
// intercepted, we don't want that causing |target_client_| to be used
// later.
weak_factory_.InvalidateWeakPtrs();
} else {
delete this;
}
}
void InterceptedRequest::SendErrorAndCompleteImmediately(int error_code)
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto status = network::URLLoaderCompletionStatus(error_code);
target_client_->OnComplete(status);
delete this;
}
ProxyingURLLoaderFactoryQt::ProxyingURLLoaderFactoryQt(int process_id, QWebEngineUrlRequestInterceptor *profile, QWebEngineUrlRequestInterceptor *page,
mojo::PendingReceiver<network::mojom::URLLoaderFactory> loader_receiver,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_info)
: m_processId(process_id), m_profileRequestInterceptor(profile), m_pageRequestInterceptor(page), m_weakFactory(this)
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (target_factory_info) {
m_targetFactory.Bind(std::move(target_factory_info));
m_targetFactory.set_disconnect_handler(
base::BindOnce(&ProxyingURLLoaderFactoryQt::OnTargetFactoryError, m_weakFactory.GetWeakPtr()));
}
m_proxyReceivers.Add(this, std::move(loader_receiver));
m_proxyReceivers.set_disconnect_handler(
base::BindRepeating(&ProxyingURLLoaderFactoryQt::OnProxyBindingError, m_weakFactory.GetWeakPtr()));
}
ProxyingURLLoaderFactoryQt::~ProxyingURLLoaderFactoryQt()
{
m_weakFactory.InvalidateWeakPtrs();
}
void ProxyingURLLoaderFactoryQt::CreateLoaderAndStart(mojo::PendingReceiver<network::mojom::URLLoader> loader, int32_t routing_id,
int32_t request_id, uint32_t options, const network::ResourceRequest &request,
mojo::PendingRemote<network::mojom::URLLoaderClient> url_loader_client,
const net::MutableNetworkTrafficAnnotationTag &traffic_annotation)
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_clone;
if (m_targetFactory)
m_targetFactory->Clone(target_factory_clone.InitWithNewPipeAndPassReceiver());
// Will manage its own lifetime
InterceptedRequest *req = new InterceptedRequest(m_processId, request_id, routing_id, options, request, traffic_annotation,
m_profileRequestInterceptor, m_pageRequestInterceptor, std::move(loader),
std::move(url_loader_client), std::move(target_factory_clone));
req->Restart();
}
void ProxyingURLLoaderFactoryQt::OnTargetFactoryError()
{
delete this;
}
void ProxyingURLLoaderFactoryQt::OnProxyBindingError()
{
if (m_proxyReceivers.empty())
delete this;
}
void ProxyingURLLoaderFactoryQt::Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver)
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
m_proxyReceivers.Add(this, std::move(receiver));
}
} // namespace QtWebEngineCore