| // 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/public/test/url_loader_interceptor.h" |
| |
| #include "base/test/bind_test_util.h" |
| #include "content/browser/frame_host/render_frame_host_impl.h" |
| #include "content/browser/loader/resource_message_filter.h" |
| #include "content/browser/loader/url_loader_factory_impl.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/browser/url_loader_factory_getter.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "mojo/public/cpp/bindings/binding_set.h" |
| #include "net/http/http_util.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/mojom/url_loader.mojom.h" |
| |
| namespace content { |
| |
| class URLLoaderInterceptor::Interceptor |
| : public network::mojom::URLLoaderFactory { |
| public: |
| using ProcessIdGetter = base::Callback<int()>; |
| using OriginalFactoryGetter = |
| base::Callback<network::mojom::URLLoaderFactory*()>; |
| |
| Interceptor(URLLoaderInterceptor* parent, |
| const ProcessIdGetter& process_id_getter, |
| const OriginalFactoryGetter& original_factory_getter) |
| : parent_(parent), |
| process_id_getter_(process_id_getter), |
| original_factory_getter_(original_factory_getter) { |
| bindings_.set_connection_error_handler(base::BindRepeating( |
| &Interceptor::OnConnectionError, base::Unretained(this))); |
| } |
| |
| ~Interceptor() override {} |
| |
| void BindRequest(network::mojom::URLLoaderFactoryRequest request) { |
| bindings_.AddBinding(this, std::move(request)); |
| } |
| |
| void SetConnectionErrorHandler(base::OnceClosure handler) { |
| error_handler_ = std::move(handler); |
| } |
| |
| private: |
| // network::mojom::URLLoaderFactory implementation: |
| void CreateLoaderAndStart(network::mojom::URLLoaderRequest request, |
| int32_t routing_id, |
| int32_t request_id, |
| uint32_t options, |
| const network::ResourceRequest& url_request, |
| network::mojom::URLLoaderClientPtr client, |
| const net::MutableNetworkTrafficAnnotationTag& |
| traffic_annotation) override { |
| RequestParams params; |
| params.process_id = process_id_getter_.Run(); |
| params.request = std::move(request); |
| params.routing_id = routing_id; |
| params.request_id = request_id; |
| params.options = options; |
| params.url_request = std::move(url_request); |
| params.client = std::move(client); |
| params.traffic_annotation = traffic_annotation; |
| // We don't intercept the blob URL for plznavigate requests. |
| if (params.url_request.resource_body_stream_url.is_empty() && |
| parent_->callback_.Run(¶ms)) |
| return; |
| |
| // mock.failed.request is a special request whereby the query indicates what |
| // error code to respond with. |
| if (params.url_request.url.DomainIs("mock.failed.request")) { |
| std::string query = params.url_request.url.query(); |
| std::string error_code = query.substr(query.find("=") + 1); |
| |
| int error = 0; |
| base::StringToInt(error_code, &error); |
| network::URLLoaderCompletionStatus status; |
| status.error_code = error; |
| params.client->OnComplete(status); |
| return; |
| } |
| |
| original_factory_getter_.Run()->CreateLoaderAndStart( |
| std::move(params.request), params.routing_id, params.request_id, |
| params.options, std::move(params.url_request), std::move(params.client), |
| params.traffic_annotation); |
| } |
| |
| void Clone(network::mojom::URLLoaderFactoryRequest request) override { |
| BindRequest(std::move(request)); |
| } |
| |
| void OnConnectionError() { |
| if (bindings_.empty() && error_handler_) |
| std::move(error_handler_).Run(); |
| } |
| |
| URLLoaderInterceptor* parent_; |
| ProcessIdGetter process_id_getter_; |
| OriginalFactoryGetter original_factory_getter_; |
| mojo::BindingSet<network::mojom::URLLoaderFactory> bindings_; |
| base::OnceClosure error_handler_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Interceptor); |
| }; |
| |
| // This class intercepts calls to each StoragePartition's URLLoaderFactoryGetter |
| // so that it can intercept frame requests. |
| class URLLoaderInterceptor::URLLoaderFactoryGetterWrapper { |
| public: |
| URLLoaderFactoryGetterWrapper( |
| URLLoaderFactoryGetter* url_loader_factory_getter, |
| URLLoaderInterceptor* parent) |
| : url_loader_factory_getter_(url_loader_factory_getter) { |
| frame_interceptor_ = std::make_unique<Interceptor>( |
| parent, base::BindRepeating([]() { return 0; }), |
| base::BindLambdaForTesting([=]() -> network::mojom::URLLoaderFactory* { |
| return url_loader_factory_getter |
| ->original_network_factory_for_testing() |
| ->get(); |
| })); |
| url_loader_factory_getter_->SetNetworkFactoryForTesting( |
| frame_interceptor_.get()); |
| } |
| |
| ~URLLoaderFactoryGetterWrapper() { |
| url_loader_factory_getter_->SetNetworkFactoryForTesting(nullptr); |
| } |
| |
| private: |
| std::unique_ptr<Interceptor> frame_interceptor_; |
| URLLoaderFactoryGetter* url_loader_factory_getter_; |
| }; |
| |
| // This class intercepts calls to |
| // StoragePartition::GetURLLoaderFactoryForBrowserProcess. |
| class URLLoaderInterceptor::BrowserProcessWrapper { |
| public: |
| BrowserProcessWrapper(network::mojom::URLLoaderFactoryRequest factory_request, |
| URLLoaderInterceptor* parent, |
| network::mojom::URLLoaderFactoryPtr original_factory) |
| : interceptor_( |
| parent, |
| base::BindRepeating([]() { return 0; }), |
| base::BindRepeating(&BrowserProcessWrapper::GetOriginalFactory, |
| base::Unretained(this))), |
| original_factory_(std::move(original_factory)) { |
| interceptor_.BindRequest(std::move(factory_request)); |
| } |
| |
| ~BrowserProcessWrapper() {} |
| |
| private: |
| network::mojom::URLLoaderFactory* GetOriginalFactory() { |
| return original_factory_.get(); |
| } |
| |
| Interceptor interceptor_; |
| network::mojom::URLLoaderFactoryPtr original_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BrowserProcessWrapper); |
| }; |
| |
| // This class is sent along a RenderFrame commit message as a subresource |
| // loader so that it can intercept subresource requests. |
| class URLLoaderInterceptor::SubresourceWrapper { |
| public: |
| SubresourceWrapper(network::mojom::URLLoaderFactoryRequest factory_request, |
| int process_id, |
| URLLoaderInterceptor* parent, |
| network::mojom::URLLoaderFactoryPtrInfo original_factory) |
| : interceptor_( |
| parent, |
| base::BindRepeating([](int process_id) { return process_id; }, |
| process_id), |
| base::BindRepeating(&SubresourceWrapper::GetOriginalFactory, |
| base::Unretained(this))), |
| original_factory_(std::move(original_factory)) { |
| interceptor_.BindRequest(std::move(factory_request)); |
| interceptor_.SetConnectionErrorHandler( |
| base::BindOnce(&URLLoaderInterceptor::SubresourceWrapperBindingError, |
| base::Unretained(parent), this)); |
| } |
| |
| ~SubresourceWrapper() {} |
| |
| private: |
| network::mojom::URLLoaderFactory* GetOriginalFactory() { |
| return original_factory_.get(); |
| } |
| |
| Interceptor interceptor_; |
| network::mojom::URLLoaderFactoryPtr original_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SubresourceWrapper); |
| }; |
| |
| URLLoaderInterceptor::RequestParams::RequestParams() = default; |
| URLLoaderInterceptor::RequestParams::~RequestParams() = default; |
| URLLoaderInterceptor::RequestParams::RequestParams(RequestParams&& other) = |
| default; |
| URLLoaderInterceptor::RequestParams& URLLoaderInterceptor::RequestParams:: |
| operator=(RequestParams&& other) = default; |
| |
| URLLoaderInterceptor::URLLoaderInterceptor(const InterceptCallback& callback) |
| : callback_(callback) { |
| DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| RenderFrameHostImpl::SetNetworkFactoryForTesting(base::BindRepeating( |
| &URLLoaderInterceptor::CreateURLLoaderFactoryForSubresources, |
| base::Unretained(this))); |
| } |
| |
| StoragePartitionImpl:: |
| SetGetURLLoaderFactoryForBrowserProcessCallbackForTesting( |
| base::BindRepeating( |
| &URLLoaderInterceptor::GetURLLoaderFactoryForBrowserProcess, |
| base::Unretained(this))); |
| |
| if (BrowserThread::IsThreadInitialized(BrowserThread::IO)) { |
| base::RunLoop run_loop; |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&URLLoaderInterceptor::InitializeOnIOThread, |
| base::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| } else { |
| InitializeOnIOThread(base::OnceClosure()); |
| } |
| } |
| |
| URLLoaderInterceptor::~URLLoaderInterceptor() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| RenderFrameHostImpl::SetNetworkFactoryForTesting( |
| RenderFrameHostImpl::CreateNetworkFactoryCallback()); |
| } |
| |
| StoragePartitionImpl:: |
| SetGetURLLoaderFactoryForBrowserProcessCallbackForTesting( |
| StoragePartitionImpl::CreateNetworkFactoryCallback()); |
| |
| base::RunLoop run_loop; |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&URLLoaderInterceptor::ShutdownOnIOThread, |
| base::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| void URLLoaderInterceptor::WriteResponse( |
| const std::string& headers, |
| const std::string& body, |
| network::mojom::URLLoaderClient* client) { |
| net::HttpResponseInfo info; |
| info.headers = new net::HttpResponseHeaders( |
| net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.length())); |
| network::ResourceResponseHead response; |
| response.headers = info.headers; |
| response.headers->GetMimeType(&response.mime_type); |
| client->OnReceiveResponse(response, base::nullopt, nullptr); |
| |
| uint32_t bytes_written = body.size(); |
| mojo::DataPipe data_pipe; |
| CHECK_EQ(MOJO_RESULT_OK, |
| data_pipe.producer_handle->WriteData( |
| body.data(), &bytes_written, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); |
| client->OnStartLoadingResponseBody(std::move(data_pipe.consumer_handle)); |
| |
| network::URLLoaderCompletionStatus status; |
| status.decoded_body_length = body.size(); |
| status.error_code = net::OK; |
| client->OnComplete(status); |
| } |
| |
| void URLLoaderInterceptor::CreateURLLoaderFactoryForSubresources( |
| network::mojom::URLLoaderFactoryRequest request, |
| int process_id, |
| network::mojom::URLLoaderFactoryPtrInfo original_factory) { |
| if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &URLLoaderInterceptor::CreateURLLoaderFactoryForSubresources, |
| base::Unretained(this), base::Passed(&request), process_id, |
| base::Passed(&original_factory))); |
| return; |
| } |
| |
| subresource_wrappers_.emplace(std::make_unique<SubresourceWrapper>( |
| std::move(request), process_id, this, std::move(original_factory))); |
| } |
| |
| network::mojom::URLLoaderFactoryPtr |
| URLLoaderInterceptor::GetURLLoaderFactoryForBrowserProcess( |
| network::mojom::URLLoaderFactoryPtr original_factory) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| network::mojom::URLLoaderFactoryPtr loader_factory; |
| browser_process_interceptors_.emplace(std::make_unique<BrowserProcessWrapper>( |
| mojo::MakeRequest(&loader_factory), this, std::move(original_factory))); |
| return loader_factory; |
| } |
| |
| void URLLoaderInterceptor::GetNetworkFactoryCallback( |
| URLLoaderFactoryGetter* url_loader_factory_getter) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| url_loader_factory_getter_wrappers_.emplace( |
| std::make_unique<URLLoaderFactoryGetterWrapper>(url_loader_factory_getter, |
| this)); |
| } |
| |
| void URLLoaderInterceptor::SubresourceWrapperBindingError( |
| SubresourceWrapper* wrapper) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| for (auto& it : subresource_wrappers_) { |
| if (it.get() == wrapper) { |
| subresource_wrappers_.erase(it); |
| return; |
| } |
| } |
| |
| NOTREACHED(); |
| } |
| |
| void URLLoaderInterceptor::InitializeOnIOThread(base::OnceClosure closure) { |
| // Once http://crbug.com/747130 is fixed, the codepath above will work for |
| // the non-network service code path. |
| if (base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| URLLoaderFactoryGetter::SetGetNetworkFactoryCallbackForTesting( |
| base::BindRepeating(&URLLoaderInterceptor::GetNetworkFactoryCallback, |
| base::Unretained(this))); |
| } |
| |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| rmf_interceptor_ = std::make_unique<Interceptor>( |
| this, base::BindRepeating([]() { |
| return ResourceMessageFilter::GetCurrentForTesting()->child_id(); |
| }), |
| base::BindRepeating([]() { |
| network::mojom::URLLoaderFactory* factory = |
| ResourceMessageFilter::GetCurrentForTesting(); |
| return factory; |
| })); |
| ResourceMessageFilter::SetNetworkFactoryForTesting(rmf_interceptor_.get()); |
| } |
| |
| if (closure) |
| std::move(closure).Run(); |
| } |
| |
| void URLLoaderInterceptor::ShutdownOnIOThread(base::OnceClosure closure) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| url_loader_factory_getter_wrappers_.clear(); |
| subresource_wrappers_.clear(); |
| |
| if (base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| URLLoaderFactoryGetter::SetGetNetworkFactoryCallbackForTesting( |
| URLLoaderFactoryGetter::GetNetworkFactoryCallback()); |
| } |
| |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| ResourceMessageFilter::SetNetworkFactoryForTesting(nullptr); |
| } |
| |
| std::move(closure).Run(); |
| } |
| |
| } // namespace content |