electron/shell/browser/net/proxying_url_loader_factory.cc
Electron Bot dc72f74020
chore: bump chromium to 580fe983e138952553cd6af11ee8b (master) (#23379)
* chore: bump chromium in DEPS to 5ce64b91b4d6a78c97480059f15ff6469fc0918e

* chore: bump chromium in DEPS to e74c73d0000f81b3f40a513176c8d024bba57d28

* chore: bump chromium in DEPS to 501640e650d4657ba63db65fa257e4a899168de7

* chore: bump chromium in DEPS to 00db20e1bc3d77706723a87ada3c1c647a1c37b7

* chore: update patches

* refactor: AddNewContents now takes a target_url

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2167732

* chore: SetHostCleanupFinalizationGroupCallback has been removed from V8

* refactor: use WebInputEvent::Namespace types directly

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2160523

* refactor: FollowRedirect takes in cors exempt headers now

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2129787

* refactor: printing::DuplexMode moved to mojo

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2162388

* refactor: use MessagePortDescriptor instead of raw mojo::MessagePipeHandles

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/1952124

* chore: update patches

* chore: bump chromium in DEPS to f1537676d613f3567cfb43adf577b3847fba4bc3

* chore: update patches

* refactor: service_manager::BinderMapWithContext merged into mojo::BinderMap

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2174654

* chore: unused argument removed from ReadAvailableTypes in ui::Clipboard

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2173666

* chore: bump chromium in DEPS to 949888433ab935dd6125c107226a4c9d6da9bf48

* chore: update patches

* update patches

* chore: update sysroots

* chore: bump chromium in DEPS to eaac5b5035fe189b6706e1637122e37134206059

* chore: bump chromium in DEPS to 258b54b903d33dab963adf59016691e6537f8b70

* build: update patches

* refactor: cursor.mojom and cursor_types.mojom moved to //ui/base/cursor/mojom

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2172874

* chore: DesktopWindowTreeHostLinux becomes DesktopWindowTreeHostPlatform

Refs:

* refactor: LogErrorEventDescription moved from ui to x11

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2164245

* chore: update patches

* chore: bump chromium in DEPS to bd06abcfe807d4461683479237cdd920dafa52ca

* chore: bump chromium in DEPS to 1afb0891e56f1e79d204db43ca053a46d0974511

* chore: bump chromium in DEPS to 5cb0f794bf7f155bf8c0a241b94e01c9d90c2744

* chore: bump chromium in DEPS to 37327ba3303234e1a3cd3310ca11a68e81b95123

* update patches

* remove ClientSideDetectionService from browser_process

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2175320

* refactor: shuttle cursor changed event to WebContentsObserver

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2172779

* chore: bump chromium in DEPS to 1d97904bb6936e106df13705208b73e47367c2b9

* avoid IPC crash introduced earlier in the roll

Refs: b67334e781 (diff-607f4e8f7c5686aa09af98c783925babR128)

* remove WebContentsView::SizeContents

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2188931

* chore: bump chromium in DEPS to 87066d1db0546a9de33952ba964e1e42f76f1dae

* update patches

* adapt to //content creating NetworkContexts

https://chromium-review.googlesource.com/c/chromium/src/+/2195196

* WIP temporarily ignore mojo-ified PrintHostMsg_PrintPreviewFailed

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2195343

* add checkout_pgo_profiles to DEPS

* chore: bump chromium in DEPS to a095318bec1dedf580d676f408eaeefdbd9956b1

* add blink's execution_context to electron_lib deps

Refs:
b67334e781#, https://chromium-review.googlesource.com/c/chromium/src/+/1952124

* update patches

* update patches

* refactor: use newly mojo-ified PrintPreviewFailed

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2195343
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* chore: update patches

* chore: remove NOTIFICATION_EXTENSIONS_READY_DEPRECATED

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2199874

* avoid a call to Profile::FromBrowserContext in printing

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2195757

* chore: bump chromium in DEPS to 8f5c6a46861a991e12ffd80554b6bd41a11b0b65

* chore: bump chromium in DEPS to ab66134739ff3b6bdb8de53bbc58a97d1b7b5d8a

* chore: bump chromium in DEPS to dd08df9f6c8d6198f0a7b931ca9e845e27dae033

* chore: update patches

* [printing] Mojofy PrintHostMsg_PrintPreviewCancelled

https://chromium-review.googlesource.com/c/chromium/src/+/2198331

* [printing] Mojofy PrintHostMsg_PrintPreviewInvalidPrinterSettings

https://chromium-review.googlesource.com/c/chromium/src/+/2200556

* [printing] Mojofy PrintHostMsg_DidPrepareDocumentForPreview

https://chromium-review.googlesource.com/c/chromium/src/+/2201496

* fix: inherit base network context params

* fix: use message handle api specific to embedders

* update patches

* chore: update v8 headers

* fixup! avoid a call to Profile::FromBrowserContext in printing

* chore: bump chromium in DEPS to f198bc3276828d992b6202d75a9da6a8363164ea

* chore: update patches

* Remove cors_exempt_headers.h

https://chromium-review.googlesource.com/c/chromium/src/+/2203759

* Supply speech recognition sandbox from service_sandbox_type.h

https://chromium-review.googlesource.com/c/chromium/src/+/2206918

* [content] Remove unused methods from WebContents

https://chromium-review.googlesource.com/c/chromium/src/+/2199113

* [XProto] Replace XAtom with x11::Atom

https://chromium-review.googlesource.com/c/chromium/src/+/2202789

* chore: bump chromium in DEPS to 5df37fabaaed09f3ea511f4ef49203d8ebee7b68

* [UI] Support secondary labels in dropdown menus

https://chromium-review.googlesource.com/c/chromium/src/+/2208976

* chore: update patches

* refactor: GetSublabelAt => GetSecondaryLabelAt

* Revert "[printing] Mojofy PrintHostMsg_DidPrepareDocumentForPreview"

This reverts commit 44ed0892a0c81716229638f17253bbb2cf8f037d.

https://chromium-review.googlesource.com/c/chromium/src/+/2208778

* Unnest CanonicalCookie::CookieInclusionStatus

https://chromium-review.googlesource.com/c/chromium/src/+/2203171

* update print-to-pdf.html with latest html data

https://bugs.chromium.org/p/chromium/issues/detail?id=1085667

* chore: bump chromium in DEPS to 5dfa8d5f706580fe983e138952553cd6af11ee8b

* fix crash in x server when setting window icon

* chore: update patches

* Use a normal return value for WebContentsView::GetContainerBounds.

https://chromium-review.googlesource.com/c/chromium/src/+/2212481

* fix build on windows

* chore: bump chromium in DEPS to 9d15054f4cba060901e43deecf74002f11f47be3

* chore: update patches

* [printing] Mojofy PrintHostMsg_CheckForCancel

https://chromium-review.googlesource.com/c/chromium/src/+/2212181

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
Co-authored-by: Electron Bot <anonymous@electronjs.org>
Co-authored-by: Andy Locascio <andy@slack-corp.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
2020-05-26 13:06:26 -07:00

924 lines
36 KiB
C++

// 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 <utility>
#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<network::mojom::URLLoader> loader_receiver,
mojo::PendingRemote<network::mojom::URLLoaderClient> 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<void(int)> 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<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const base::Optional<GURL>& 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,
modified_cors_exempt_headers, new_url);
} else {
auto params = std::make_unique<FollowRedirectParams>();
params->removed_headers = removed_headers;
params->modified_headers = modified_headers;
params->modified_cors_exempt_headers = modified_cors_exempt_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<network::mojom::TrustedHeaderClient> 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<net::HttpResponseHeaders>(headers);
current_response_->remote_endpoint = remote_endpoint;
HandleResponseOrRedirectHeaders(
base::BindOnce(&InProgressRequest::ContinueToHandleOverrideHeaders,
weak_factory_.GetWeakPtr()));
}
void ProxyingURLLoaderFactory::OnLoaderForCorsPreflightCreated(
const network::ResourceRequest& request,
mojo::PendingReceiver<network::mojom::TrustedHeaderClient> 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<InProgressRequest>(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::string>(), std::set<std::string>(),
net::OK);
}
void ProxyingURLLoaderFactory::InProgressRequest::ContinueToSendHeaders(
const std::set<std::string>& removed_headers,
const std::set<std::string>& 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_->modified_cors_exempt_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<std::string> 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::HttpResponseHeaders>(
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<extensions::ExtensionNavigationUIData> navigation_ui_data,
base::Optional<int64_t> navigation_id,
network::mojom::URLLoaderFactoryRequest loader_request,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_remote,
mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient>
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<network::mojom::URLLoader> loader,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& original_request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
// Take a copy so we can mutate the request.
network::ResourceRequest request = original_request;
if (ShouldIgnoreConnectionsLimit(request)) {
request.priority = net::RequestPriority::MAXIMUM_PRIORITY;
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()) {
// <scheme, <type, handler>>
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;
}
// The loader of ServiceWorker forbids loading scripts from file:// URLs, and
// Chromium does not provide a way to override this behavior. So in order to
// make ServiceWorker work with file:// URLs, we have to intercept its
// requests here.
if (IsForServiceWorkerScript() && 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<InProgressRequest>(
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<network::mojom::URLLoaderFactory> loader_receiver) {
proxy_receivers_.Add(this, std::move(loader_receiver));
}
void ProxyingURLLoaderFactory::OnLoaderCreated(
int32_t request_id,
mojo::PendingReceiver<network::mojom::TrustedHeaderClient> 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