diff options
Diffstat (limited to 'src/core/net/custom_url_loader_factory.cpp')
-rw-r--r-- | src/core/net/custom_url_loader_factory.cpp | 446 |
1 files changed, 446 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..555eccf42 --- /dev/null +++ b/src/core/net/custom_url_loader_factory.cpp @@ -0,0 +1,446 @@ +/**************************************************************************** +** +** 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_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/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::PostTaskWithTraits(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 { } + void ProceedWithResponse() 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 (m_request.headers.GetHeader(net::HttpRequestHeaders::kRange, &rangeHeader)) { + std::vector<net::HttpByteRange> ranges; + if (net::HttpUtil::ParseRangeHeader(rangeHeader, &ranges)) { + // Chromium doesn't support multiple range requests in one single URL request. + if (ranges.size() == 1) + m_firstBytePosition = ranges[0].first_byte_position(); + } + } + +// m_taskRunner->PostTask(FROM_HERE, + base::PostTaskWithTraits(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::PostTaskWithTraits(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_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 { + headers += "HTTP/1.1 200 OK\n"; + if (m_mimeType.size() > 0) { + headers += base::StringPrintf("Content-Type: %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, m_request.top_frame_origin, + 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)); + + readAvailableData(); + if (m_device) { + 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(); + } + void 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; // Wait for pipe watcher + if (beginResult != MOJO_RESULT_OK) + break; + + 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()) { + OnTransferComplete(MOJO_RESULT_OK); + return; + } + + if (readResult == 0) + return; // Wait for readyRead + if (readResult < 0) + break; + } + + CompleteWithFailure(m_error ? net::Error(m_error) : net::ERR_FAILED); + } + 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; + + 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(network::mojom::URLLoaderRequest loader, + int32_t routing_id, + int32_t request_id, + uint32_t options, + const network::ResourceRequest &request, + network::mojom::URLLoaderClientPtr 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), client.PassInterface(), + m_profileAdapter)); + + } + + void Clone(network::mojom::URLLoaderFactoryRequest request) override + { + m_bindings.AddBinding(this, std::move(request)); + } + + const scoped_refptr<base::SequencedTaskRunner> m_taskRunner; + mojo::BindingSet<network::mojom::URLLoaderFactory> m_bindings; + 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 + |