// Copyright (c) 2019 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "shell/browser/net/proxying_url_loader_factory.h" #include #include "base/command_line.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "content/public/browser/browser_context.h" #include "extensions/browser/extension_navigation_ui_data.h" #include "mojo/public/cpp/bindings/binding.h" #include "net/base/completion_repeating_callback.h" #include "net/base/load_flags.h" #include "net/http/http_util.h" #include "services/network/public/cpp/features.h" #include "shell/browser/net/asar/asar_url_loader.h" #include "shell/common/options_switches.h" namespace electron { ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams:: FollowRedirectParams() = default; ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams:: ~FollowRedirectParams() = default; ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest( ProxyingURLLoaderFactory* factory, int64_t web_request_id, int32_t routing_id, int32_t network_service_request_id, uint32_t options, const network::ResourceRequest& request, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, mojo::PendingReceiver loader_receiver, mojo::PendingRemote client) : factory_(factory), request_(request), original_initiator_(request.request_initiator), request_id_(web_request_id), routing_id_(routing_id), network_service_request_id_(network_service_request_id), options_(options), traffic_annotation_(traffic_annotation), proxied_loader_receiver_(this, std::move(loader_receiver)), target_client_(std::move(client)), current_response_(network::mojom::URLResponseHead::New()), // Always use "extraHeaders" mode to be compatible with old APIs, except // when the |request_id_| is zero, which is not supported in Chromium and // only happens in Electron when the request is started from net module. has_any_extra_headers_listeners_(network_service_request_id != 0) { // If there is a client error, clean up the request. target_client_.set_disconnect_handler(base::BindOnce( &ProxyingURLLoaderFactory::InProgressRequest::OnRequestError, weak_factory_.GetWeakPtr(), network::URLLoaderCompletionStatus(net::ERR_ABORTED))); proxied_loader_receiver_.set_disconnect_handler(base::BindOnce( &ProxyingURLLoaderFactory::InProgressRequest::OnRequestError, weak_factory_.GetWeakPtr(), network::URLLoaderCompletionStatus(net::ERR_ABORTED))); } ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest( ProxyingURLLoaderFactory* factory, uint64_t request_id, const network::ResourceRequest& request) : factory_(factory), request_(request), original_initiator_(request.request_initiator), request_id_(request_id), proxied_loader_receiver_(this), for_cors_preflight_(true), has_any_extra_headers_listeners_(true) {} ProxyingURLLoaderFactory::InProgressRequest::~InProgressRequest() { // This is important to ensure that no outstanding blocking requests continue // to reference state owned by this object. if (info_) { factory_->web_request_api()->OnRequestWillBeDestroyed(&info_.value()); } if (on_before_send_headers_callback_) { std::move(on_before_send_headers_callback_) .Run(net::ERR_ABORTED, base::nullopt); } if (on_headers_received_callback_) { std::move(on_headers_received_callback_) .Run(net::ERR_ABORTED, base::nullopt, base::nullopt); } } void ProxyingURLLoaderFactory::InProgressRequest::Restart() { UpdateRequestInfo(); RestartInternal(); } void ProxyingURLLoaderFactory::InProgressRequest::UpdateRequestInfo() { // Derive a new WebRequestInfo value any time |Restart()| is called, because // the details in |request_| may have changed e.g. if we've been redirected. // |request_initiator| can be modified on redirects, but we keep the original // for |initiator| in the event. See also // https://developer.chrome.com/extensions/webRequest#event-onBeforeRequest. network::ResourceRequest request_for_info = request_; request_for_info.request_initiator = original_initiator_; info_.emplace(extensions::WebRequestInfoInitParams( request_id_, factory_->render_process_id_, request_.render_frame_id, factory_->navigation_ui_data_ ? factory_->navigation_ui_data_->DeepCopy() : nullptr, routing_id_, request_for_info, false, !(options_ & network::mojom::kURLLoadOptionSynchronous), factory_->IsForServiceWorkerScript(), factory_->navigation_id_)); current_request_uses_header_client_ = factory_->url_loader_header_client_receiver_.is_bound() && (for_cors_preflight_ || network_service_request_id_ != 0) && has_any_extra_headers_listeners_; } void ProxyingURLLoaderFactory::InProgressRequest::RestartInternal() { DCHECK_EQ(info_->url, request_.url) << "UpdateRequestInfo must have been called first"; // 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 if (for_cors_preflight_) { // In this case we do nothing because extensions should see nothing. return; } else { continuation = base::BindRepeating(&InProgressRequest::ContinueToBeforeSendHeaders, weak_factory_.GetWeakPtr()); } redirect_url_ = GURL(); int result = factory_->web_request_api()->OnBeforeRequest( &info_.value(), request_, continuation, &redirect_url_); 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_receiver_.is_bound()) proxied_client_receiver_.Pause(); // Pause the header client, since we want to wait until OnBeforeRequest has // finished before processing any future events. if (header_client_receiver_.is_bound()) header_client_receiver_.Pause(); 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::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( network::mojom::URLResponseHeadPtr 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_ = std::move(head); current_response_->headers = saved_headers; ContinueToResponseStarted(net::OK); } else { current_response_ = std::move(head); HandleResponseOrRedirectHeaders( base::BindOnce(&InProgressRequest::ContinueToResponseStarted, weak_factory_.GetWeakPtr())); } } void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveRedirect( const net::RedirectInfo& redirect_info, network::mojom::URLResponseHeadPtr 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_ = std::move(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_ = std::move(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); factory_->web_request_api()->OnCompleted(&info_.value(), request_, status.error_code); // Deletes |this|. factory_->RemoveRequest(network_service_request_id_, request_id_); } void ProxyingURLLoaderFactory::InProgressRequest::OnLoaderCreated( mojo::PendingReceiver receiver) { header_client_receiver_.Bind(std::move(receiver)); if (for_cors_preflight_) { // In this case we don't have |target_loader_| and // |proxied_client_binding_|, and |receiver| is the only connection to the // network service, so we observe mojo connection errors. header_client_receiver_.set_disconnect_handler(base::BindOnce( &ProxyingURLLoaderFactory::InProgressRequest::OnRequestError, weak_factory_.GetWeakPtr(), network::URLLoaderCompletionStatus(net::ERR_FAILED))); } } 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, const net::IPEndPoint& remote_endpoint, OnHeadersReceivedCallback callback) { if (!current_request_uses_header_client_) { std::move(callback).Run(net::OK, base::nullopt, GURL()); if (for_cors_preflight_) { // CORS preflight is supported only when "extraHeaders" is specified. // Deletes |this|. factory_->RemoveRequest(network_service_request_id_, request_id_); } return; } on_headers_received_callback_ = std::move(callback); current_response_ = network::mojom::URLResponseHead::New(); current_response_->headers = base::MakeRefCounted(headers); current_response_->remote_endpoint = remote_endpoint; HandleResponseOrRedirectHeaders( base::BindOnce(&InProgressRequest::ContinueToHandleOverrideHeaders, weak_factory_.GetWeakPtr())); } void ProxyingURLLoaderFactory::OnLoaderForCorsPreflightCreated( const network::ResourceRequest& request, mojo::PendingReceiver receiver) { // Please note that the URLLoader is now starting, without waiting for // additional signals from here. The URLLoader will be blocked before // sending HTTP request headers (TrustedHeaderClient.OnBeforeSendHeaders), // but the connection set up will be done before that. This is acceptable from // Web Request API because the extension has already allowed to set up // a connection to the same URL (i.e., the actual request), and distinguishing // two connections for the actual request and the preflight request before // sending request headers is very difficult. const uint64_t web_request_id = ++(*request_id_generator_); auto result = requests_.insert(std::make_pair( web_request_id, std::make_unique(this, web_request_id, request))); result.first->second->OnLoaderCreated(std::move(receiver)); result.first->second->Restart(); } 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()) { if (for_cors_preflight_) { // CORS preflight doesn't support redirect. OnRequestError(network::URLLoaderCompletionStatus(net::ERR_FAILED)); return; } HandleBeforeRequestRedirect(); return; } if (proxied_client_receiver_.is_bound()) proxied_client_receiver_.Resume(); auto continuation = base::BindRepeating( &InProgressRequest::ContinueToSendHeaders, weak_factory_.GetWeakPtr()); // Note: In Electron onBeforeSendHeaders is called for all protocols. int result = factory_->web_request_api()->OnBeforeSendHeaders( &info_.value(), request_, continuation, &request_.headers); 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 receiver here to prevent further client message processing. if (proxied_client_receiver_.is_bound()) proxied_client_receiver_.Resume(); 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_receiver_.is_bound()) proxied_client_receiver_.Resume(); // Note: In Electron onSendHeaders is called for all protocols. factory_->web_request_api()->OnSendHeaders(&info_.value(), request_, request_.headers); 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_receiver_.is_bound()) proxied_client_receiver_.Resume(); if (header_client_receiver_.is_bound()) header_client_receiver_.Resume(); if (for_cors_preflight_) { // For CORS preflight requests, we have already started the request in // the network service. We did block the request by blocking // |header_client_receiver_|, which we unblocked right above. return; } 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. 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_, proxied_client_receiver_.BindNewPipeAndPassRemote(), traffic_annotation_); } // From here the lifecycle of this request is driven by subsequent events on // either |proxy_loader_binding_|, |proxy_client_receiver_|, or // |header_client_receiver_|. } 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_; } } if (for_cors_preflight_ && !redirect_url_.is_empty()) { OnRequestError(network::URLLoaderCompletionStatus(net::ERR_FAILED)); return; } std::move(on_headers_received_callback_).Run(net::OK, headers, redirect_url_); override_headers_ = nullptr; if (for_cors_preflight_) { // If this is for CORS preflight, there is no associated client. We notify // the completion here, and deletes |this|. info_->AddResponseInfoFromResourceResponse(*current_response_); factory_->web_request_api()->OnResponseStarted(&info_.value(), request_); factory_->web_request_api()->OnCompleted(&info_.value(), request_, net::OK); // Deletes |this|. factory_->RemoveRequest(network_service_request_id_, request_id_); return; } if (proxied_client_receiver_.is_bound()) proxied_client_receiver_.Resume(); } void ProxyingURLLoaderFactory::InProgressRequest::ContinueToResponseStarted( int error_code) { DCHECK(!for_cors_preflight_); 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 = net::SiteForCookies::FromUrl(new_url); // These will get re-bound if a new request is initiated by // |FollowRedirect()|. proxied_client_receiver_.reset(); header_client_receiver_.reset(); target_loader_.reset(); ContinueToBeforeRedirect(redirect_info, net::OK); return; } info_->AddResponseInfoFromResourceResponse(*current_response_); proxied_client_receiver_.Resume(); factory_->web_request_api()->OnResponseStarted(&info_.value(), request_); target_client_->OnReceiveResponse(current_response_.Clone()); } void ProxyingURLLoaderFactory::InProgressRequest::ContinueToBeforeRedirect( const net::RedirectInfo& redirect_info, int error_code) { if (error_code != net::OK) { OnRequestError(network::URLLoaderCompletionStatus(error_code)); return; } info_->AddResponseInfoFromResourceResponse(*current_response_); if (proxied_client_receiver_.is_bound()) proxied_client_receiver_.Resume(); factory_->web_request_api()->OnBeforeRedirect(&info_.value(), request_, redirect_info.new_url); target_client_->OnReceiveRedirect(redirect_info, current_response_.Clone()); 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; } 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_receiver_.reset(); header_client_receiver_.reset(); 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 = net::SiteForCookies::FromUrl(redirect_url_); auto head = network::mojom::URLResponseHead::New(); 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 (factory_->browser_context_->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_ = std::move(head); ContinueToBeforeRedirect(redirect_info, net::OK); } void ProxyingURLLoaderFactory::InProgressRequest:: HandleResponseOrRedirectHeaders(net::CompletionOnceCallback continuation) { override_headers_ = nullptr; redirect_url_ = GURL(); info_->AddResponseInfoFromResourceResponse(*current_response_); net::CompletionRepeatingCallback copyable_callback = base::AdaptCallbackForRepeating(std::move(continuation)); DCHECK(info_.has_value()); int result = factory_->web_request_api()->OnHeadersReceived( &info_.value(), request_, copyable_callback, current_response_->headers.get(), &override_headers_, &redirect_url_); 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 receiver here to prevent further client message processing. if (proxied_client_receiver_.is_bound()) proxied_client_receiver_.Pause(); return; } DCHECK_EQ(net::OK, result); copyable_callback.Run(net::OK); } void ProxyingURLLoaderFactory::InProgressRequest::OnRequestError( const network::URLLoaderCompletionStatus& status) { if (target_client_) target_client_->OnComplete(status); factory_->web_request_api()->OnErrorOccurred(&info_.value(), request_, status.error_code); // Deletes |this|. factory_->RemoveRequest(network_service_request_id_, request_id_); } ProxyingURLLoaderFactory::ProxyingURLLoaderFactory( WebRequestAPI* web_request_api, const HandlersMap& intercepted_handlers, content::BrowserContext* browser_context, int render_process_id, uint64_t* request_id_generator, std::unique_ptr navigation_ui_data, base::Optional navigation_id, network::mojom::URLLoaderFactoryRequest loader_request, mojo::PendingRemote target_factory_remote, mojo::PendingReceiver header_client_receiver, content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type) : web_request_api_(web_request_api), intercepted_handlers_(intercepted_handlers), browser_context_(browser_context), render_process_id_(render_process_id), request_id_generator_(request_id_generator), navigation_ui_data_(std::move(navigation_ui_data)), navigation_id_(std::move(navigation_id)), loader_factory_type_(loader_factory_type) { target_factory_.Bind(std::move(target_factory_remote)); target_factory_.set_disconnect_handler(base::BindOnce( &ProxyingURLLoaderFactory::OnTargetFactoryError, base::Unretained(this))); proxy_receivers_.Add(this, std::move(loader_request)); proxy_receivers_.set_disconnect_handler(base::BindRepeating( &ProxyingURLLoaderFactory::OnProxyBindingError, base::Unretained(this))); if (header_client_receiver) url_loader_header_client_receiver_.Bind(std::move(header_client_receiver)); ignore_connections_limit_domains_ = base::SplitString( base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( switches::kIgnoreConnectionsLimit), ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); } ProxyingURLLoaderFactory::~ProxyingURLLoaderFactory() = default; bool ProxyingURLLoaderFactory::ShouldIgnoreConnectionsLimit( const network::ResourceRequest& request) { for (const auto& domain : ignore_connections_limit_domains_) { if (request.url.DomainIs(domain)) { return true; } } return false; } void ProxyingURLLoaderFactory::CreateLoaderAndStart( mojo::PendingReceiver loader, int32_t routing_id, int32_t request_id, uint32_t options, const network::ResourceRequest& original_request, mojo::PendingRemote client, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) { // Take a copy so we can mutate the request. network::ResourceRequest request = original_request; if (ShouldIgnoreConnectionsLimit(request)) { request.load_flags |= net::LOAD_IGNORE_LIMITS; } // Check if user has intercepted this scheme. auto it = intercepted_handlers_.find(request.url.scheme()); if (it != intercepted_handlers_.end()) { // > it->second.second.Run( request, base::BindOnce(&ElectronURLLoaderFactory::StartLoading, std::move(loader), routing_id, request_id, options, request, std::move(client), traffic_annotation, this, it->second.first)); return; } // Intercept file:// protocol to support asar archives. if (request.url.SchemeIsFile()) { asar::CreateAsarURLLoader(request, std::move(loader), std::move(client), new net::HttpResponseHeaders("")); return; } if (!web_request_api()->HasListener()) { // Pass-through to the original factory. target_factory_->CreateLoaderAndStart( std::move(loader), routing_id, request_id, options, request, std::move(client), traffic_annotation); return; } // The request ID doesn't really matter. It just needs to be unique // per-BrowserContext so extensions can make sense of it. Note that // |network_service_request_id_| by contrast is not necessarily unique, so we // don't use it for identity here. const uint64_t web_request_id = ++(*request_id_generator_); // Notes: Chromium assumes that requests with zero-ID would never use the // "extraHeaders" code path, however in Electron requests started from // the net module would have zero-ID because they do not have renderer process // associated. if (request_id) network_request_id_to_web_request_id_.emplace(request_id, web_request_id); auto result = requests_.emplace( web_request_id, std::make_unique( this, web_request_id, routing_id, request_id, options, request, traffic_annotation, std::move(loader), std::move(client))); result.first->second->Restart(); } void ProxyingURLLoaderFactory::Clone( mojo::PendingReceiver loader_receiver) { proxy_receivers_.Add(this, std::move(loader_receiver)); } void ProxyingURLLoaderFactory::OnLoaderCreated( int32_t request_id, mojo::PendingReceiver receiver) { auto it = network_request_id_to_web_request_id_.find(request_id); if (it == network_request_id_to_web_request_id_.end()) return; auto request_it = requests_.find(it->second); DCHECK(request_it != requests_.end()); request_it->second->OnLoaderCreated(std::move(receiver)); } bool ProxyingURLLoaderFactory::IsForServiceWorkerScript() const { return loader_factory_type_ == content::ContentBrowserClient:: URLLoaderFactoryType::kServiceWorkerScript; } void ProxyingURLLoaderFactory::OnTargetFactoryError() { target_factory_.reset(); proxy_receivers_.Clear(); MaybeDeleteThis(); } void ProxyingURLLoaderFactory::OnProxyBindingError() { if (proxy_receivers_.empty()) target_factory_.reset(); MaybeDeleteThis(); } void ProxyingURLLoaderFactory::RemoveRequest(int32_t network_service_request_id, uint64_t request_id) { network_request_id_to_web_request_id_.erase(network_service_request_id); requests_.erase(request_id); MaybeDeleteThis(); } void ProxyingURLLoaderFactory::MaybeDeleteThis() { // 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; delete this; } } // namespace electron