// Copyright (c) 2019 The Chromium Embedded Framework Authors. Portions // Copyright (c) 2018 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 "libcef/browser/net_service/proxy_url_loader_factory.h" #include "libcef/browser/context.h" #include "libcef/browser/origin_whitelist_impl.h" #include "libcef/browser/thread_util.h" #include "libcef/common/net/scheme_registration.h" #include "libcef/common/net_service/net_service_util.h" #include "base/barrier_closure.h" #include "base/strings/string_number_conversions.h" #include "components/safe_browsing/core/common/safebrowsing_constants.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/resource_context.h" #include "content/public/browser/web_contents.h" #include "mojo/public/cpp/base/big_buffer.h" #include "net/http/http_status_code.h" #include "services/network/public/cpp/cors/cors.h" #include "services/network/public/cpp/features.h" namespace net_service { namespace { // User data key for ResourceContextData. const void* const kResourceContextUserDataKey = &kResourceContextUserDataKey; base::Optional GetHeaderString( const net::HttpResponseHeaders* headers, const std::string& header_name) { std::string header_value; if (!headers || !headers->GetNormalizedHeader(header_name, &header_value)) { return base::nullopt; } return header_value; } bool IsOutOfBlinkCorsEnabled() { static int state = -1; if (state == -1) { state = base::FeatureList::IsEnabled(network::features::kOutOfBlinkCors) ? 1 : 0; } return !!state; } } // namespace // Owns all of the ProxyURLLoaderFactorys for a given BrowserContext. Since // these live on the IO thread this is done indirectly through the // ResourceContext. class ResourceContextData : public base::SupportsUserData::Data { public: ~ResourceContextData() override {} static void AddProxyOnUIThread( ProxyURLLoaderFactory* proxy, content::WebContents::Getter web_contents_getter) { CEF_REQUIRE_UIT(); content::WebContents* web_contents = web_contents_getter.Run(); // Maybe the browser was destroyed while AddProxyOnUIThread was pending. if (!web_contents) { // Delete on the IO thread as expected by mojo bindings. content::BrowserThread::DeleteSoon(content::BrowserThread::IO, FROM_HERE, proxy); return; } content::BrowserContext* browser_context = web_contents->GetBrowserContext(); DCHECK(browser_context); content::ResourceContext* resource_context = browser_context->GetResourceContext(); DCHECK(resource_context); CEF_POST_TASK(CEF_IOT, base::BindOnce(ResourceContextData::AddProxy, base::Unretained(proxy), base::Unretained(resource_context))); } static void AddProxy(ProxyURLLoaderFactory* proxy, content::ResourceContext* resource_context) { CEF_REQUIRE_IOT(); // Maybe the proxy was destroyed while AddProxyOnUIThread was pending. if (proxy->destroyed_) { delete proxy; return; } auto* self = static_cast( resource_context->GetUserData(kResourceContextUserDataKey)); if (!self) { self = new ResourceContextData(); resource_context->SetUserData(kResourceContextUserDataKey, base::WrapUnique(self)); } proxy->SetDisconnectCallback(base::BindOnce( &ResourceContextData::RemoveProxy, self->weak_factory_.GetWeakPtr())); self->proxies_.emplace(base::WrapUnique(proxy)); } private: void RemoveProxy(ProxyURLLoaderFactory* proxy) { CEF_REQUIRE_IOT(); auto it = proxies_.find(proxy); DCHECK(it != proxies_.end()); proxies_.erase(it); } ResourceContextData() : weak_factory_(this) {} std::set, base::UniquePtrComparator> proxies_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(ResourceContextData); }; //============================== // InterceptedRequest //============================= // Handles intercepted, in-progress requests/responses, so that they can be // controlled and modified accordingly. class InterceptedRequest : public network::mojom::URLLoader, public network::mojom::URLLoaderClient, public network::mojom::TrustedHeaderClient { public: InterceptedRequest( ProxyURLLoaderFactory* factory, RequestId id, uint32_t options, const network::ResourceRequest& request, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, mojo::PendingReceiver loader_receiver, mojo::PendingRemote client, mojo::PendingRemote target_factory); ~InterceptedRequest() override; // Restart the request. This happens on initial start and after redirect. void Restart(); // Called from ProxyURLLoaderFactory::OnLoaderCreated. void OnLoaderCreated( mojo::PendingReceiver receiver); // Called from InterceptDelegate::OnInputStreamOpenFailed. void InputStreamFailed(bool restart_needed); // mojom::TrustedHeaderClient methods: void OnBeforeSendHeaders(const net::HttpRequestHeaders& headers, OnBeforeSendHeadersCallback callback) override; void OnHeadersReceived(const std::string& headers, const net::IPEndPoint& remote_endpoint, OnHeadersReceivedCallback callback) override; // mojom::URLLoaderClient methods: void OnReceiveResponse(network::mojom::URLResponseHeadPtr head) override; void OnReceiveRedirect(const net::RedirectInfo& redirect_info, network::mojom::URLResponseHeadPtr head) override; void OnUploadProgress(int64_t current_position, int64_t total_size, OnUploadProgressCallback callback) override; void OnReceiveCachedMetadata(mojo_base::BigBuffer data) override; void OnTransferSizeUpdated(int32_t transfer_size_diff) override; void OnStartLoadingResponseBody( mojo::ScopedDataPipeConsumerHandle body) override; void OnComplete(const network::URLLoaderCompletionStatus& status) override; // mojom::URLLoader methods: void FollowRedirect( const std::vector& removed_headers, const net::HttpRequestHeaders& modified_headers, const net::HttpRequestHeaders& modified_cors_exempt_headers, const base::Optional& new_url) override; void SetPriority(net::RequestPriority priority, int32_t intra_priority_value) override; void PauseReadingBodyFromNet() override; void ResumeReadingBodyFromNet() override; const RequestId id() const { return id_; } private: // Helpers for determining the request handler. void BeforeRequestReceived(const GURL& original_url, bool intercept_request, bool intercept_only); void InterceptResponseReceived(const GURL& original_url, std::unique_ptr response); void ContinueAfterIntercept(); void ContinueAfterInterceptWithOverride( std::unique_ptr response); // Helpers for optionally overriding headers. void HandleResponseOrRedirectHeaders( base::Optional redirect_info, net::CompletionOnceCallback continuation); void ContinueResponseOrRedirect( net::CompletionOnceCallback continuation, InterceptedRequestHandler::ResponseMode response_mode, scoped_refptr override_headers, const GURL& redirect_url); void ContinueToHandleOverrideHeaders(int error_code); net::RedirectInfo MakeRedirectResponseAndInfo(const GURL& new_location); // Helpers for redirect handling. void ContinueToBeforeRedirect(const net::RedirectInfo& redirect_info, int error_code); // Helpers for response handling. void ContinueToResponseStarted(int error_code); void OnDestroy(); void OnProcessRequestHeaders(const GURL& redirect_url, net::HttpRequestHeaders* modified_headers, std::vector* removed_headers); // This is called when the original URLLoaderClient has a connection error. void OnURLLoaderClientError(); // This is called when the original URLLoader has a connection error. void OnURLLoaderError(uint32_t custom_reason, const std::string& description); // Call OnComplete on |target_client_|. If |wait_for_loader_error| is true // then this object will wait for |proxied_loader_binding_| to have a // connection error before destructing. void CallOnComplete(const network::URLLoaderCompletionStatus& status, bool wait_for_loader_error); void SendErrorAndCompleteImmediately(int error_code); void SendErrorStatusAndCompleteImmediately( const network::URLLoaderCompletionStatus& status); void SendErrorCallback(int error_code, bool safebrowsing_hit); void OnUploadProgressACK(); ProxyURLLoaderFactory* const factory_; const RequestId id_; const uint32_t options_; bool input_stream_previously_failed_ = false; bool request_was_redirected_ = false; int redirect_limit_ = net::URLRequest::kMaxRedirects; // To avoid sending multiple OnReceivedError callbacks. bool sent_error_callback_ = false; // When true, the loader will provide the option to intercept the request. bool intercept_request_ = true; // When true, the loader will not proceed unless the intercept request // callback provided a non-null response. bool intercept_only_ = false; network::URLLoaderCompletionStatus status_; bool got_loader_error_ = false; // Used for rate limiting OnUploadProgress callbacks. bool waiting_for_upload_progress_ack_ = false; network::ResourceRequest request_; network::mojom::URLResponseHeadPtr current_response_; scoped_refptr current_headers_; scoped_refptr override_headers_; GURL original_url_; GURL redirect_url_; GURL header_client_redirect_url_; const net::MutableNetworkTrafficAnnotationTag traffic_annotation_; mojo::Binding proxied_loader_binding_; network::mojom::URLLoaderClientPtr target_client_; mojo::Binding proxied_client_binding_; network::mojom::URLLoaderPtr target_loader_; network::mojom::URLLoaderFactoryPtr target_factory_; bool current_request_uses_header_client_ = false; OnHeadersReceivedCallback on_headers_received_callback_; mojo::Receiver header_client_receiver_{ this}; StreamReaderURLLoader* stream_loader_ = nullptr; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(InterceptedRequest); }; class InterceptDelegate : public StreamReaderURLLoader::Delegate { public: explicit InterceptDelegate(std::unique_ptr response, base::WeakPtr request) : response_(std::move(response)), request_(request) {} bool OpenInputStream(const RequestId& request_id, const network::ResourceRequest& request, OpenCallback callback) override { return response_->OpenInputStream(request_id, request, std::move(callback)); } void OnInputStreamOpenFailed(const RequestId& request_id, bool* restarted) override { request_->InputStreamFailed(false /* restart_needed */); *restarted = false; } void GetResponseHeaders(const RequestId& request_id, int* status_code, std::string* reason_phrase, std::string* mime_type, std::string* charset, int64_t* content_length, HeaderMap* extra_headers) override { response_->GetResponseHeaders(request_id, status_code, reason_phrase, mime_type, charset, content_length, extra_headers); } private: std::unique_ptr response_; base::WeakPtr request_; }; InterceptedRequest::InterceptedRequest( ProxyURLLoaderFactory* factory, RequestId id, uint32_t options, const network::ResourceRequest& request, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, mojo::PendingReceiver loader_receiver, mojo::PendingRemote client, mojo::PendingRemote target_factory) : factory_(factory), id_(id), options_(options), request_(request), traffic_annotation_(traffic_annotation), proxied_loader_binding_(this, std::move(loader_receiver)), target_client_(std::move(client)), proxied_client_binding_(this), target_factory_(std::move(target_factory)), weak_factory_(this) { status_ = network::URLLoaderCompletionStatus(net::OK); net::HttpRequestHeaders modified_headers; std::vector removed_headers; OnProcessRequestHeaders(GURL() /* redirect_url */, &modified_headers, &removed_headers); // If there is a client error, clean up the request. target_client_.set_connection_error_handler(base::BindOnce( &InterceptedRequest::OnURLLoaderClientError, base::Unretained(this))); proxied_loader_binding_.set_connection_error_with_reason_handler( base::BindOnce(&InterceptedRequest::OnURLLoaderError, base::Unretained(this))); } InterceptedRequest::~InterceptedRequest() { if (status_.error_code != net::OK) SendErrorCallback(status_.error_code, false); if (on_headers_received_callback_) { std::move(on_headers_received_callback_) .Run(net::ERR_ABORTED, base::nullopt, GURL()); } } void InterceptedRequest::Restart() { stream_loader_ = nullptr; if (proxied_client_binding_.is_bound()) { proxied_client_binding_.Unbind(); target_loader_.reset(); } if (header_client_receiver_.is_bound()) ignore_result(header_client_receiver_.Unbind()); current_request_uses_header_client_ = !!factory_->url_loader_header_client_receiver_; if (IsOutOfBlinkCorsEnabled() && request_.request_initiator && network::cors::ShouldCheckCors(request_.url, request_.request_initiator, request_.mode)) { if (scheme::IsCorsEnabledScheme(request_.url.scheme())) { // Add the Origin header for CORS-enabled scheme requests. request_.headers.SetHeaderIfMissing( net::HttpRequestHeaders::kOrigin, request_.request_initiator->Serialize()); } else if (!HasCrossOriginWhitelistEntry( *request_.request_initiator, url::Origin::Create(request_.url))) { // Fail requests if a CORS check is required and the scheme is not CORS // enabled. This matches the error condition that would be generated by // CorsURLLoader::StartRequest in the network process. SendErrorStatusAndCompleteImmediately( network::URLLoaderCompletionStatus(network::CorsErrorStatus( network::mojom::CorsError::kCorsDisabledScheme))); return; } } const GURL original_url = request_.url; factory_->request_handler_->OnBeforeRequest( id_, &request_, request_was_redirected_, base::BindOnce(&InterceptedRequest::BeforeRequestReceived, weak_factory_.GetWeakPtr(), original_url), base::BindOnce(&InterceptedRequest::SendErrorAndCompleteImmediately, weak_factory_.GetWeakPtr())); } void InterceptedRequest::OnLoaderCreated( mojo::PendingReceiver receiver) { DCHECK(current_request_uses_header_client_); // Only called if we're using the default loader. header_client_receiver_.Bind(std::move(receiver)); } void InterceptedRequest::InputStreamFailed(bool restart_needed) { DCHECK(!input_stream_previously_failed_); if (intercept_only_) { // This can happen for unsupported schemes, when no proper // response from the intercept handler is received, i.e. // the provided input stream in response failed to load. In // this case we send and error and stop loading. SendErrorAndCompleteImmediately(net::ERR_UNKNOWN_URL_SCHEME); return; } if (!restart_needed) return; input_stream_previously_failed_ = true; Restart(); } // TrustedHeaderClient methods. void InterceptedRequest::OnBeforeSendHeaders( const net::HttpRequestHeaders& headers, OnBeforeSendHeadersCallback callback) { if (!current_request_uses_header_client_) { std::move(callback).Run(net::OK, base::nullopt); return; } request_.headers = headers; std::move(callback).Run(net::OK, base::nullopt); // Resume handling of client messages after continuing from an async callback. if (proxied_client_binding_) proxied_client_binding_.ResumeIncomingMethodCallProcessing(); } void InterceptedRequest::OnHeadersReceived( const std::string& headers, const net::IPEndPoint& remote_endpoint, OnHeadersReceivedCallback callback) { if (!current_request_uses_header_client_) { std::move(callback).Run(net::OK, base::nullopt, GURL()); return; } current_headers_ = base::MakeRefCounted(headers); on_headers_received_callback_ = std::move(callback); base::Optional redirect_info; std::string location; if (current_headers_->IsRedirect(&location)) { const GURL new_url = request_.url.Resolve(location); redirect_info = MakeRedirectInfo(request_, current_headers_.get(), new_url, 0); } HandleResponseOrRedirectHeaders( redirect_info, base::BindOnce(&InterceptedRequest::ContinueToHandleOverrideHeaders, weak_factory_.GetWeakPtr())); } // URLLoaderClient methods. void InterceptedRequest::OnReceiveResponse( network::mojom::URLResponseHeadPtr head) { current_response_ = std::move(head); if (current_request_uses_header_client_) { // Use the headers we got from OnHeadersReceived as that'll contain // Set-Cookie if it existed. DCHECK(current_headers_); current_response_->headers = current_headers_; current_headers_ = nullptr; ContinueToResponseStarted(net::OK); } else { HandleResponseOrRedirectHeaders( base::nullopt, base::BindOnce(&InterceptedRequest::ContinueToResponseStarted, weak_factory_.GetWeakPtr())); } } void InterceptedRequest::OnReceiveRedirect( const net::RedirectInfo& redirect_info, network::mojom::URLResponseHeadPtr head) { bool needs_callback = false; current_response_ = std::move(head); if (current_request_uses_header_client_) { // Use the headers we got from OnHeadersReceived as that'll contain // Set-Cookie if it existed. May be null for synthetic redirects. if (current_headers_) { current_response_->headers = current_headers_; current_headers_ = nullptr; } } else { needs_callback = true; } if (--redirect_limit_ == 0) { SendErrorAndCompleteImmediately(net::ERR_TOO_MANY_REDIRECTS); return; } net::RedirectInfo new_redirect_info; // When we redirect via ContinueToHandleOverrideHeaders the |redirect_info| // value is sometimes nonsense (HTTP_OK). Also, we won't get another call to // OnHeadersReceived for the new URL so we need to execute the callback here. if (header_client_redirect_url_.is_valid() && redirect_info.status_code == net::HTTP_OK) { DCHECK(current_request_uses_header_client_); needs_callback = true; new_redirect_info = MakeRedirectResponseAndInfo(header_client_redirect_url_); } else { new_redirect_info = redirect_info; } if (needs_callback) { HandleResponseOrRedirectHeaders( new_redirect_info, base::BindOnce(&InterceptedRequest::ContinueToBeforeRedirect, weak_factory_.GetWeakPtr(), new_redirect_info)); } else { ContinueToBeforeRedirect(new_redirect_info, net::OK); } } void InterceptedRequest::OnUploadProgress(int64_t current_position, int64_t total_size, OnUploadProgressCallback callback) { // Implement our own rate limiting for OnUploadProgress calls. if (!waiting_for_upload_progress_ack_) { waiting_for_upload_progress_ack_ = true; target_client_->OnUploadProgress( current_position, total_size, base::BindOnce(&InterceptedRequest::OnUploadProgressACK, weak_factory_.GetWeakPtr())); } // Always execute the callback immediately to avoid a race between // URLLoaderClient_OnUploadProgress_ProxyToResponder::Run() (which would // otherwise be blocked on the target client executing the callback) and // CallOnComplete(). If CallOnComplete() is executed first the interface pipe // will be closed and the callback destructor will generate an assertion like: // "URLLoaderClient::OnUploadProgressCallback was destroyed without first // either being run or its corresponding binding being closed. It is an error // to drop response callbacks which still correspond to an open interface // pipe." std::move(callback).Run(); } void InterceptedRequest::OnReceiveCachedMetadata(mojo_base::BigBuffer data) { target_client_->OnReceiveCachedMetadata(std::move(data)); } void InterceptedRequest::OnTransferSizeUpdated(int32_t transfer_size_diff) { target_client_->OnTransferSizeUpdated(transfer_size_diff); } void InterceptedRequest::OnStartLoadingResponseBody( mojo::ScopedDataPipeConsumerHandle body) { target_client_->OnStartLoadingResponseBody( factory_->request_handler_->OnFilterResponseBody(id_, request_, std::move(body))); } void InterceptedRequest::OnComplete( const network::URLLoaderCompletionStatus& status) { // Only wait for the original loader to possibly have a custom error if the // target loader exists and succeeded. If the target loader failed, then it // was a race as to whether that error or the safe browsing error would be // reported. CallOnComplete(status, !stream_loader_ && status.error_code == net::OK); } // URLLoader methods. void InterceptedRequest::FollowRedirect( const std::vector& removed_headers_ext, const net::HttpRequestHeaders& modified_headers_ext, const net::HttpRequestHeaders& modified_cors_exempt_headers, const base::Optional& new_url) { std::vector removed_headers = removed_headers_ext; net::HttpRequestHeaders modified_headers = modified_headers_ext; OnProcessRequestHeaders(new_url.value_or(GURL()), &modified_headers, &removed_headers); // If |OnURLLoaderClientError| was called then we're just waiting for the // connection error handler of |proxied_loader_binding_|. Don't restart the // job since that'll create another URLLoader. if (!target_client_) return; // Normally we would call FollowRedirect on the target loader and it would // begin loading the redirected request. However, the client might want to // intercept that request so restart the job instead. Restart(); } void InterceptedRequest::SetPriority(net::RequestPriority priority, int32_t intra_priority_value) { if (target_loader_) target_loader_->SetPriority(priority, intra_priority_value); } void InterceptedRequest::PauseReadingBodyFromNet() { if (target_loader_) target_loader_->PauseReadingBodyFromNet(); } void InterceptedRequest::ResumeReadingBodyFromNet() { if (target_loader_) target_loader_->ResumeReadingBodyFromNet(); } // Helper methods. void InterceptedRequest::BeforeRequestReceived(const GURL& original_url, bool intercept_request, bool intercept_only) { intercept_request_ = intercept_request; intercept_only_ = intercept_only; if (input_stream_previously_failed_ || !intercept_request_) { // Equivalent to no interception. InterceptResponseReceived(original_url, nullptr); } else { // TODO(network): Verify the case when WebContents::RenderFrameDeleted is // called before network request is intercepted (i.e. if that's possible // and whether it can result in any issues). factory_->request_handler_->ShouldInterceptRequest( id_, &request_, base::BindOnce(&InterceptedRequest::InterceptResponseReceived, weak_factory_.GetWeakPtr(), original_url)); } } void InterceptedRequest::InterceptResponseReceived( const GURL& original_url, std::unique_ptr response) { if (request_.url != original_url) { // A response object shouldn't be created if we're redirecting. DCHECK(!response); // Perform the redirect. current_response_ = network::mojom::URLResponseHead::New(); current_response_->request_start = base::TimeTicks::Now(); current_response_->response_start = base::TimeTicks::Now(); auto headers = MakeResponseHeaders( net::HTTP_TEMPORARY_REDIRECT, std::string(), std::string(), std::string(), -1, {}, false /* allow_existing_header_override */); current_response_->headers = headers; current_response_->encoded_data_length = headers->raw_headers().length(); current_response_->content_length = current_response_->encoded_body_length = 0; std::string origin; if (request_.headers.GetHeader(net::HttpRequestHeaders::kOrigin, &origin) && origin != url::Origin().Serialize()) { // Allow redirects of cross-origin resource loads. headers->AddHeader(network::cors::header_names::kAccessControlAllowOrigin, origin); } if (request_.credentials_mode == network::mojom::CredentialsMode::kInclude) { headers->AddHeader( network::cors::header_names::kAccessControlAllowCredentials, "true"); } const net::RedirectInfo& redirect_info = MakeRedirectInfo(request_, headers.get(), request_.url, 0); HandleResponseOrRedirectHeaders( redirect_info, base::BindOnce(&InterceptedRequest::ContinueToBeforeRedirect, weak_factory_.GetWeakPtr(), redirect_info)); return; } if (response) { // Non-null response: make sure to use it as an override for the // normal network data. ContinueAfterInterceptWithOverride(std::move(response)); } else { // Request was not intercepted/overridden. Proceed with loading // from network, unless this is a special |intercept_only_| loader, // which happens for external schemes (e.g. unsupported schemes). if (intercept_only_) { SendErrorAndCompleteImmediately(net::ERR_UNKNOWN_URL_SCHEME); return; } ContinueAfterIntercept(); } } void InterceptedRequest::ContinueAfterIntercept() { if (!target_loader_ && target_factory_) { network::mojom::URLLoaderClientPtr proxied_client; proxied_client_binding_.Bind(mojo::MakeRequest(&proxied_client)); // Even if this request does not use the header client, future redirects // might, so we need to set the option on the loader. uint32_t options = options_ | network::mojom::kURLLoadOptionUseHeaderClient; target_factory_->CreateLoaderAndStart( mojo::MakeRequest(&target_loader_), id_.routing_id(), id_.request_id(), options, request_, proxied_client.PassInterface(), traffic_annotation_); } } void InterceptedRequest::ContinueAfterInterceptWithOverride( std::unique_ptr response) { network::mojom::URLLoaderClientPtr proxied_client; proxied_client_binding_.Bind(mojo::MakeRequest(&proxied_client)); // StreamReaderURLLoader will synthesize TrustedHeaderClient callbacks to // avoid having Set-Cookie headers stripped by the IPC layer. current_request_uses_header_client_ = true; network::mojom::TrustedHeaderClientPtr header_client; header_client_receiver_.Bind(mojo::MakeRequest(&header_client)); stream_loader_ = new StreamReaderURLLoader( id_, request_, std::move(proxied_client), std::move(header_client), traffic_annotation_, std::make_unique(std::move(response), weak_factory_.GetWeakPtr())); stream_loader_->Start(); } void InterceptedRequest::HandleResponseOrRedirectHeaders( base::Optional redirect_info, net::CompletionOnceCallback continuation) { override_headers_ = nullptr; redirect_url_ = redirect_info.has_value() ? redirect_info->new_url : GURL(); original_url_ = request_.url; // |current_response_| may be nullptr when called from OnHeadersReceived. auto headers = current_response_ ? current_response_->headers : current_headers_; // Even though |head| is const we can get a non-const pointer to the headers // and modifications we make are passed to the target client. factory_->request_handler_->ProcessResponseHeaders( id_, request_, redirect_url_, headers.get()); // Pause handling of client messages before waiting on an async callback. if (proxied_client_binding_) proxied_client_binding_.PauseIncomingMethodCallProcessing(); factory_->request_handler_->OnRequestResponse( id_, &request_, headers.get(), redirect_info, base::BindOnce(&InterceptedRequest::ContinueResponseOrRedirect, weak_factory_.GetWeakPtr(), std::move(continuation))); } void InterceptedRequest::ContinueResponseOrRedirect( net::CompletionOnceCallback continuation, InterceptedRequestHandler::ResponseMode response_mode, scoped_refptr override_headers, const GURL& redirect_url) { if (response_mode == InterceptedRequestHandler::ResponseMode::CANCEL) { std::move(continuation).Run(net::ERR_ABORTED); return; } else if (response_mode == InterceptedRequestHandler::ResponseMode::RESTART) { Restart(); return; } override_headers_ = override_headers; if (override_headers_) { // Make sure to update current_response_, since when OnReceiveResponse // is called we will not use its headers as it might be missing the // Set-Cookie line (which gets stripped by the IPC layer). current_response_->headers = override_headers_; } redirect_url_ = redirect_url; std::move(continuation).Run(net::OK); } void InterceptedRequest::ContinueToHandleOverrideHeaders(int error_code) { if (error_code != net::OK) { SendErrorAndCompleteImmediately(error_code); return; } DCHECK(on_headers_received_callback_); base::Optional headers; if (override_headers_) headers = override_headers_->raw_headers(); header_client_redirect_url_ = redirect_url_; std::move(on_headers_received_callback_).Run(net::OK, headers, redirect_url_); override_headers_ = nullptr; redirect_url_ = GURL(); // Resume handling of client messages after continuing from an async callback. if (proxied_client_binding_) proxied_client_binding_.ResumeIncomingMethodCallProcessing(); } net::RedirectInfo InterceptedRequest::MakeRedirectResponseAndInfo( const GURL& new_location) { // Clear the Content-Type values. current_response_->mime_type = current_response_->charset = std::string(); current_response_->headers->RemoveHeader( net::HttpRequestHeaders::kContentType); // Clear the Content-Length values. current_response_->content_length = current_response_->encoded_body_length = 0; current_response_->headers->RemoveHeader( net::HttpRequestHeaders::kContentLength); current_response_->encoded_data_length = current_response_->headers->raw_headers().size(); const net::RedirectInfo& redirect_info = MakeRedirectInfo( request_, current_response_->headers.get(), new_location, 0); current_response_->headers->ReplaceStatusLine( MakeStatusLine(redirect_info.status_code, std::string(), true)); return redirect_info; } void InterceptedRequest::ContinueToBeforeRedirect( const net::RedirectInfo& redirect_info, int error_code) { if (error_code != net::OK) { SendErrorAndCompleteImmediately(error_code); return; } request_was_redirected_ = true; if (header_client_redirect_url_.is_valid()) header_client_redirect_url_ = GURL(); const GURL redirect_url = redirect_url_; override_headers_ = nullptr; redirect_url_ = GURL(); // Resume handling of client messages after continuing from an async callback. if (proxied_client_binding_) proxied_client_binding_.ResumeIncomingMethodCallProcessing(); if (redirect_url.is_valid()) { net::RedirectInfo new_redirect_info = redirect_info; new_redirect_info.new_url = redirect_url; target_client_->OnReceiveRedirect(new_redirect_info, std::move(current_response_)); request_.url = redirect_url; } else { target_client_->OnReceiveRedirect(redirect_info, std::move(current_response_)); request_.url = redirect_info.new_url; } // If request_ changes from POST to GET, strip POST headers. const bool post_to_get = request_.method == "POST" && redirect_info.new_method == net::HttpRequestHeaders::kGetMethod; request_.method = redirect_info.new_method; request_.site_for_cookies = redirect_info.new_site_for_cookies; request_.referrer = GURL(redirect_info.new_referrer); request_.referrer_policy = redirect_info.new_referrer_policy; // The request method can be changed to "GET". In this case we need to // reset the request body manually, and strip the POST headers. if (request_.method == net::HttpRequestHeaders::kGetMethod) { request_.request_body = nullptr; if (post_to_get) { request_.headers.RemoveHeader(net::HttpRequestHeaders::kContentLength); request_.headers.RemoveHeader(net::HttpRequestHeaders::kContentType); } } } void InterceptedRequest::ContinueToResponseStarted(int error_code) { if (error_code != net::OK) { SendErrorAndCompleteImmediately(error_code); return; } const GURL redirect_url = redirect_url_; override_headers_ = nullptr; redirect_url_ = GURL(); scoped_refptr headers = current_response_ ? current_response_->headers : nullptr; std::string location; const bool is_redirect = redirect_url.is_valid() || (headers && headers->IsRedirect(&location)); if (stream_loader_ && is_redirect) { // Redirecting from OnReceiveResponse generally isn't supported by the // NetworkService, so we can only support it when using a custom loader. // TODO(network): Remove this special case. const GURL new_location = redirect_url.is_valid() ? redirect_url : original_url_.Resolve(location); const net::RedirectInfo& redirect_info = MakeRedirectResponseAndInfo(new_location); HandleResponseOrRedirectHeaders( redirect_info, base::BindOnce(&InterceptedRequest::ContinueToBeforeRedirect, weak_factory_.GetWeakPtr(), redirect_info)); } else { LOG_IF(WARNING, is_redirect) << "Redirect at this time is not supported by " "the default network loader."; // CORS check for requests that are handled by the client. Requests handled // by the network process will be checked there. if (IsOutOfBlinkCorsEnabled() && stream_loader_ && !is_redirect && request_.request_initiator && network::cors::ShouldCheckCors(request_.url, request_.request_initiator, request_.mode)) { const auto error_status = network::cors::CheckAccess( request_.url, GetHeaderString( headers.get(), network::cors::header_names::kAccessControlAllowOrigin), GetHeaderString( headers.get(), network::cors::header_names::kAccessControlAllowCredentials), request_.credentials_mode, *request_.request_initiator); if (error_status && !HasCrossOriginWhitelistEntry(*request_.request_initiator, url::Origin::Create(request_.url))) { SendErrorStatusAndCompleteImmediately( network::URLLoaderCompletionStatus(*error_status)); return; } } // Resume handling of client messages after continuing from an async // callback. if (proxied_client_binding_) proxied_client_binding_.ResumeIncomingMethodCallProcessing(); target_client_->OnReceiveResponse(std::move(current_response_)); } if (stream_loader_) stream_loader_->ContinueResponse(is_redirect); } void InterceptedRequest::OnDestroy() { // We don't want any callbacks after this point. weak_factory_.InvalidateWeakPtrs(); factory_->request_handler_->OnRequestComplete(id_, request_, status_); // Destroys |this|. factory_->RemoveRequest(this); } void InterceptedRequest::OnProcessRequestHeaders( const GURL& redirect_url, net::HttpRequestHeaders* modified_headers, std::vector* removed_headers) { factory_->request_handler_->ProcessRequestHeaders( id_, request_, redirect_url, modified_headers, removed_headers); if (!modified_headers->IsEmpty() || !removed_headers->empty()) { request_.headers.MergeFrom(*modified_headers); for (const std::string& name : *removed_headers) request_.headers.RemoveHeader(name); } } void InterceptedRequest::OnURLLoaderClientError() { // We set |wait_for_loader_error| to true because if the loader did have a // custom_reason error then the client would be reset as well and it would be // a race as to which connection error we saw first. CallOnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED), true /* wait_for_loader_error */); } void InterceptedRequest::OnURLLoaderError(uint32_t custom_reason, const std::string& description) { if (custom_reason == network::mojom::URLLoader::kClientDisconnectReason) SendErrorCallback(safe_browsing::kNetErrorCodeForSafeBrowsing, true); got_loader_error_ = true; // If CallOnComplete was already called, then this object is ready to be // deleted. if (!target_client_) OnDestroy(); } void InterceptedRequest::CallOnComplete( const network::URLLoaderCompletionStatus& status, bool wait_for_loader_error) { status_ = status; if (target_client_) target_client_->OnComplete(status); if (proxied_loader_binding_ && (wait_for_loader_error && !got_loader_error_)) { // Don't delete |this| yet, in case the |proxied_loader_binding_|'s // error_handler is called with a reason to indicate an error which we want // to send to the client bridge. Also reset |target_client_| so we don't // get its error_handler called and then delete |this|. target_client_.reset(); // Since the original client is gone no need to continue loading the // request. proxied_client_binding_.Close(); header_client_receiver_.reset(); target_loader_.reset(); // In case there are pending checks as to whether this request should be // intercepted, we don't want that causing |target_client_| to be used // later. weak_factory_.InvalidateWeakPtrs(); } else { OnDestroy(); } } void InterceptedRequest::SendErrorAndCompleteImmediately(int error_code) { SendErrorStatusAndCompleteImmediately( network::URLLoaderCompletionStatus(error_code)); } void InterceptedRequest::SendErrorStatusAndCompleteImmediately( const network::URLLoaderCompletionStatus& status) { status_ = status; SendErrorCallback(status_.error_code, false); target_client_->OnComplete(status_); OnDestroy(); } void InterceptedRequest::SendErrorCallback(int error_code, bool safebrowsing_hit) { // Ensure we only send one error callback, e.g. to avoid sending two if // there's both a networking error and safe browsing blocked the request. if (sent_error_callback_) return; sent_error_callback_ = true; factory_->request_handler_->OnRequestError(id_, request_, error_code, safebrowsing_hit); } void InterceptedRequest::OnUploadProgressACK() { DCHECK(waiting_for_upload_progress_ack_); waiting_for_upload_progress_ack_ = false; } //============================== // InterceptedRequestHandler //============================== InterceptedRequestHandler::InterceptedRequestHandler() {} InterceptedRequestHandler::~InterceptedRequestHandler() {} void InterceptedRequestHandler::OnBeforeRequest( const RequestId& id, network::ResourceRequest* request, bool request_was_redirected, OnBeforeRequestResultCallback callback, CancelRequestCallback cancel_callback) { std::move(callback).Run(false, false); } void InterceptedRequestHandler::ShouldInterceptRequest( const RequestId& id, network::ResourceRequest* request, ShouldInterceptRequestResultCallback callback) { std::move(callback).Run(nullptr); } void InterceptedRequestHandler::OnRequestResponse( const RequestId& id, network::ResourceRequest* request, net::HttpResponseHeaders* headers, base::Optional redirect_info, OnRequestResponseResultCallback callback) { std::move(callback).Run( ResponseMode::CONTINUE, nullptr, redirect_info.has_value() ? redirect_info->new_url : GURL()); } mojo::ScopedDataPipeConsumerHandle InterceptedRequestHandler::OnFilterResponseBody( const RequestId& id, const network::ResourceRequest& request, mojo::ScopedDataPipeConsumerHandle body) { return body; } //============================== // ProxyURLLoaderFactory //============================== ProxyURLLoaderFactory::ProxyURLLoaderFactory( mojo::PendingReceiver factory_receiver, network::mojom::URLLoaderFactoryPtrInfo target_factory_info, mojo::PendingReceiver header_client_receiver, std::unique_ptr request_handler) : request_handler_(std::move(request_handler)), weak_factory_(this) { CEF_REQUIRE_IOT(); DCHECK(request_handler_); // Actual creation of the factory. if (target_factory_info) { target_factory_.Bind(std::move(target_factory_info)); target_factory_.set_connection_error_handler(base::BindOnce( &ProxyURLLoaderFactory::OnTargetFactoryError, base::Unretained(this))); } proxy_bindings_.AddBinding(this, std::move(factory_receiver)); proxy_bindings_.set_connection_error_handler(base::BindRepeating( &ProxyURLLoaderFactory::OnProxyBindingError, base::Unretained(this))); if (header_client_receiver) url_loader_header_client_receiver_.Bind(std::move(header_client_receiver)); } ProxyURLLoaderFactory::~ProxyURLLoaderFactory() { CEF_REQUIRE_IOT(); } // static void ProxyURLLoaderFactory::CreateOnIOThread( mojo::PendingReceiver factory_receiver, network::mojom::URLLoaderFactoryPtrInfo target_factory_info, mojo::PendingReceiver header_client_receiver, content::ResourceContext* resource_context, std::unique_ptr request_handler) { CEF_REQUIRE_IOT(); auto proxy = new ProxyURLLoaderFactory( std::move(factory_receiver), std::move(target_factory_info), std::move(header_client_receiver), std::move(request_handler)); ResourceContextData::AddProxy(proxy, resource_context); } void ProxyURLLoaderFactory::SetDisconnectCallback( DisconnectCallback on_disconnect) { CEF_REQUIRE_IOT(); DCHECK(!destroyed_); DCHECK(!on_disconnect_); on_disconnect_ = std::move(on_disconnect); } // static void ProxyURLLoaderFactory::CreateProxy( content::BrowserContext* browser_context, mojo::PendingReceiver* factory_receiver, mojo::PendingRemote* header_client, std::unique_ptr request_handler) { CEF_REQUIRE_UIT(); DCHECK(request_handler); auto proxied_receiver = std::move(*factory_receiver); network::mojom::URLLoaderFactoryPtrInfo target_factory_info; *factory_receiver = mojo::MakeRequest(&target_factory_info); mojo::PendingReceiver header_client_receiver; if (header_client) header_client_receiver = header_client->InitWithNewPipeAndPassReceiver(); content::ResourceContext* resource_context = browser_context->GetResourceContext(); DCHECK(resource_context); CEF_POST_TASK( CEF_IOT, base::BindOnce( &ProxyURLLoaderFactory::CreateOnIOThread, std::move(proxied_receiver), std::move(target_factory_info), std::move(header_client_receiver), base::Unretained(resource_context), std::move(request_handler))); } // static ProxyURLLoaderFactory* ProxyURLLoaderFactory::CreateProxy( content::WebContents::Getter web_contents_getter, mojo::PendingReceiver loader_receiver, std::unique_ptr request_handler) { CEF_REQUIRE_IOT(); DCHECK(request_handler); auto proxy = new ProxyURLLoaderFactory( std::move(loader_receiver), nullptr, mojo::PendingReceiver(), std::move(request_handler)); CEF_POST_TASK(CEF_UIT, base::BindOnce(ResourceContextData::AddProxyOnUIThread, base::Unretained(proxy), web_contents_getter)); return proxy; } void ProxyURLLoaderFactory::CreateLoaderAndStart( mojo::PendingReceiver receiver, int32_t routing_id, int32_t request_id, uint32_t options, const network::ResourceRequest& request, mojo::PendingRemote client, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) { CEF_REQUIRE_IOT(); if (!CONTEXT_STATE_VALID()) { // Don't start a request while we're shutting down. return; } bool pass_through = false; if (pass_through) { // This is the so-called pass-through, no-op option. target_factory_->CreateLoaderAndStart( std::move(receiver), routing_id, request_id, options, request, std::move(client), traffic_annotation); return; } mojo::PendingRemote target_factory_clone; if (target_factory_) target_factory_->Clone( target_factory_clone.InitWithNewPipeAndPassReceiver()); InterceptedRequest* req = new InterceptedRequest( this, RequestId(request_id, routing_id), options, request, traffic_annotation, std::move(receiver), std::move(client), std::move(target_factory_clone)); requests_.insert(std::make_pair(request_id, base::WrapUnique(req))); req->Restart(); } void ProxyURLLoaderFactory::Clone( mojo::PendingReceiver factory) { CEF_REQUIRE_IOT(); proxy_bindings_.AddBinding(this, std::move(factory)); } void ProxyURLLoaderFactory::OnLoaderCreated( int32_t request_id, mojo::PendingReceiver receiver) { CEF_REQUIRE_IOT(); auto request_it = requests_.find(request_id); if (request_it != requests_.end()) request_it->second->OnLoaderCreated(std::move(receiver)); } void ProxyURLLoaderFactory::OnLoaderForCorsPreflightCreated( const ::network::ResourceRequest& request, mojo::PendingReceiver header_client) { CEF_REQUIRE_IOT(); // TODO(cef): Should we do something here? } void ProxyURLLoaderFactory::OnTargetFactoryError() { // Stop calls to CreateLoaderAndStart() when |target_factory_| is invalid. target_factory_.reset(); proxy_bindings_.CloseAllBindings(); MaybeDestroySelf(); } void ProxyURLLoaderFactory::OnProxyBindingError() { if (proxy_bindings_.empty()) target_factory_.reset(); MaybeDestroySelf(); } void ProxyURLLoaderFactory::RemoveRequest(InterceptedRequest* request) { auto it = requests_.find(request->id().request_id()); DCHECK(it != requests_.end()); requests_.erase(it); MaybeDestroySelf(); } void ProxyURLLoaderFactory::MaybeDestroySelf() { // Even if all URLLoaderFactory pipes connected to this object have been // closed it has to stay alive until all active requests have completed. if (target_factory_.is_bound() || !requests_.empty()) return; destroyed_ = true; // In some cases we may be destroyed before SetDisconnectCallback is called. if (on_disconnect_) { // Deletes |this|. std::move(on_disconnect_).Run(this); } } } // namespace net_service