| // Copyright 2017 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. |
| |
| #include "content/browser/appcache/appcache_subresource_url_factory.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/check_op.h" |
| #include "base/debug/crash_logging.h" |
| #include "content/browser/appcache/appcache_host.h" |
| #include "content/browser/appcache/appcache_request.h" |
| #include "content/browser/appcache/appcache_request_handler.h" |
| #include "content/browser/loader/navigation_url_loader_impl.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/common/content_client.h" |
| #include "mojo/public/cpp/bindings/message.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/network/public/cpp/request_mode.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/mojom/url_loader_factory.mojom.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // URLLoader implementation that utilizes either a network loader |
| // or an appcache loader depending on where the resources should |
| // be loaded from. This class binds to the remote client in the |
| // renderer and internally creates one or the other kind of loader. |
| // The URLLoader and URLLoaderClient interfaces are proxied between |
| // the remote consumer and the chosen internal loader. |
| // |
| // This class owns and scopes the lifetime of the AppCacheRequestHandler |
| // for the duration of a subresource load. |
| class SubresourceLoader : public network::mojom::URLLoader, |
| public network::mojom::URLLoaderClient { |
| public: |
| SubresourceLoader( |
| mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver, |
| int32_t routing_id, |
| int32_t request_id, |
| uint32_t options, |
| const network::ResourceRequest& request, |
| mojo::PendingRemote<network::mojom::URLLoaderClient> client, |
| const net::MutableNetworkTrafficAnnotationTag& annotation, |
| base::WeakPtr<AppCacheHost> appcache_host, |
| scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory) |
| : remote_receiver_(this, std::move(url_loader_receiver)), |
| remote_client_(std::move(client)), |
| request_(request), |
| routing_id_(routing_id), |
| request_id_(request_id), |
| options_(options), |
| traffic_annotation_(annotation), |
| network_loader_factory_(std::move(network_loader_factory)), |
| host_(appcache_host) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| remote_receiver_.set_disconnect_handler(base::BindOnce( |
| &SubresourceLoader::OnMojoDisconnect, base::Unretained(this))); |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&SubresourceLoader::Start, weak_factory_.GetWeakPtr())); |
| } |
| |
| private: |
| ~SubresourceLoader() override = default; |
| |
| void OnMojoDisconnect() { delete this; } |
| |
| void Start() { |
| if (!host_) { |
| remote_client_->OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_FAILED)); |
| return; |
| } |
| handler_ = host_->CreateRequestHandler( |
| std::make_unique<AppCacheRequest>(request_), request_.destination, |
| request_.should_reset_appcache); |
| if (!handler_) { |
| CreateAndStartNetworkLoader(); |
| return; |
| } |
| handler_->MaybeCreateSubresourceLoader( |
| request_, base::BindOnce(&SubresourceLoader::ContinueStart, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ContinueStart(SingleRequestURLLoaderFactory::RequestHandler handler) { |
| if (handler) |
| CreateAndStartAppCacheLoader(std::move(handler)); |
| else |
| CreateAndStartNetworkLoader(); |
| } |
| |
| void CreateAndStartAppCacheLoader( |
| SingleRequestURLLoaderFactory::RequestHandler handler) { |
| DCHECK(!appcache_loader_) << "only expected to be called onced"; |
| DCHECK(handler); |
| |
| // Disconnect from the network loader first. |
| local_client_receiver_.reset(); |
| network_loader_.reset(); |
| |
| std::move(handler).Run(request_, |
| appcache_loader_.BindNewPipeAndPassReceiver(), |
| local_client_receiver_.BindNewPipeAndPassRemote()); |
| } |
| |
| void CreateAndStartNetworkLoader() { |
| DCHECK(!appcache_loader_); |
| network_loader_factory_->CreateLoaderAndStart( |
| network_loader_.BindNewPipeAndPassReceiver(), routing_id_, request_id_, |
| options_, request_, local_client_receiver_.BindNewPipeAndPassRemote(), |
| traffic_annotation_); |
| if (has_set_priority_) |
| network_loader_->SetPriority(priority_, intra_priority_value_); |
| if (has_paused_reading_) |
| network_loader_->PauseReadingBodyFromNet(); |
| } |
| |
| // network::mojom::URLLoader implementation |
| // Called by the remote client in the renderer. |
| void FollowRedirect( |
| const std::vector<std::string>& removed_headers, |
| const net::HttpRequestHeaders& modified_headers, |
| const net::HttpRequestHeaders& modified_cors_exempt_headers, |
| const base::Optional<GURL>& new_url) override { |
| DCHECK(modified_headers.IsEmpty() && modified_cors_exempt_headers.IsEmpty()) |
| << "Redirect with modified headers was not supported yet. " |
| "crbug.com/845683"; |
| if (!handler_) { |
| network_loader_->FollowRedirect( |
| removed_headers, {} /* modified_headers */, |
| {} /* modified_cors_exempt_headers */, base::nullopt /* new_url */); |
| return; |
| } |
| DCHECK(network_loader_); |
| DCHECK(!appcache_loader_); |
| handler_->MaybeFollowSubresourceRedirect( |
| redirect_info_, |
| base::BindOnce(&SubresourceLoader::ContinueFollowRedirect, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| // network::mojom::URLLoader implementation |
| void ContinueFollowRedirect( |
| SingleRequestURLLoaderFactory::RequestHandler handler) { |
| if (handler) { |
| CreateAndStartAppCacheLoader(std::move(handler)); |
| } else { |
| network_loader_->FollowRedirect( |
| {} /* removed_headers */, {} /* modified_headers */, |
| {} /* modified_cors_exempt_headers */, base::nullopt /* new_url */); |
| } |
| } |
| |
| void SetPriority(net::RequestPriority priority, |
| int32_t intra_priority_value) override { |
| has_set_priority_ = true; |
| priority_ = priority; |
| intra_priority_value_ = intra_priority_value; |
| if (network_loader_) |
| network_loader_->SetPriority(priority, intra_priority_value); |
| } |
| |
| void PauseReadingBodyFromNet() override { |
| has_paused_reading_ = true; |
| if (network_loader_) |
| network_loader_->PauseReadingBodyFromNet(); |
| } |
| |
| void ResumeReadingBodyFromNet() override { |
| has_paused_reading_ = false; |
| if (network_loader_) |
| network_loader_->ResumeReadingBodyFromNet(); |
| } |
| |
| // network::mojom::URLLoaderClient implementation |
| // Called by either the appcache or network loader, whichever is in use. |
| void OnReceiveResponse( |
| network::mojom::URLResponseHeadPtr response_head) override { |
| // Don't MaybeFallback for appcache produced responses. |
| if (appcache_loader_ || !handler_) { |
| remote_client_->OnReceiveResponse(std::move(response_head)); |
| return; |
| } |
| |
| did_receive_network_response_ = true; |
| auto response_head_clone = response_head.Clone(); |
| handler_->MaybeFallbackForSubresourceResponse( |
| std::move(response_head), |
| base::BindOnce(&SubresourceLoader::ContinueOnReceiveResponse, |
| weak_factory_.GetWeakPtr(), |
| std::move(response_head_clone))); |
| } |
| |
| void ContinueOnReceiveResponse( |
| network::mojom::URLResponseHeadPtr response_head, |
| SingleRequestURLLoaderFactory::RequestHandler handler) { |
| if (handler) { |
| CreateAndStartAppCacheLoader(std::move(handler)); |
| } else { |
| remote_client_->OnReceiveResponse(std::move(response_head)); |
| } |
| } |
| |
| void OnReceiveRedirect( |
| const net::RedirectInfo& redirect_info, |
| network::mojom::URLResponseHeadPtr response_head) override { |
| DCHECK(network_loader_) << "appcache loader does not produce redirects"; |
| if (!redirect_limit_--) { |
| OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_TOO_MANY_REDIRECTS)); |
| return; |
| } |
| if (!handler_) { |
| remote_client_->OnReceiveRedirect(redirect_info_, |
| std::move(response_head)); |
| return; |
| } |
| redirect_info_ = redirect_info; |
| handler_->MaybeFallbackForSubresourceRedirect( |
| redirect_info, |
| base::BindOnce(&SubresourceLoader::ContinueOnReceiveRedirect, |
| weak_factory_.GetWeakPtr(), std::move(response_head))); |
| } |
| |
| void ContinueOnReceiveRedirect( |
| network::mojom::URLResponseHeadPtr response_head, |
| SingleRequestURLLoaderFactory::RequestHandler handler) { |
| if (handler) { |
| CreateAndStartAppCacheLoader(std::move(handler)); |
| } else { |
| remote_client_->OnReceiveRedirect(redirect_info_, |
| std::move(response_head)); |
| } |
| } |
| |
| void OnUploadProgress(int64_t current_position, |
| int64_t total_size, |
| OnUploadProgressCallback ack_callback) override { |
| remote_client_->OnUploadProgress(current_position, total_size, |
| std::move(ack_callback)); |
| } |
| |
| void OnReceiveCachedMetadata(mojo_base::BigBuffer data) override { |
| remote_client_->OnReceiveCachedMetadata(std::move(data)); |
| } |
| |
| void OnTransferSizeUpdated(int32_t transfer_size_diff) override { |
| remote_client_->OnTransferSizeUpdated(transfer_size_diff); |
| } |
| |
| void OnStartLoadingResponseBody( |
| mojo::ScopedDataPipeConsumerHandle body) override { |
| remote_client_->OnStartLoadingResponseBody(std::move(body)); |
| } |
| |
| void OnComplete(const network::URLLoaderCompletionStatus& status) override { |
| if (!network_loader_ || !handler_ || did_receive_network_response_ || |
| status.error_code == net::OK) { |
| remote_client_->OnComplete(status); |
| return; |
| } |
| handler_->MaybeFallbackForSubresourceResponse( |
| network::mojom::URLResponseHead::New(), |
| base::BindOnce(&SubresourceLoader::ContinueOnComplete, |
| weak_factory_.GetWeakPtr(), status)); |
| } |
| |
| void ContinueOnComplete( |
| const network::URLLoaderCompletionStatus& status, |
| SingleRequestURLLoaderFactory::RequestHandler handler) { |
| if (handler) |
| CreateAndStartAppCacheLoader(std::move(handler)); |
| else |
| remote_client_->OnComplete(status); |
| } |
| |
| // The receiver and remote client associated with the renderer. |
| mojo::Receiver<network::mojom::URLLoader> remote_receiver_; |
| mojo::Remote<network::mojom::URLLoaderClient> remote_client_; |
| |
| network::ResourceRequest request_; |
| int32_t routing_id_; |
| int32_t request_id_; |
| uint32_t options_; |
| net::MutableNetworkTrafficAnnotationTag traffic_annotation_; |
| scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory_; |
| net::RedirectInfo redirect_info_; |
| int redirect_limit_ = net::URLRequest::kMaxRedirects; |
| bool did_receive_network_response_ = false; |
| bool has_paused_reading_ = false; |
| bool has_set_priority_ = false; |
| net::RequestPriority priority_; |
| int32_t intra_priority_value_; |
| |
| // Core appcache logic that decides how to handle a request. |
| std::unique_ptr<AppCacheRequestHandler> handler_; |
| |
| // The local receiver to either our network or appcache loader, |
| // we only use one of them at any given time. |
| mojo::Receiver<network::mojom::URLLoaderClient> local_client_receiver_{this}; |
| mojo::Remote<network::mojom::URLLoader> network_loader_; |
| mojo::Remote<network::mojom::URLLoader> appcache_loader_; |
| |
| base::WeakPtr<AppCacheHost> host_; |
| |
| base::WeakPtrFactory<SubresourceLoader> weak_factory_{this}; |
| DISALLOW_COPY_AND_ASSIGN(SubresourceLoader); |
| }; |
| |
| } // namespace |
| |
| // Implements the URLLoaderFactory mojom for AppCache requests. |
| AppCacheSubresourceURLFactory::AppCacheSubresourceURLFactory( |
| scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory, |
| base::WeakPtr<AppCacheHost> host) |
| : network_loader_factory_(std::move(network_loader_factory)), |
| appcache_host_(host) { |
| receivers_.set_disconnect_handler( |
| base::BindRepeating(&AppCacheSubresourceURLFactory::OnMojoDisconnect, |
| base::Unretained(this))); |
| } |
| |
| AppCacheSubresourceURLFactory::~AppCacheSubresourceURLFactory() = default; |
| |
| // static |
| bool AppCacheSubresourceURLFactory::CreateURLLoaderFactory( |
| base::WeakPtr<AppCacheHost> host, |
| mojo::PendingReceiver<network::mojom::URLLoaderFactory> |
| loader_factory_receiver) { |
| DCHECK(host.get()); |
| scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory; |
| // The partition has shutdown, return without binding |loader_factory|. |
| if (!host->service()->partition()) |
| return false; |
| network_loader_factory = |
| host->service() |
| ->partition() |
| ->GetURLLoaderFactoryForBrowserProcessWithCORBEnabled(); |
| // This instance is effectively reference counted by the number of pipes open |
| // to it and will get deleted when all clients drop their connections. |
| // Please see OnMojoDisconnect() for details. |
| auto* impl = new AppCacheSubresourceURLFactory( |
| std::move(network_loader_factory), host); |
| impl->Clone(std::move(loader_factory_receiver)); |
| |
| // Save the factory in the host to ensure that we don't create it again when |
| // the cache is selected, etc. |
| host->SetAppCacheSubresourceFactory(impl); |
| return true; |
| } |
| |
| void AppCacheSubresourceURLFactory::CreateLoaderAndStart( |
| mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver, |
| 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) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (request.request_initiator.has_value() && appcache_host_ && |
| !appcache_host_->security_policy_handle()->CanAccessDataForOrigin( |
| request.request_initiator.value())) { |
| static auto* initiator_origin_key = base::debug::AllocateCrashKeyString( |
| "initiator_origin", base::debug::CrashKeySize::Size64); |
| base::debug::SetCrashKeyString( |
| initiator_origin_key, request.request_initiator.value().Serialize()); |
| |
| mojo::ReportBadMessage( |
| "APPCACHE_SUBRESOURCE_URL_FACTORY_INVALID_INITIATOR"); |
| return; |
| } |
| |
| // Subresource requests from renderer processes should not be allowed to use |
| // network::mojom::FetchRequestMode::kNavigate. |
| if (request.mode == network::mojom::RequestMode::kNavigate) { |
| mojo::ReportBadMessage("APPCACHE_SUBRESOURCE_URL_FACTORY_NAVIGATE"); |
| return; |
| } |
| |
| new SubresourceLoader(std::move(url_loader_receiver), routing_id, request_id, |
| options, request, std::move(client), traffic_annotation, |
| appcache_host_, network_loader_factory_); |
| } |
| |
| void AppCacheSubresourceURLFactory::Clone( |
| mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver) { |
| receivers_.Add(this, std::move(receiver)); |
| } |
| |
| base::WeakPtr<AppCacheSubresourceURLFactory> |
| AppCacheSubresourceURLFactory::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void AppCacheSubresourceURLFactory::OnMojoDisconnect() { |
| if (receivers_.empty()) |
| delete this; |
| } |
| |
| } // namespace content |