/**************************************************************************** ** ** 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/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 "api/qwebengineurlscheme.h" #include "net/url_request_custom_job_proxy.h" #include "profile_adapter.h" #include "type_conversion.h" #include #include #include #include #include #include #include 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) { // 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 &removed_headers, const net::HttpRequestHeaders &modified_headers, const net::HttpRequestHeaders &modified_cors_exempt_headers, // FIXME: do something with this? const absl::optional &new_url) override { // We can be asked for follow our own redirect scoped_refptr 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) // ### 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_receiver(this, std::move(loader)) , m_client(std::move(client_info)) , 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); } ~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); // Custom schemes are not covered by CorsURLLoader, so we need to reject CORS requests manually. if (!m_corsEnabled && !m_request.request_initiator->IsSameOriginWith(url::Origin::Create(m_request.url))) 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 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(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)); base::PostTask(FROM_HERE, { content::BrowserThread::UI }, 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"; } } m_head->headers = base::MakeRefCounted(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::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)); m_client->OnStartLoadingResponseBody(std::move(m_pipeConsumerHandle)); m_head = nullptr; if (readAvailableData()) // May delete this return; m_watcher = std::make_unique( FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC, m_taskRunner); m_watcher->Watch(m_pipeProducerHandle.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::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(std::move(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_pipeProducerHandle->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::max()}) bufferSize = std::min(bufferSize, uint32_t(m_maxBytesToRead)); int readResult = m_device->read(static_cast(buffer), bufferSize); uint32_t bytesRead = std::max(readResult, 0); m_pipeProducerHandle->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 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 m_taskRunner; scoped_refptr m_proxy; mojo::Receiver m_receiver; network::mojom::URLLoaderClientPtr m_client; mojo::ScopedDataPipeProducerHandle m_pipeProducerHandle; mojo::ScopedDataPipeConsumerHandle m_pipeConsumerHandle; std::unique_ptr 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; base::WeakPtrFactory m_weakPtrFactory{this}; DISALLOW_COPY_AND_ASSIGN(CustomURLLoader); }; class CustomURLLoaderFactory : public network::mojom::URLLoaderFactory { public: CustomURLLoaderFactory(ProfileAdapter *profileAdapter, mojo::PendingReceiver receiver) : m_taskRunner(base::CreateSequencedTaskRunner({ content::BrowserThread::IO })) , 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 loader, int32_t request_id, uint32_t options, const network::ResourceRequest &request, mojo::PendingRemote 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 receiver) override { m_receivers.Add(this, std::move(receiver)); } void OnDisconnect() { if (m_receivers.empty()) delete this; } static mojo::PendingRemote Create(ProfileAdapter *profileAdapter) { mojo::PendingRemote pending_remote; new CustomURLLoaderFactory(profileAdapter, pending_remote.InitWithNewPipeAndPassReceiver()); return pending_remote; } const scoped_refptr m_taskRunner; mojo::ReceiverSet m_receivers; QPointer m_profileAdapter; DISALLOW_COPY_AND_ASSIGN(CustomURLLoaderFactory); }; } // namespace mojo::PendingRemote CreateCustomURLLoaderFactory(ProfileAdapter *profileAdapter) { return CustomURLLoaderFactory::Create(profileAdapter); } } // namespace QtWebEngineCore