зеркало из https://github.com/electron/electron.git
944 строки
36 KiB
C++
944 строки
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 <memory>
|
|
#include <utility>
|
|
|
|
#include "base/bind.h"
|
|
#include "base/callback_helpers.h"
|
|
#include "base/command_line.h"
|
|
#include "base/strings/string_split.h"
|
|
#include "base/strings/string_util.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "content/public/browser/browser_context.h"
|
|
#include "extensions/browser/extension_navigation_ui_data.h"
|
|
#include "net/base/completion_repeating_callback.h"
|
|
#include "net/base/load_flags.h"
|
|
#include "net/http/http_response_headers.h"
|
|
#include "net/http/http_status_code.h"
|
|
#include "net/http/http_util.h"
|
|
#include "net/url_request/redirect_info.h"
|
|
#include "services/metrics/public/cpp/ukm_source_id.h"
|
|
#include "services/network/public/cpp/features.h"
|
|
#include "services/network/public/mojom/early_hints.mojom.h"
|
|
#include "shell/browser/net/asar/asar_url_loader.h"
|
|
#include "shell/common/options_switches.h"
|
|
#include "url/origin.h"
|
|
|
|
namespace electron {
|
|
|
|
ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams::
|
|
FollowRedirectParams() = default;
|
|
ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams::
|
|
~FollowRedirectParams() = default;
|
|
|
|
ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
|
|
ProxyingURLLoaderFactory* factory,
|
|
uint64_t web_request_id,
|
|
int32_t view_routing_id,
|
|
int32_t frame_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),
|
|
network_service_request_id_(network_service_request_id),
|
|
view_routing_id_(view_routing_id),
|
|
frame_routing_id_(frame_routing_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,
|
|
int32_t frame_routing_id,
|
|
const network::ResourceRequest& request)
|
|
: factory_(factory),
|
|
request_(request),
|
|
original_initiator_(request.request_initiator),
|
|
request_id_(request_id),
|
|
frame_routing_id_(frame_routing_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, absl::nullopt);
|
|
}
|
|
if (on_headers_received_callback_) {
|
|
std::move(on_headers_received_callback_)
|
|
.Run(net::ERR_ABORTED, absl::nullopt, absl::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_, frame_routing_id_,
|
|
factory_->navigation_ui_data_ ? factory_->navigation_ui_data_->DeepCopy()
|
|
: nullptr,
|
|
view_routing_id_, request_for_info, false,
|
|
!(options_ & network::mojom::kURLLoadOptionSynchronous),
|
|
factory_->IsForServiceWorkerScript(), factory_->navigation_id_,
|
|
ukm::kInvalidSourceIdObj));
|
|
|
|
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 receiver 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 absl::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::OnReceiveEarlyHints(
|
|
network::mojom::EarlyHintsPtr early_hints) {
|
|
target_client_->OnReceiveEarlyHints(std::move(early_hints));
|
|
}
|
|
|
|
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_);
|
|
}
|
|
|
|
bool ProxyingURLLoaderFactory::IsForServiceWorkerScript() const {
|
|
return loader_factory_type_ == content::ContentBrowserClient::
|
|
URLLoaderFactoryType::kServiceWorkerScript;
|
|
}
|
|
|
|
void ProxyingURLLoaderFactory::InProgressRequest::OnLoaderCreated(
|
|
mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver) {
|
|
// When CORS is involved there may be multiple network::URLLoader associated
|
|
// with this InProgressRequest, because CorsURLLoader may create a new
|
|
// network::URLLoader for the same request id in redirect handling - see
|
|
// CorsURLLoader::FollowRedirect. In such a case the old network::URLLoader
|
|
// is going to be detached fairly soon, so we don't need to take care of it.
|
|
// We need this explicit reset to avoid a DCHECK failure in mojo::Receiver.
|
|
header_client_receiver_.reset();
|
|
|
|
header_client_receiver_.Bind(std::move(receiver));
|
|
if (for_cors_preflight_) {
|
|
// In this case we don't have |target_loader_| and
|
|
// |proxied_client_receiver_|, 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, absl::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, absl::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::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 = net::HTTP_TEMPORARY_REDIRECT;
|
|
|
|
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());
|
|
|
|
// 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();
|
|
}
|
|
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::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::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_), 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 |proxied_loader_receiver_|, |proxied_client_receiver_|, or
|
|
// |header_client_receiver_|.
|
|
}
|
|
|
|
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::
|
|
ContinueToHandleOverrideHeaders(int error_code) {
|
|
if (error_code != net::OK) {
|
|
OnRequestError(network::URLLoaderCompletionStatus(error_code));
|
|
return;
|
|
}
|
|
|
|
DCHECK(on_headers_received_callback_);
|
|
absl::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.
|
|
info_->AddResponseInfoFromResourceResponse(*current_response_);
|
|
// Do not finish proxied preflight requests that require proxy auth.
|
|
// The request is not finished yet, give control back to network service
|
|
// which will start authentication process.
|
|
if (info_->response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED)
|
|
return;
|
|
// We notify the completion here, and delete |this|.
|
|
factory_->web_request_api()->OnResponseStarted(&info_.value(), request_);
|
|
factory_->web_request_api()->OnCompleted(&info_.value(), request_, net::OK);
|
|
|
|
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::
|
|
HandleResponseOrRedirectHeaders(net::CompletionOnceCallback continuation) {
|
|
override_headers_ = nullptr;
|
|
redirect_url_ = GURL();
|
|
|
|
info_->AddResponseInfoFromResourceResponse(*current_response_);
|
|
|
|
auto callback_pair = base::SplitOnceCallback(std::move(continuation));
|
|
DCHECK(info_.has_value());
|
|
int result = factory_->web_request_api()->OnHeadersReceived(
|
|
&info_.value(), request_, std::move(callback_pair.first),
|
|
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);
|
|
|
|
std::move(callback_pair.second).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,
|
|
int render_process_id,
|
|
int frame_routing_id,
|
|
int view_routing_id,
|
|
uint64_t* request_id_generator,
|
|
std::unique_ptr<extensions::ExtensionNavigationUIData> navigation_ui_data,
|
|
absl::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),
|
|
render_process_id_(render_process_id),
|
|
frame_routing_id_(frame_routing_id),
|
|
view_routing_id_(view_routing_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);
|
|
}
|
|
|
|
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 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()) {
|
|
mojo::PendingRemote<network::mojom::URLLoaderFactory> loader_remote;
|
|
this->Clone(loader_remote.InitWithNewPipeAndPassReceiver());
|
|
|
|
// <scheme, <type, handler>>
|
|
it->second.second.Run(
|
|
request, base::BindOnce(&ElectronURLLoaderFactory::StartLoading,
|
|
std::move(loader), request_id, options, request,
|
|
std::move(client), traffic_annotation,
|
|
std::move(loader_remote), 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),
|
|
base::MakeRefCounted<net::HttpResponseHeaders>(""));
|
|
return;
|
|
}
|
|
|
|
if (!web_request_api()->HasListener()) {
|
|
// Pass-through to the original factory.
|
|
target_factory_->CreateLoaderAndStart(std::move(loader), 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, view_routing_id_, frame_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));
|
|
}
|
|
|
|
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, frame_routing_id_, request)));
|
|
|
|
result.first->second->OnLoaderCreated(std::move(receiver));
|
|
result.first->second->Restart();
|
|
}
|
|
|
|
ProxyingURLLoaderFactory::~ProxyingURLLoaderFactory() = default;
|
|
|
|
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() ||
|
|
!proxy_receivers_.empty())
|
|
return;
|
|
|
|
delete this;
|
|
}
|
|
|
|
} // namespace electron
|