blob: e5f5805362c4d1e9c44ebe0a030603714f3a5606 [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 "custom_url_loader_factory.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "api/qwebengineurlscheme.h"
#include "net/url_request_custom_job_proxy.h"
#include "profile_adapter.h"
#include "type_conversion.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qiodevice.h>
#include <QtCore/qmimedatabase.h>
#include <QtCore/qmimedata.h>
#include <QtCore/qurl.h>
namespace QtWebEngineCore {
namespace {
class CustomURLLoader : public network::mojom::URLLoader
, private URLRequestCustomJobProxy::Client
{
public:
static void CreateAndStart(const network::ResourceRequest &request,
network::mojom::URLLoaderRequest loader,
network::mojom::URLLoaderClientPtrInfo client_info,
QPointer<ProfileAdapter> profileAdapter)
{
// CustomURLLoader will handle its own life-cycle, and delete when
// the client lets go.
auto *customUrlLoader = new CustomURLLoader(request, std::move(loader), std::move(client_info), profileAdapter);
customUrlLoader->Start();
}
// network::mojom::URLLoader:
void FollowRedirect(const std::vector<std::string> &removed_headers,
const net::HttpRequestHeaders &modified_headers,
const base::Optional<GURL> &new_url) override
{
// We can be asked for follow our own redirect
scoped_refptr<URLRequestCustomJobProxy> proxy = new URLRequestCustomJobProxy(this, m_proxy->m_scheme, m_proxy->m_profileAdapter);
m_proxy->m_client = nullptr;
// m_taskRunner->PostTask(FROM_HERE, base::BindOnce(&URLRequestCustomJobProxy::release, m_proxy));
base::PostTask(FROM_HERE, { content::BrowserThread::UI },
base::BindOnce(&URLRequestCustomJobProxy::release, m_proxy));
m_proxy = std::move(proxy);
if (new_url)
m_request.url = *new_url;
else
m_request.url = m_redirect;
m_redirect = GURL();
for (const std::string &header: removed_headers)
m_request.headers.RemoveHeader(header);
m_request.headers.MergeFrom(modified_headers);
Start();
}
void SetPriority(net::RequestPriority priority, int32_t intra_priority_value) override { }
void PauseReadingBodyFromNet() override { }
void ResumeReadingBodyFromNet() override { }
private:
CustomURLLoader(const network::ResourceRequest &request,
network::mojom::URLLoaderRequest loader,
network::mojom::URLLoaderClientPtrInfo client_info,
QPointer<ProfileAdapter> profileAdapter)
// ### We can opt to run the url-loader on the UI thread instead
: m_taskRunner(base::CreateSingleThreadTaskRunner({ content::BrowserThread::IO }))
, m_proxy(new URLRequestCustomJobProxy(this, request.url.scheme(), profileAdapter))
, m_binding(this, std::move(loader))
, m_client(std::move(client_info))
, m_request(request)
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
m_binding.set_connection_error_handler(
base::BindOnce(&CustomURLLoader::OnConnectionError, m_weakPtrFactory.GetWeakPtr()));
m_firstBytePosition = 0;
m_device = nullptr;
m_error = 0;
QWebEngineUrlScheme scheme = QWebEngineUrlScheme::schemeByName(QByteArray::fromStdString(request.url.scheme()));
m_corsEnabled = scheme.flags().testFlag(QWebEngineUrlScheme::CorsEnabled);
}
~CustomURLLoader() override = default;
void Start()
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
m_head.request_start = base::TimeTicks::Now();
if (!m_pipe.consumer_handle.is_valid())
return CompleteWithFailure(net::ERR_FAILED);
std::map<std::string, std::string> headers;
net::HttpRequestHeaders::Iterator it(m_request.headers);
while (it.GetNext())
headers.emplace(it.name(), it.value());
if (!m_request.referrer.is_empty())
headers.emplace("Referer", m_request.referrer.spec());
std::string rangeHeader;
if (ParseRange(m_request.headers))
m_firstBytePosition = m_byteRange.first_byte_position();
// m_taskRunner->PostTask(FROM_HERE,
base::PostTask(FROM_HERE, { content::BrowserThread::UI },
base::BindOnce(&URLRequestCustomJobProxy::initialize, m_proxy,
m_request.url, m_request.method, m_request.request_initiator, std::move(headers)));
}
void CompleteWithFailure(net::Error net_error)
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
m_client->OnComplete(network::URLLoaderCompletionStatus(net_error));
ClearProxyAndClient(false);
}
void OnConnectionError()
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
m_binding.Close();
if (m_client.is_bound())
ClearProxyAndClient(false);
else
delete this;
}
void OnTransferComplete(MojoResult result)
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
if (result == MOJO_RESULT_OK) {
network::URLLoaderCompletionStatus status(net::OK);
status.encoded_data_length = m_totalBytesRead + m_head.headers->raw_headers().length();
status.encoded_body_length = m_totalBytesRead;
status.decoded_body_length = m_totalBytesRead;
m_client->OnComplete(status);
} else {
m_client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
}
ClearProxyAndClient(false /* result == MOJO_RESULT_OK */);
}
void ClearProxyAndClient(bool wait_for_loader_error = false)
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
m_proxy->m_client = nullptr;
m_client.reset();
if (m_device && m_device->isOpen())
m_device->close();
m_device = nullptr;
// m_taskRunner->PostTask(FROM_HERE, base::BindOnce(&URLRequestCustomJobProxy::release, m_proxy));
base::PostTask(FROM_HERE, { content::BrowserThread::UI },
base::BindOnce(&URLRequestCustomJobProxy::release, m_proxy));
if (!wait_for_loader_error || !m_binding.is_bound())
delete this;
}
// URLRequestCustomJobProxy::Client:
void notifyExpectedContentSize(qint64 size) override
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
m_totalSize = size;
if (m_byteRange.IsValid()) {
if (!m_byteRange.ComputeBounds(size)) {
CompleteWithFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
} else {
m_maxBytesToRead = m_byteRange.last_byte_position() - m_byteRange.first_byte_position() + 1;
m_head.content_length = m_maxBytesToRead;
}
} else {
m_head.content_length = size;
}
}
void notifyHeadersComplete() override
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
DCHECK(!m_error);
m_head.response_start = base::TimeTicks::Now();
std::string headers;
if (!m_redirect.is_empty()) {
headers += "HTTP/1.1 303 See Other\n";
headers += base::StringPrintf("Location: %s\n", m_redirect.spec().c_str());
} else {
if (m_byteRange.IsValid() && m_totalSize > 0) {
headers += "HTTP/1.1 206 Partial Content\n";
headers += net::HttpResponseHeaders::kContentRange;
headers += base::StringPrintf(": bytes %lld-%lld/%lld",
qlonglong{m_byteRange.first_byte_position()},
qlonglong{m_byteRange.last_byte_position()},
qlonglong{m_totalSize});
headers += "\n";
} else {
headers += "HTTP/1.1 200 OK\n";
}
if (m_mimeType.size() > 0) {
headers += net::HttpRequestHeaders::kContentType;
headers += base::StringPrintf(": %s", m_mimeType.c_str());
if (m_charset.size() > 0)
headers += base::StringPrintf("; charset=%s", m_charset.c_str());
headers += "\n";
}
}
if (m_corsEnabled) {
std::string origin;
if (m_request.headers.GetHeader("Origin", &origin)) {
headers += base::StringPrintf("Access-Control-Allow-Origin: %s\n", origin.c_str());
headers += "Access-Control-Allow-Credentials: true\n";
}
}
m_head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(net::HttpUtil::AssembleRawHeaders(headers));
m_head.encoded_data_length = m_head.headers->raw_headers().length();
if (!m_redirect.is_empty()) {
m_head.content_length = m_head.encoded_body_length = -1;
net::URLRequest::FirstPartyURLPolicy first_party_url_policy =
m_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(
m_request.method, m_request.url,
m_request.site_for_cookies,
first_party_url_policy, m_request.referrer_policy,
m_request.referrer.spec(), net::HTTP_SEE_OTHER,
m_redirect, base::nullopt, false /*insecure_scheme_was_upgraded*/);
m_client->OnReceiveRedirect(redirectInfo, m_head);
// ### should m_request be updated with RedirectInfo? (see FollowRedirect)
return;
}
DCHECK(m_device);
m_head.mime_type = m_mimeType;
m_head.charset = m_charset;
m_client->OnReceiveResponse(m_head);
m_client->OnStartLoadingResponseBody(std::move(m_pipe.consumer_handle));
if (readAvailableData()) // May delete this
return;
m_watcher = std::make_unique<mojo::SimpleWatcher>(
FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC, m_taskRunner);
m_watcher->Watch(m_pipe.producer_handle.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
MOJO_WATCH_CONDITION_SATISFIED,
base::BindRepeating(&CustomURLLoader::notifyReadyWrite,
m_weakPtrFactory.GetWeakPtr()));
}
void notifyCanceled() override
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
OnTransferComplete(MOJO_RESULT_CANCELLED);
}
void notifyAborted() override
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
notifyStartFailure(net::ERR_ABORTED);
}
void notifyStartFailure(int error) override
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
m_head.response_start = base::TimeTicks::Now();
std::string headers;
switch (error) {
case net::ERR_INVALID_URL:
headers = "HTTP/1.1 400 Bad Request\n";
break;
case net::ERR_FILE_NOT_FOUND:
headers = "HTTP/1.1 404 Not Found\n";
break;
case net::ERR_ABORTED:
headers = "HTTP/1.1 503 Request Aborted\n";
break;
case net::ERR_ACCESS_DENIED:
headers = "HTTP/1.1 403 Forbidden\n";
break;
case net::ERR_FAILED:
headers = "HTTP/1.1 400 Request Failed\n";
break;
default:
headers = "HTTP/1.1 500 Internal Error\n";
break;
}
m_head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(net::HttpUtil::AssembleRawHeaders(headers));
m_head.encoded_data_length = m_head.headers->raw_headers().length();
m_head.content_length = m_head.encoded_body_length = -1;
m_client->OnReceiveResponse(m_head);
CompleteWithFailure(net::Error(error));
}
void notifyReadyRead() override
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
readAvailableData();
}
void notifyReadyWrite(MojoResult result, const mojo::HandleSignalsState &state)
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
if (result != MOJO_RESULT_OK) {
CompleteWithFailure(net::ERR_FAILED);
return;
}
readAvailableData();
}
bool readAvailableData()
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
for (;;) {
if (m_error || !m_device)
break;
void *buffer = nullptr;
uint32_t bufferSize = 0;
MojoResult beginResult = m_pipe.producer_handle->BeginWriteData(
&buffer, &bufferSize, MOJO_BEGIN_WRITE_DATA_FLAG_NONE);
if (beginResult == MOJO_RESULT_SHOULD_WAIT)
return false; // Wait for pipe watcher
if (beginResult != MOJO_RESULT_OK)
break;
if (m_maxBytesToRead > 0 && m_maxBytesToRead <= int64_t{std::numeric_limits<uint32_t>::max()})
bufferSize = std::min(bufferSize, uint32_t(m_maxBytesToRead));
int readResult = m_device->read(static_cast<char *>(buffer), bufferSize);
uint32_t bytesRead = std::max(readResult, 0);
m_pipe.producer_handle->EndWriteData(bytesRead);
m_totalBytesRead += bytesRead;
m_client->OnTransferSizeUpdated(m_totalBytesRead);
if (m_device->atEnd() || (m_maxBytesToRead > 0 && m_totalBytesRead >= m_maxBytesToRead)) {
OnTransferComplete(MOJO_RESULT_OK);
return true; // Done with reading
}
if (readResult == 0)
return false; // Wait for readyRead
if (readResult < 0)
break;
}
CompleteWithFailure(m_error ? net::Error(m_error) : net::ERR_FAILED);
return true; // Done with reading
}
bool ParseRange(const net::HttpRequestHeaders &headers)
{
std::string range_header;
if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
std::vector<net::HttpByteRange> ranges;
if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
// Chromium doesn't support multirange requests.
if (ranges.size() == 1) {
m_byteRange = ranges[0];
return true;
}
}
}
return false;
}
base::TaskRunner *taskRunner() override
{
DCHECK(m_taskRunner->RunsTasksInCurrentSequence());
return m_taskRunner.get();
}
scoped_refptr<base::SequencedTaskRunner> m_taskRunner;
scoped_refptr<URLRequestCustomJobProxy> m_proxy;
mojo::Binding<network::mojom::URLLoader> m_binding;
network::mojom::URLLoaderClientPtr m_client;
mojo::DataPipe m_pipe;
std::unique_ptr<mojo::SimpleWatcher> m_watcher;
net::HttpByteRange m_byteRange;
int64_t m_totalSize = 0;
int64_t m_maxBytesToRead = -1;
network::ResourceRequest m_request;
network::ResourceResponseHead m_head;
qint64 m_totalBytesRead = 0;
bool m_corsEnabled;
base::WeakPtrFactory<CustomURLLoader> m_weakPtrFactory{this};
DISALLOW_COPY_AND_ASSIGN(CustomURLLoader);
};
class CustomURLLoaderFactory : public network::mojom::URLLoaderFactory {
public:
CustomURLLoaderFactory(ProfileAdapter *profileAdapter)
: m_taskRunner(base::CreateSequencedTaskRunner({ content::BrowserThread::IO }))
, m_profileAdapter(profileAdapter)
{
}
~CustomURLLoaderFactory() override = default;
// network::mojom::URLLoaderFactory:
void 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> client,
const net::MutableNetworkTrafficAnnotationTag &traffic_annotation) override
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Q_UNUSED(routing_id);
Q_UNUSED(request_id);
Q_UNUSED(options);
Q_UNUSED(traffic_annotation);
m_taskRunner->PostTask(FROM_HERE,
base::BindOnce(&CustomURLLoader::CreateAndStart, request,
std::move(loader), std::move(client),
m_profileAdapter));
}
void Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver) override
{
m_receivers.Add(this, std::move(receiver));
}
const scoped_refptr<base::SequencedTaskRunner> m_taskRunner;
mojo::ReceiverSet<network::mojom::URLLoaderFactory> m_receivers;
QPointer<ProfileAdapter> m_profileAdapter;
DISALLOW_COPY_AND_ASSIGN(CustomURLLoaderFactory);
};
} // namespace
std::unique_ptr<network::mojom::URLLoaderFactory> CreateCustomURLLoaderFactory(ProfileAdapter *profileAdapter)
{
return std::make_unique<CustomURLLoaderFactory>(profileAdapter);
}
} // namespace QtWebEngineCore