diff --git a/shell/browser/atom_browser_client.cc b/shell/browser/atom_browser_client.cc index bf0f60491da8..d4145e268367 100644 --- a/shell/browser/atom_browser_client.cc +++ b/shell/browser/atom_browser_client.cc @@ -998,9 +998,16 @@ bool AtomBrowserClient::WillCreateURLLoaderFactory( auto proxied_request = std::move(*factory_request); network::mojom::URLLoaderFactoryPtrInfo target_factory_info; *factory_request = mojo::MakeRequest(&target_factory_info); - new ProxyingURLLoaderFactory(protocol->intercept_handlers(), - std::move(proxied_request), - std::move(target_factory_info)); + + network::mojom::TrustedURLLoaderHeaderClientRequest header_client_request; + if (header_client) + header_client_request = mojo::MakeRequest(header_client); + + new ProxyingURLLoaderFactory( + protocol->intercept_handlers(), std::move(proxied_request), + std::move(target_factory_info), std::move(header_client_request)); + + *bypass_redirect_checks = true; return true; } diff --git a/shell/browser/net/proxying_url_loader_factory.cc b/shell/browser/net/proxying_url_loader_factory.cc index 21d6f89c1230..92789fc9205a 100644 --- a/shell/browser/net/proxying_url_loader_factory.cc +++ b/shell/browser/net/proxying_url_loader_factory.cc @@ -7,22 +7,631 @@ #include #include "mojo/public/cpp/bindings/binding.h" -#include "services/network/public/mojom/url_loader.mojom.h" +#include "net/base/completion_repeating_callback.h" +#include "net/http/http_util.h" +#include "services/network/public/cpp/features.h" #include "shell/browser/net/asar/asar_url_loader.h" namespace electron { +ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams:: + FollowRedirectParams() = default; +ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams:: + ~FollowRedirectParams() = default; + +ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest( + ProxyingURLLoaderFactory* factory, + int32_t routing_id, + int32_t network_service_request_id, + uint32_t options, + const network::ResourceRequest& request, + const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, + network::mojom::URLLoaderRequest loader_request, + network::mojom::URLLoaderClientPtr client) + : factory_(factory), + request_(request), + original_initiator_(request.request_initiator), + routing_id_(routing_id), + network_service_request_id_(network_service_request_id), + options_(options), + traffic_annotation_(traffic_annotation), + proxied_loader_binding_(this, std::move(loader_request)), + target_client_(std::move(client)), + proxied_client_binding_(this), + // TODO(zcbenz): We should always use "extraHeaders" mode to be compatible + // with old APIs. + has_any_extra_headers_listeners_(false), + header_client_binding_(this) { + // If there is a client error, clean up the request. + target_client_.set_connection_error_handler(base::BindOnce( + &ProxyingURLLoaderFactory::InProgressRequest::OnRequestError, + weak_factory_.GetWeakPtr(), + network::URLLoaderCompletionStatus(net::ERR_ABORTED))); +} + +ProxyingURLLoaderFactory::InProgressRequest::~InProgressRequest() {} + +void ProxyingURLLoaderFactory::InProgressRequest::Restart() { + UpdateRequestInfo(); + RestartInternal(); +} + +void ProxyingURLLoaderFactory::InProgressRequest::UpdateRequestInfo() { + current_request_uses_header_client_ = + factory_->url_loader_header_client_binding_ && + network_service_request_id_ != 0 && + false /* HasExtraHeadersListenerForRequest */; +} + +void ProxyingURLLoaderFactory::InProgressRequest::RestartInternal() { + request_completed_ = false; + + // If the header client will be used, we start the request immediately, and + // OnBeforeSendHeaders and OnSendHeaders will be handled there. Otherwise, + // send these events before the request starts. + base::RepeatingCallback continuation; + if (current_request_uses_header_client_) { + continuation = base::BindRepeating( + &InProgressRequest::ContinueToStartRequest, weak_factory_.GetWeakPtr()); + } else { + continuation = + base::BindRepeating(&InProgressRequest::ContinueToBeforeSendHeaders, + weak_factory_.GetWeakPtr()); + } + redirect_url_ = GURL(); + // TODO(zcbenz): Call webRequest.onBeforeRequest. + int result = net::OK; + if (result == net::ERR_BLOCKED_BY_CLIENT) { + // The request was cancelled synchronously. Dispatch an error notification + // and terminate the request. + network::URLLoaderCompletionStatus status(result); + OnRequestError(status); + return; + } + + if (result == net::ERR_IO_PENDING) { + // One or more listeners is blocking, so the request must be paused until + // they respond. |continuation| above will be invoked asynchronously to + // continue or cancel the request. + // + // We pause the binding here to prevent further client message processing. + if (proxied_client_binding_.is_bound()) + proxied_client_binding_.PauseIncomingMethodCallProcessing(); + + // Pause the header client, since we want to wait until OnBeforeRequest has + // finished before processing any future events. + if (header_client_binding_) + header_client_binding_.PauseIncomingMethodCallProcessing(); + return; + } + DCHECK_EQ(net::OK, result); + + continuation.Run(net::OK); +} + +void ProxyingURLLoaderFactory::InProgressRequest::FollowRedirect( + const std::vector& removed_headers, + const net::HttpRequestHeaders& modified_headers, + const base::Optional& new_url) { + if (new_url) + request_.url = new_url.value(); + + for (const std::string& header : removed_headers) + request_.headers.RemoveHeader(header); + request_.headers.MergeFrom(modified_headers); + + // Call this before checking |current_request_uses_header_client_| as it + // calculates it. + UpdateRequestInfo(); + + if (target_loader_.is_bound()) { + // If header_client_ is used, then we have to call FollowRedirect now as + // that's what triggers the network service calling back to + // OnBeforeSendHeaders(). Otherwise, don't call FollowRedirect now. Wait for + // the onBeforeSendHeaders callback(s) to run as these may modify request + // headers and if so we'll pass these modifications to FollowRedirect. + if (current_request_uses_header_client_) { + target_loader_->FollowRedirect(removed_headers, modified_headers, + new_url); + } else { + auto params = std::make_unique(); + params->removed_headers = removed_headers; + params->modified_headers = modified_headers; + params->new_url = new_url; + pending_follow_redirect_params_ = std::move(params); + } + } + + RestartInternal(); +} + +void ProxyingURLLoaderFactory::InProgressRequest::ProceedWithResponse() { + if (target_loader_.is_bound()) + target_loader_->ProceedWithResponse(); +} + +void ProxyingURLLoaderFactory::InProgressRequest::SetPriority( + net::RequestPriority priority, + int32_t intra_priority_value) { + if (target_loader_.is_bound()) + target_loader_->SetPriority(priority, intra_priority_value); +} + +void ProxyingURLLoaderFactory::InProgressRequest::PauseReadingBodyFromNet() { + if (target_loader_.is_bound()) + target_loader_->PauseReadingBodyFromNet(); +} + +void ProxyingURLLoaderFactory::InProgressRequest::ResumeReadingBodyFromNet() { + if (target_loader_.is_bound()) + target_loader_->ResumeReadingBodyFromNet(); +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveResponse( + const network::ResourceResponseHead& head) { + if (current_request_uses_header_client_) { + // Use the headers we got from OnHeadersReceived as that'll contain + // Set-Cookie if it existed. + auto saved_headers = current_response_.headers; + current_response_ = head; + current_response_.headers = saved_headers; + ContinueToResponseStarted(net::OK); + } else { + current_response_ = head; + HandleResponseOrRedirectHeaders( + base::BindOnce(&InProgressRequest::ContinueToResponseStarted, + weak_factory_.GetWeakPtr())); + } +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveRedirect( + const net::RedirectInfo& redirect_info, + const network::ResourceResponseHead& head) { + // Note: In Electron we don't check IsRedirectSafe. + + if (current_request_uses_header_client_) { + // Use the headers we got from OnHeadersReceived as that'll contain + // Set-Cookie if it existed. + auto saved_headers = current_response_.headers; + current_response_ = head; + // If this redirect is from an HSTS upgrade, OnHeadersReceived will not be + // called before OnReceiveRedirect, so make sure the saved headers exist + // before setting them. + if (saved_headers) + current_response_.headers = saved_headers; + ContinueToBeforeRedirect(redirect_info, net::OK); + } else { + current_response_ = head; + HandleResponseOrRedirectHeaders( + base::BindOnce(&InProgressRequest::ContinueToBeforeRedirect, + weak_factory_.GetWeakPtr(), redirect_info)); + } +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnUploadProgress( + int64_t current_position, + int64_t total_size, + OnUploadProgressCallback callback) { + target_client_->OnUploadProgress(current_position, total_size, + std::move(callback)); +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveCachedMetadata( + mojo_base::BigBuffer data) { + target_client_->OnReceiveCachedMetadata(std::move(data)); +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnTransferSizeUpdated( + int32_t transfer_size_diff) { + target_client_->OnTransferSizeUpdated(transfer_size_diff); +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnStartLoadingResponseBody( + mojo::ScopedDataPipeConsumerHandle body) { + target_client_->OnStartLoadingResponseBody(std::move(body)); +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnComplete( + const network::URLLoaderCompletionStatus& status) { + if (status.error_code != net::OK) { + OnRequestError(status); + return; + } + + target_client_->OnComplete(status); + // TODO(zcbenz): Call webRequest.onCompleted. + + // TODO(zcbenz): Disassociate from factory. + delete this; +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnLoaderCreated( + network::mojom::TrustedHeaderClientRequest request) { + header_client_binding_.Bind(std::move(request)); +} + +void ProxyingURLLoaderFactory::InProgressRequest::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; + on_before_send_headers_callback_ = std::move(callback); + ContinueToBeforeSendHeaders(net::OK); +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnHeadersReceived( + const std::string& headers, + OnHeadersReceivedCallback callback) { + if (!current_request_uses_header_client_) { + std::move(callback).Run(net::OK, base::nullopt, GURL()); + return; + } + + on_headers_received_callback_ = std::move(callback); + current_response_.headers = + base::MakeRefCounted(headers); + HandleResponseOrRedirectHeaders( + base::BindOnce(&InProgressRequest::ContinueToHandleOverrideHeaders, + weak_factory_.GetWeakPtr())); +} + +void ProxyingURLLoaderFactory::InProgressRequest::ContinueToBeforeSendHeaders( + int error_code) { + if (error_code != net::OK) { + OnRequestError(network::URLLoaderCompletionStatus(error_code)); + return; + } + + if (!current_request_uses_header_client_ && !redirect_url_.is_empty()) { + HandleBeforeRequestRedirect(); + return; + } + + if (proxied_client_binding_.is_bound()) + proxied_client_binding_.ResumeIncomingMethodCallProcessing(); + + auto continuation = base::BindRepeating( + &InProgressRequest::ContinueToSendHeaders, weak_factory_.GetWeakPtr()); + // Note: In Electron onBeforeSendHeaders is called for all protocols. + // TODO(zcbenz): Call webRequest.onBeforeSendHeaders. + int result = net::OK; + + if (result == net::ERR_BLOCKED_BY_CLIENT) { + // The request was cancelled synchronously. Dispatch an error notification + // and terminate the request. + OnRequestError(network::URLLoaderCompletionStatus(result)); + return; + } + + if (result == net::ERR_IO_PENDING) { + // One or more listeners is blocking, so the request must be paused until + // they respond. |continuation| above will be invoked asynchronously to + // continue or cancel the request. + // + // We pause the binding here to prevent further client message processing. + if (proxied_client_binding_.is_bound()) + proxied_client_binding_.PauseIncomingMethodCallProcessing(); + return; + } + DCHECK_EQ(net::OK, result); + + ContinueToSendHeaders(std::set(), std::set(), + net::OK); +} + +void ProxyingURLLoaderFactory::InProgressRequest::ContinueToSendHeaders( + const std::set& removed_headers, + const std::set& set_headers, + int error_code) { + if (error_code != net::OK) { + OnRequestError(network::URLLoaderCompletionStatus(error_code)); + return; + } + + if (current_request_uses_header_client_) { + DCHECK(on_before_send_headers_callback_); + std::move(on_before_send_headers_callback_) + .Run(error_code, request_.headers); + } else if (pending_follow_redirect_params_) { + pending_follow_redirect_params_->removed_headers.insert( + pending_follow_redirect_params_->removed_headers.end(), + removed_headers.begin(), removed_headers.end()); + + for (auto& set_header : set_headers) { + std::string header_value; + if (request_.headers.GetHeader(set_header, &header_value)) { + pending_follow_redirect_params_->modified_headers.SetHeader( + set_header, header_value); + } else { + NOTREACHED(); + } + } + + if (target_loader_.is_bound()) { + target_loader_->FollowRedirect( + pending_follow_redirect_params_->removed_headers, + pending_follow_redirect_params_->modified_headers, + pending_follow_redirect_params_->new_url); + } + + pending_follow_redirect_params_.reset(); + } + + if (proxied_client_binding_.is_bound()) + proxied_client_binding_.ResumeIncomingMethodCallProcessing(); + + // Note: In Electron onSendHeaders is called for all protocols. + // TODO(zcbenz): Call webRequest.onSendHeaders. + + if (!current_request_uses_header_client_) + ContinueToStartRequest(net::OK); +} + +void ProxyingURLLoaderFactory::InProgressRequest::ContinueToStartRequest( + int error_code) { + if (error_code != net::OK) { + OnRequestError(network::URLLoaderCompletionStatus(error_code)); + return; + } + + if (current_request_uses_header_client_ && !redirect_url_.is_empty()) { + HandleBeforeRequestRedirect(); + return; + } + + if (proxied_client_binding_.is_bound()) + proxied_client_binding_.ResumeIncomingMethodCallProcessing(); + + if (header_client_binding_) + header_client_binding_.ResumeIncomingMethodCallProcessing(); + + if (!target_loader_.is_bound() && factory_->target_factory_.is_bound()) { + // No extensions have cancelled us up to this point, so it's now OK to + // initiate the real network request. + network::mojom::URLLoaderClientPtr proxied_client; + proxied_client_binding_.Bind(mojo::MakeRequest(&proxied_client)); + uint32_t options = options_; + // Even if this request does not use the header client, future redirects + // might, so we need to set the option on the loader. + if (has_any_extra_headers_listeners_) + options |= network::mojom::kURLLoadOptionUseHeaderClient; + factory_->target_factory_->CreateLoaderAndStart( + mojo::MakeRequest(&target_loader_), routing_id_, + network_service_request_id_, options, request_, + std::move(proxied_client), traffic_annotation_); + } + + // From here the lifecycle of this request is driven by subsequent events on + // either |proxy_loader_binding_|, |proxy_client_binding_|, or + // |header_client_binding_|. +} + +void ProxyingURLLoaderFactory::InProgressRequest:: + ContinueToHandleOverrideHeaders(int error_code) { + if (error_code != net::OK) { + OnRequestError(network::URLLoaderCompletionStatus(error_code)); + return; + } + + DCHECK(on_headers_received_callback_); + base::Optional headers; + if (override_headers_) { + headers = override_headers_->raw_headers(); + if (current_request_uses_header_client_) { + // 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 (as that gets stripped over IPC). + current_response_.headers = override_headers_; + } + } + std::move(on_headers_received_callback_).Run(net::OK, headers, redirect_url_); + override_headers_ = nullptr; + + if (proxied_client_binding_) + proxied_client_binding_.ResumeIncomingMethodCallProcessing(); +} + +void ProxyingURLLoaderFactory::InProgressRequest::ContinueToResponseStarted( + int error_code) { + if (error_code != net::OK) { + OnRequestError(network::URLLoaderCompletionStatus(error_code)); + return; + } + + DCHECK(!current_request_uses_header_client_ || !override_headers_); + if (override_headers_) + current_response_.headers = override_headers_; + + std::string redirect_location; + if (override_headers_ && override_headers_->IsRedirect(&redirect_location)) { + // The response headers may have been overridden by an |onHeadersReceived| + // handler and may have been changed to a redirect. We handle that here + // instead of acting like regular request completion. + // + // Note that we can't actually change how the Network Service handles the + // original request at this point, so our "redirect" is really just + // generating an artificial |onBeforeRedirect| event and starting a new + // request to the Network Service. Our client shouldn't know the difference. + GURL new_url(redirect_location); + + net::RedirectInfo redirect_info; + redirect_info.status_code = override_headers_->response_code(); + redirect_info.new_method = request_.method; + redirect_info.new_url = new_url; + redirect_info.new_site_for_cookies = new_url; + + // These will get re-bound if a new request is initiated by + // |FollowRedirect()|. + proxied_client_binding_.Close(); + header_client_binding_.Close(); + target_loader_.reset(); + + ContinueToBeforeRedirect(redirect_info, net::OK); + return; + } + + proxied_client_binding_.ResumeIncomingMethodCallProcessing(); + + // TODO(zcbenz): Call webRequest.onResponseStarted. + target_client_->OnReceiveResponse(current_response_); +} + +void ProxyingURLLoaderFactory::InProgressRequest::ContinueToBeforeRedirect( + const net::RedirectInfo& redirect_info, + int error_code) { + if (error_code != net::OK) { + OnRequestError(network::URLLoaderCompletionStatus(error_code)); + return; + } + + if (proxied_client_binding_.is_bound()) + proxied_client_binding_.ResumeIncomingMethodCallProcessing(); + + // TODO(zcbenz): Call WebRequest.onBeforeRedirect. + target_client_->OnReceiveRedirect(redirect_info, current_response_); + request_.url = redirect_info.new_url; + 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. + if (request_.method == net::HttpRequestHeaders::kGetMethod) + request_.request_body = nullptr; + + request_completed_ = true; +} + +void ProxyingURLLoaderFactory::InProgressRequest:: + HandleBeforeRequestRedirect() { + // The extension requested a redirect. Close the connection with the current + // URLLoader and inform the URLLoaderClient the WebRequest API generated a + // redirect. To load |redirect_url_|, a new URLLoader will be recreated + // after receiving FollowRedirect(). + + // Forgetting to close the connection with the current URLLoader caused + // bugs. The latter doesn't know anything about the redirect. Continuing + // the load with it gives unexpected results. See + // https://crbug.com/882661#c72. + proxied_client_binding_.Close(); + header_client_binding_.Close(); + target_loader_.reset(); + + constexpr int kInternalRedirectStatusCode = 307; + + net::RedirectInfo redirect_info; + redirect_info.status_code = kInternalRedirectStatusCode; + redirect_info.new_method = request_.method; + redirect_info.new_url = redirect_url_; + redirect_info.new_site_for_cookies = redirect_url_; + + network::ResourceResponseHead head; + std::string headers = base::StringPrintf( + "HTTP/1.1 %i Internal Redirect\n" + "Location: %s\n" + "Non-Authoritative-Reason: WebRequest API\n\n", + kInternalRedirectStatusCode, redirect_url_.spec().c_str()); + + if (network::features::ShouldEnableOutOfBlinkCors()) { + // Cross-origin requests need to modify the Origin header to 'null'. Since + // CorsURLLoader sets |request_initiator| to the Origin request header in + // NetworkService, we need to modify |request_initiator| here to craft the + // Origin header indirectly. + // Following checks implement the step 10 of "4.4. HTTP-redirect fetch", + // https://fetch.spec.whatwg.org/#http-redirect-fetch + if (request_.request_initiator && + (!url::Origin::Create(redirect_url_) + .IsSameOriginWith(url::Origin::Create(request_.url)) && + !request_.request_initiator->IsSameOriginWith( + url::Origin::Create(request_.url)))) { + // Reset the initiator to pretend tainted origin flag of the spec is set. + request_.request_initiator = url::Origin(); + } + } else { + // If this redirect is used in a cross-origin request, add CORS headers to + // make sure that the redirect gets through the Blink CORS. Note that the + // destination URL is still subject to the usual CORS policy, i.e. the + // resource will only be available to web pages if the server serves the + // response with the required CORS response headers. Matches the behavior in + // url_request_redirect_job.cc. + std::string http_origin; + if (request_.headers.GetHeader("Origin", &http_origin)) { + headers += base::StringPrintf( + "\n" + "Access-Control-Allow-Origin: %s\n" + "Access-Control-Allow-Credentials: true", + http_origin.c_str()); + } + } + head.headers = base::MakeRefCounted( + net::HttpUtil::AssembleRawHeaders(headers)); + head.encoded_data_length = 0; + + current_response_ = head; + ContinueToBeforeRedirect(redirect_info, net::OK); +} + +void ProxyingURLLoaderFactory::InProgressRequest:: + HandleResponseOrRedirectHeaders(net::CompletionOnceCallback continuation) { + override_headers_ = nullptr; + redirect_url_ = GURL(); + + net::CompletionRepeatingCallback copyable_callback = + base::AdaptCallbackForRepeating(std::move(continuation)); + // TODO(zcbenz): Call webRequest.onHeadersReceived. + int result = net::OK; + if (result == net::ERR_BLOCKED_BY_CLIENT) { + OnRequestError(network::URLLoaderCompletionStatus(result)); + return; + } + + if (result == net::ERR_IO_PENDING) { + // One or more listeners is blocking, so the request must be paused until + // they respond. |continuation| above will be invoked asynchronously to + // continue or cancel the request. + // + // We pause the binding here to prevent further client message processing. + proxied_client_binding_.PauseIncomingMethodCallProcessing(); + return; + } + + DCHECK_EQ(net::OK, result); + + copyable_callback.Run(net::OK); +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnRequestError( + const network::URLLoaderCompletionStatus& status) { + if (!request_completed_) { + target_client_->OnComplete(status); + // TODO(zcbenz): Call webRequest.onErrorOccurred. + } + + // TODO(zcbenz): Disassociate from factory. + delete this; +} + ProxyingURLLoaderFactory::ProxyingURLLoaderFactory( - const HandlersMap& handlers, + const HandlersMap& intercepted_handlers, network::mojom::URLLoaderFactoryRequest loader_request, - network::mojom::URLLoaderFactoryPtrInfo target_factory_info) - : handlers_(handlers) { + network::mojom::URLLoaderFactoryPtrInfo target_factory_info, + network::mojom::TrustedURLLoaderHeaderClientRequest header_client_request) + : intercepted_handlers_(intercepted_handlers), + url_loader_header_client_binding_(this) { target_factory_.Bind(std::move(target_factory_info)); target_factory_.set_connection_error_handler(base::BindOnce( &ProxyingURLLoaderFactory::OnTargetFactoryError, base::Unretained(this))); proxy_bindings_.AddBinding(this, std::move(loader_request)); proxy_bindings_.set_connection_error_handler(base::BindRepeating( &ProxyingURLLoaderFactory::OnProxyBindingError, base::Unretained(this))); + + if (header_client_request) + url_loader_header_client_binding_.Bind(std::move(header_client_request)); } ProxyingURLLoaderFactory::~ProxyingURLLoaderFactory() = default; @@ -36,8 +645,8 @@ void ProxyingURLLoaderFactory::CreateLoaderAndStart( network::mojom::URLLoaderClientPtr client, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) { // Check if user has intercepted this scheme. - auto it = handlers_.find(request.url.scheme()); - if (it != handlers_.end()) { + auto it = intercepted_handlers_.find(request.url.scheme()); + if (it != intercepted_handlers_.end()) { // > it->second.second.Run( request, base::BindOnce(&AtomURLLoaderFactory::StartLoading, @@ -58,6 +667,8 @@ void ProxyingURLLoaderFactory::CreateLoaderAndStart( target_factory_->CreateLoaderAndStart(std::move(loader), routing_id, request_id, options, request, std::move(client), traffic_annotation); + + // TODO(zcbenz): Create InProgressRequest. } void ProxyingURLLoaderFactory::Clone( @@ -65,6 +676,12 @@ void ProxyingURLLoaderFactory::Clone( proxy_bindings_.AddBinding(this, std::move(loader_request)); } +void ProxyingURLLoaderFactory::OnLoaderCreated( + int32_t request_id, + network::mojom::TrustedHeaderClientRequest request) { + // TODO(zcbenz): Call |OnLoaderCreated| for |InProgressRequest|. +} + void ProxyingURLLoaderFactory::OnTargetFactoryError() { delete this; } diff --git a/shell/browser/net/proxying_url_loader_factory.h b/shell/browser/net/proxying_url_loader_factory.h index 50196c8d1d35..667093ead09a 100644 --- a/shell/browser/net/proxying_url_loader_factory.h +++ b/shell/browser/net/proxying_url_loader_factory.h @@ -5,16 +5,154 @@ #ifndef SHELL_BROWSER_NET_PROXYING_URL_LOADER_FACTORY_H_ #define SHELL_BROWSER_NET_PROXYING_URL_LOADER_FACTORY_H_ +#include +#include +#include +#include + +#include "base/optional.h" +#include "services/network/public/cpp/resource_request.h" +#include "services/network/public/cpp/resource_response.h" +#include "services/network/public/mojom/network_context.mojom.h" +#include "services/network/public/mojom/url_loader.mojom.h" #include "shell/browser/net/atom_url_loader_factory.h" namespace electron { -class ProxyingURLLoaderFactory : public network::mojom::URLLoaderFactory { +// This class is responsible for following tasks when NetworkService is enabled: +// 1. handling intercepted protocols; +// 2. implementing webRequest module; +// +// For the task #2, the code is referenced from the +// extensions::WebRequestProxyingURLLoaderFactory class. +class ProxyingURLLoaderFactory + : public network::mojom::URLLoaderFactory, + public network::mojom::TrustedURLLoaderHeaderClient { public: + class InProgressRequest : public network::mojom::URLLoader, + public network::mojom::URLLoaderClient, + public network::mojom::TrustedHeaderClient { + public: + InProgressRequest( + ProxyingURLLoaderFactory* factory, + int32_t routing_id, + int32_t network_service_request_id, + uint32_t options, + const network::ResourceRequest& request, + const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, + network::mojom::URLLoaderRequest loader_request, + network::mojom::URLLoaderClientPtr client); + ~InProgressRequest() override; + + void Restart(); + + // network::mojom::URLLoader: + void FollowRedirect(const std::vector& removed_headers, + const net::HttpRequestHeaders& modified_headers, + const base::Optional& new_url) override; + void ProceedWithResponse() override; + void SetPriority(net::RequestPriority priority, + int32_t intra_priority_value) override; + void PauseReadingBodyFromNet() override; + void ResumeReadingBodyFromNet() override; + + // network::mojom::URLLoaderClient: + void OnReceiveResponse(const network::ResourceResponseHead& head) override; + void OnReceiveRedirect(const net::RedirectInfo& redirect_info, + const network::ResourceResponseHead& 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; + + void OnLoaderCreated(network::mojom::TrustedHeaderClientRequest request); + + // network::mojom::TrustedHeaderClient: + void OnBeforeSendHeaders(const net::HttpRequestHeaders& headers, + OnBeforeSendHeadersCallback callback) override; + void OnHeadersReceived(const std::string& headers, + OnHeadersReceivedCallback callback) override; + + private: + // These two methods combined form the implementation of Restart(). + void UpdateRequestInfo(); + void RestartInternal(); + + void ContinueToBeforeSendHeaders(int error_code); + void ContinueToSendHeaders(const std::set& removed_headers, + const std::set& set_headers, + int error_code); + void ContinueToStartRequest(int error_code); + void ContinueToHandleOverrideHeaders(int error_code); + void ContinueToResponseStarted(int error_code); + void ContinueToBeforeRedirect(const net::RedirectInfo& redirect_info, + int error_code); + void HandleBeforeRequestRedirect(); + void HandleResponseOrRedirectHeaders( + net::CompletionOnceCallback continuation); + void OnRequestError(const network::URLLoaderCompletionStatus& status); + + ProxyingURLLoaderFactory* factory_; + network::ResourceRequest request_; + const base::Optional original_initiator_; + const int32_t routing_id_; + const int32_t network_service_request_id_; + const uint32_t options_; + const net::MutableNetworkTrafficAnnotationTag traffic_annotation_; + mojo::Binding proxied_loader_binding_; + network::mojom::URLLoaderClientPtr target_client_; + + network::ResourceResponseHead current_response_; + scoped_refptr override_headers_; + GURL redirect_url_; + + mojo::Binding proxied_client_binding_; + network::mojom::URLLoaderPtr target_loader_; + + bool request_completed_ = false; + + // If |has_any_extra_headers_listeners_| is set to true, the request will be + // sent with the network::mojom::kURLLoadOptionUseHeaderClient option, and + // we expect events to come through the + // network::mojom::TrustedURLLoaderHeaderClient binding on the factory. This + // is only set to true if there is a listener that needs to view or modify + // headers set in the network process. + bool has_any_extra_headers_listeners_ = false; + bool current_request_uses_header_client_ = false; + OnBeforeSendHeadersCallback on_before_send_headers_callback_; + OnHeadersReceivedCallback on_headers_received_callback_; + mojo::Binding header_client_binding_; + + // If |has_any_extra_headers_listeners_| is set to false and a redirect is + // in progress, this stores the parameters to FollowRedirect that came from + // the client. That way we can combine it with any other changes that + // extensions made to headers in their callbacks. + struct FollowRedirectParams { + FollowRedirectParams(); + ~FollowRedirectParams(); + std::vector removed_headers; + net::HttpRequestHeaders modified_headers; + base::Optional new_url; + + DISALLOW_COPY_AND_ASSIGN(FollowRedirectParams); + }; + std::unique_ptr pending_follow_redirect_params_; + + base::WeakPtrFactory weak_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(InProgressRequest); + }; + ProxyingURLLoaderFactory( - const HandlersMap& handlers, + const HandlersMap& intercepted_handlers, network::mojom::URLLoaderFactoryRequest loader_request, - network::mojom::URLLoaderFactoryPtrInfo target_factory_info); + network::mojom::URLLoaderFactoryPtrInfo target_factory_info, + network::mojom::TrustedURLLoaderHeaderClientRequest + header_client_request); ~ProxyingURLLoaderFactory() override; // network::mojom::URLLoaderFactory: @@ -28,6 +166,11 @@ class ProxyingURLLoaderFactory : public network::mojom::URLLoaderFactory { traffic_annotation) override; void Clone(network::mojom::URLLoaderFactoryRequest request) override; + // network::mojom::TrustedURLLoaderHeaderClient: + void OnLoaderCreated( + int32_t request_id, + network::mojom::TrustedHeaderClientRequest request) override; + private: void OnTargetFactoryError(); void OnProxyBindingError(); @@ -39,10 +182,12 @@ class ProxyingURLLoaderFactory : public network::mojom::URLLoaderFactory { // reference is guarenteed to be valid. // // In this way we can avoid using code from api namespace in this file. - const HandlersMap& handlers_; + const HandlersMap& intercepted_handlers_; mojo::BindingSet proxy_bindings_; network::mojom::URLLoaderFactoryPtr target_factory_; + mojo::Binding + url_loader_header_client_binding_; DISALLOW_COPY_AND_ASSIGN(ProxyingURLLoaderFactory); };