diff options
Diffstat (limited to 'src/core/net/custom_url_loader_factory.cpp')
-rw-r--r-- | src/core/net/custom_url_loader_factory.cpp | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/src/core/net/custom_url_loader_factory.cpp b/src/core/net/custom_url_loader_factory.cpp new file mode 100644 index 000000000..4274def99 --- /dev/null +++ b/src/core/net/custom_url_loader_factory.cpp @@ -0,0 +1,518 @@ +// Copyright (C) 2019 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 "custom_url_loader_factory.h" + +#include "base/strings/stringprintf.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.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/cors/cors.h" +#include "services/network/public/cpp/resource_request.h" +#include "services/network/public/mojom/url_loader.mojom.h" +#include "services/network/public/mojom/url_loader_factory.mojom.h" +#include "services/network/public/mojom/url_response_head.mojom.h" +#include "url/url_util.h" +#include "url/url_util_qt.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/qpointer.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, + mojo::PendingReceiver<network::mojom::URLLoader> loader, + mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote, + 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_remote), profileAdapter); + customUrlLoader->Start(); + } + + // network::mojom::URLLoader: + void FollowRedirect(const std::vector<std::string> &removed_headers, + const net::HttpRequestHeaders &modified_headers, + const net::HttpRequestHeaders &modified_cors_exempt_headers, // FIXME: do something with this? + const absl::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)); + content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, + 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, + mojo::PendingReceiver<network::mojom::URLLoader> loader, + mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote, + QPointer<ProfileAdapter> profileAdapter) + // ### We can opt to run the url-loader on the UI thread instead + : m_taskRunner(content::GetIOThreadTaskRunner({})) + , m_proxy(new URLRequestCustomJobProxy(this, request.url.scheme(), profileAdapter)) + , m_receiver(this, std::move(loader)) + , m_client(std::move(client_remote)) + , m_request(request) + { + DCHECK(m_taskRunner->RunsTasksInCurrentSequence()); + m_receiver.set_disconnect_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); + m_isLocal = scheme.flags().testFlag(QWebEngineUrlScheme::LocalScheme); + } + + ~CustomURLLoader() override = default; + + void Start() + { + DCHECK(m_taskRunner->RunsTasksInCurrentSequence()); + + if (network::cors::IsCorsEnabledRequestMode(m_request.mode)) { + // CORS mode requires a valid request_initiator. + if (!m_request.request_initiator) + return CompleteWithFailure(net::ERR_INVALID_ARGUMENT); + + if (m_isLocal) { + std::string fromScheme = m_request.request_initiator->GetTupleOrPrecursorTupleIfOpaque().scheme(); + const std::vector<std::string> &localSchemes = url::GetLocalSchemes(); + bool fromLocal = base::Contains(localSchemes, fromScheme); + bool hasLocalAccess = fromLocal; + if (const url::CustomScheme *cs = url::CustomScheme::FindScheme(fromScheme)) + hasLocalAccess = cs->flags & (url::CustomScheme::LocalAccessAllowed | url::CustomScheme::Local); + if (!hasLocalAccess) + return CompleteWithFailure(net::ERR_ACCESS_DENIED); + } else if (!m_corsEnabled && !m_request.request_initiator->IsSameOriginWith(url::Origin::Create(m_request.url))) { + // Custom schemes are not covered by CorsURLLoader, so we need to reject CORS requests manually. + return CompleteWithFailure(network::CorsErrorStatus(network::mojom::CorsError::kCorsDisabledScheme)); + } + } + + if (mojo::CreateDataPipe(nullptr, m_pipeProducerHandle, m_pipeConsumerHandle) != MOJO_RESULT_OK) + return CompleteWithFailure(net::ERR_FAILED); + + m_head = network::mojom::URLResponseHead::New(); + m_head->request_start = base::TimeTicks::Now(); + + if (!m_pipeConsumerHandle.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, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, + base::BindOnce(&URLRequestCustomJobProxy::initialize, m_proxy, m_request.url, + m_request.method, m_request.request_initiator, std::move(headers), + m_request.request_body)); + } + + void CompleteWithFailure(network::CorsErrorStatus cors_error) + { + DCHECK(m_taskRunner->RunsTasksInCurrentSequence()); + m_client->OnComplete(network::URLLoaderCompletionStatus(cors_error)); + ClearProxyAndClient(false); + } + + 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_receiver.reset(); + 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_headerBytesRead; + 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)); + content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, + base::BindOnce(&URLRequestCustomJobProxy::release, m_proxy)); + if (!wait_for_loader_error || !m_receiver.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"; + } + } + for (auto it = m_additionalResponseHeaders.cbegin(); + it != m_additionalResponseHeaders.cend(); ++it) { + headers += it.key().toLower().toStdString() + ": " + it.value().toLower().toStdString() + + "\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 = {}; + net::RedirectInfo::FirstPartyURLPolicy first_party_url_policy = + m_request.update_first_party_url_on_redirect ? net::RedirectInfo::FirstPartyURLPolicy::UPDATE_URL_ON_REDIRECT + : net::RedirectInfo::FirstPartyURLPolicy::NEVER_CHANGE_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, absl::nullopt, false /*insecure_scheme_was_upgraded*/); + m_client->OnReceiveRedirect(redirectInfo, std::move(m_head)); + m_head = nullptr; + // ### 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_headerBytesRead = m_head->headers->raw_headers().length(); + m_client->OnReceiveResponse(std::move(m_head), std::move(m_pipeConsumerHandle), absl::nullopt); + m_head = nullptr; + + m_watcher = std::make_unique<mojo::SimpleWatcher>( + FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL, m_taskRunner); + m_watcher->Watch(m_pipeProducerHandle.get(), MOJO_HANDLE_SIGNAL_WRITABLE, + MOJO_WATCH_CONDITION_SATISFIED, + base::BindRepeating(&CustomURLLoader::notifyReadyWrite, + m_weakPtrFactory.GetWeakPtr())); + + readAvailableData(); // May delete this + } + 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 = {}; + m_client->OnReceiveResponse(std::move(m_head), mojo::ScopedDataPipeConsumerHandle(), absl::nullopt); + 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_pipeProducerHandle->BeginWriteData( + &buffer, &bufferSize, MOJO_BEGIN_WRITE_DATA_FLAG_NONE); + if (beginResult == MOJO_RESULT_SHOULD_WAIT) { + m_watcher->ArmOrNotify(); + 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_pipeProducerHandle->EndWriteData(bytesRead); + m_totalBytesRead += bytesRead; + m_client->OnTransferSizeUpdated(m_totalBytesRead); + + const bool deviceAtEnd = m_device->atEnd(); + if ((deviceAtEnd && !m_device->isSequential()) + || (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 && deviceAtEnd && m_device->isSequential()) { + // Failure on read, and sequential device claiming to be at end, so treat it as a successful end-of-data. + OnTransferComplete(MOJO_RESULT_OK); + return true; // Done with reading + } + 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::SequencedTaskRunner *taskRunner() override + { + DCHECK(m_taskRunner->RunsTasksInCurrentSequence()); + return m_taskRunner.get(); + } + + scoped_refptr<base::SequencedTaskRunner> m_taskRunner; + scoped_refptr<URLRequestCustomJobProxy> m_proxy; + + mojo::Receiver<network::mojom::URLLoader> m_receiver; + mojo::Remote<network::mojom::URLLoaderClient> m_client; + mojo::ScopedDataPipeProducerHandle m_pipeProducerHandle; + mojo::ScopedDataPipeConsumerHandle m_pipeConsumerHandle; + 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::mojom::URLResponseHeadPtr m_head; + qint64 m_headerBytesRead = 0; + qint64 m_totalBytesRead = 0; + bool m_corsEnabled; + bool m_isLocal; + + base::WeakPtrFactory<CustomURLLoader> m_weakPtrFactory{this}; +}; + +class CustomURLLoaderFactory : public network::mojom::URLLoaderFactory { +public: + CustomURLLoaderFactory(ProfileAdapter *profileAdapter, mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver) + : m_taskRunner(content::GetIOThreadTaskRunner({})) + , m_profileAdapter(profileAdapter) + { + m_receivers.set_disconnect_handler(base::BindRepeating( + &CustomURLLoaderFactory::OnDisconnect, base::Unretained(this))); + m_receivers.Add(this, std::move(receiver)); + } + ~CustomURLLoaderFactory() override = default; + + // network::mojom::URLLoaderFactory: + void CreateLoaderAndStart(mojo::PendingReceiver<network::mojom::URLLoader> loader, + 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(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)); + } + + void OnDisconnect() + { + if (m_receivers.empty()) + delete this; + } + + static mojo::PendingRemote<network::mojom::URLLoaderFactory> Create(ProfileAdapter *profileAdapter) + { + mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_remote; + new CustomURLLoaderFactory(profileAdapter, pending_remote.InitWithNewPipeAndPassReceiver()); + return pending_remote; + } + + const scoped_refptr<base::SequencedTaskRunner> m_taskRunner; + mojo::ReceiverSet<network::mojom::URLLoaderFactory> m_receivers; + QPointer<ProfileAdapter> m_profileAdapter; +}; + +} // namespace + +mojo::PendingRemote<network::mojom::URLLoaderFactory> CreateCustomURLLoaderFactory(ProfileAdapter *profileAdapter) +{ + return CustomURLLoaderFactory::Create(profileAdapter); +} + +} // namespace QtWebEngineCore + |