blob: 4aad402fc55fbef1cc7db4a4403ba702b65da309 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/debug/alias.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/common/url_utils.h"
#include "extensions/browser/extension_navigation_ui_data.h"
#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/features.h"
#include "third_party/blink/public/platform/resource_request_blocked_reason.h"
#include "url/origin.h"
namespace extensions {
WebRequestProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
WebRequestProxyingURLLoaderFactory* factory,
uint64_t request_id,
int32_t network_service_request_id,
int32_t routing_id,
uint32_t options,
const network::ResourceRequest& request,
bool is_download,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
network::mojom::URLLoaderRequest loader_request,
network::mojom::URLLoaderClientPtr client)
: factory_(factory),
request_(request),
original_initiator_(request.request_initiator),
is_download_(is_download),
request_id_(request_id),
network_service_request_id_(network_service_request_id),
routing_id_(routing_id),
options_(options),
traffic_annotation_(traffic_annotation),
proxied_loader_binding_(this, std::move(loader_request)),
target_client_(std::move(client)),
proxied_client_binding_(this),
has_any_extra_headers_listeners_(
network_service_request_id_ != 0 &&
ExtensionWebRequestEventRouter::GetInstance()
->HasAnyExtraHeadersListener(factory_->browser_context_)),
header_client_binding_(this),
weak_factory_(this) {
// If there is a client error, clean up the request.
target_client_.set_connection_error_handler(base::BindOnce(
&WebRequestProxyingURLLoaderFactory::InProgressRequest::OnRequestError,
weak_factory_.GetWeakPtr(),
network::URLLoaderCompletionStatus(net::ERR_ABORTED)));
}
WebRequestProxyingURLLoaderFactory::InProgressRequest::~InProgressRequest() {
// This is important to ensure that no outstanding blocking requests continue
// to reference state owned by this object.
if (info_) {
ExtensionWebRequestEventRouter::GetInstance()->OnRequestWillBeDestroyed(
factory_->browser_context_, &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, GURL());
}
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::Restart() {
request_completed_ = false;
// 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(
request_id_, factory_->render_process_id_, request_.render_frame_id,
factory_->navigation_ui_data_ ? factory_->navigation_ui_data_->DeepCopy()
: nullptr,
routing_id_, factory_->resource_context_, request_for_info, is_download_,
!(options_ & network::mojom::kURLLoadOptionSynchronous));
current_request_uses_header_client_ =
factory_->url_loader_header_client_binding_ &&
request_.url.SchemeIsHTTPOrHTTPS() && network_service_request_id_ != 0 &&
ExtensionWebRequestEventRouter::GetInstance()
->HasExtraHeadersListenerForRequest(
factory_->browser_context_, factory_->info_map_, &info_.value());
// 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 {
continuation =
base::BindRepeating(&InProgressRequest::ContinueToBeforeSendHeaders,
weak_factory_.GetWeakPtr());
}
redirect_url_ = GURL();
bool should_collapse_initiator = false;
int result = ExtensionWebRequestEventRouter::GetInstance()->OnBeforeRequest(
factory_->browser_context_, factory_->info_map_, &info_.value(),
continuation, &redirect_url_, &should_collapse_initiator);
if (result == net::ERR_BLOCKED_BY_CLIENT) {
// The request was cancelled synchronously. Dispatch an error notification
// and terminate the request.
network::URLLoaderCompletionStatus status(result);
if (should_collapse_initiator) {
status.extended_error_code = static_cast<int>(
blink::ResourceRequestBlockedReason::kCollapsedByClient);
}
OnRequestError(status);
return;
}
if (result == net::ERR_IO_PENDING) {
// One or more listeners is blocking, so the request must be paused until
// they respond. |continuation| above will be invoked asynchronously to
// continue or cancel the request.
//
// We pause the binding here to prevent further client message processing.
if (proxied_client_binding_.is_bound())
proxied_client_binding_.PauseIncomingMethodCallProcessing();
// Pause the header client, since we want to wait until OnBeforeRequest has
// finished before processing any future events.
if (header_client_binding_)
header_client_binding_.PauseIncomingMethodCallProcessing();
return;
}
DCHECK_EQ(net::OK, result);
continuation.Run(net::OK);
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_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);
if (target_loader_.is_bound()) {
target_loader_->FollowRedirect(removed_headers, modified_headers, new_url);
}
Restart();
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
ProceedWithResponse() {
if (target_loader_.is_bound())
target_loader_->ProceedWithResponse();
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::SetPriority(
net::RequestPriority priority,
int32_t intra_priority_value) {
if (target_loader_.is_bound())
target_loader_->SetPriority(priority, intra_priority_value);
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
PauseReadingBodyFromNet() {
if (target_loader_.is_bound())
target_loader_->PauseReadingBodyFromNet();
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
ResumeReadingBodyFromNet() {
if (target_loader_.is_bound())
target_loader_->ResumeReadingBodyFromNet();
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnReceiveResponse(
const network::ResourceResponseHead& head) {
on_receive_response_received_ = true;
if (current_request_uses_header_client_) {
// Use the headers we got from OnHeadersReceived as that'll contain
// Set-Cookie if it existed.
auto saved_headers = current_response_.headers;
current_response_ = head;
current_response_.headers = saved_headers;
ContinueToResponseStarted(net::OK);
} else {
current_response_ = head;
HandleResponseOrRedirectHeaders(
base::BindRepeating(&InProgressRequest::ContinueToResponseStarted,
weak_factory_.GetWeakPtr()));
}
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& head) {
if (redirect_url_ != redirect_info.new_url &&
!IsRedirectSafe(request_.url, redirect_info.new_url)) {
OnRequestError(
network::URLLoaderCompletionStatus(net::ERR_UNSAFE_REDIRECT));
return;
}
if (current_request_uses_header_client_) {
// Use the headers we got from OnHeadersReceived as that'll contain
// Set-Cookie if it existed.
auto saved_headers = current_response_.headers;
current_response_ = head;
current_response_.headers = saved_headers;
ContinueToBeforeRedirect(redirect_info, net::OK);
} else {
current_response_ = head;
HandleResponseOrRedirectHeaders(
base::BindRepeating(&InProgressRequest::ContinueToBeforeRedirect,
weak_factory_.GetWeakPtr(), redirect_info));
}
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnUploadProgress(
int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) {
target_client_->OnUploadProgress(current_position, total_size,
std::move(callback));
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
OnReceiveCachedMetadata(const std::vector<uint8_t>& data) {
target_client_->OnReceiveCachedMetadata(data);
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
OnTransferSizeUpdated(int32_t transfer_size_diff) {
target_client_->OnTransferSizeUpdated(transfer_size_diff);
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
OnStartLoadingResponseBody(mojo::ScopedDataPipeConsumerHandle body) {
// TODO(https://crbug.com/882661): Remove this once the bug is fixed.
if (!on_receive_response_sent_) {
bool on_receive_response_received = on_receive_response_received_;
base::debug::Alias(&on_receive_response_received);
DEBUG_ALIAS_FOR_GURL(request_url, request_.url)
base::debug::DumpWithoutCrashing();
}
target_client_->OnStartLoadingResponseBody(std::move(body));
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnComplete(
const network::URLLoaderCompletionStatus& status) {
if (status.error_code != net::OK) {
OnRequestError(status);
return;
}
target_client_->OnComplete(status);
ExtensionWebRequestEventRouter::GetInstance()->OnCompleted(
factory_->browser_context_, factory_->info_map_, &info_.value(),
status.error_code);
// Deletes |this|.
factory_->RemoveRequest(network_service_request_id_, request_id_);
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::HandleAuthRequest(
net::AuthChallengeInfo* auth_info,
scoped_refptr<net::HttpResponseHeaders> response_headers,
WebRequestAPI::AuthRequestCallback callback) {
DCHECK(!auth_credentials_);
// If |current_request_uses_header_client_| is true, |current_response_|
// should already hold the correct set of response headers (including
// Set-Cookie). So we don't use |response_headers| since it won't have the
// Set-Cookie headers.
if (!current_request_uses_header_client_) {
network::ResourceResponseHead head;
head.headers = response_headers;
current_response_ = head;
}
// We first need to simulate |onHeadersReceived| for the response headers
// which indicated a need to authenticate.
HandleResponseOrRedirectHeaders(base::BindRepeating(base::BindRepeating(
&InProgressRequest::ContinueAuthRequest, weak_factory_.GetWeakPtr(),
base::RetainedRef(auth_info), base::Passed(&callback))));
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnLoaderCreated(
network::mojom::TrustedHeaderClientRequest request) {
header_client_binding_.Bind(std::move(request));
}
void WebRequestProxyingURLLoaderFactory::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 WebRequestProxyingURLLoaderFactory::InProgressRequest::OnHeadersReceived(
const std::string& headers,
OnHeadersReceivedCallback callback) {
if (!current_request_uses_header_client_) {
std::move(callback).Run(net::OK, base::nullopt, GURL());
return;
}
on_headers_received_callback_ = std::move(callback);
current_response_.headers =
base::MakeRefCounted<net::HttpResponseHeaders>(headers);
HandleResponseOrRedirectHeaders(
base::BindRepeating(&InProgressRequest::ContinueToHandleOverrideHeaders,
weak_factory_.GetWeakPtr()));
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
HandleBeforeRequestRedirect() {
// The extension requested a redirect. Close the connection with the current
// URLLoader and inform the URLLoaderClient the WebRequest API generated a
// redirect. To load |redirect_url_|, a new URLLoader will be recreated
// after receiving FollowRedirect().
// Forgetting to close the connection with the current URLLoader caused
// bugs. The latter doesn't know anything about the redirect. Continuing
// the load with it gives unexpected results. See
// https://crbug.com/882661#c72.
proxied_client_binding_.Close();
header_client_binding_.Close();
target_loader_.reset();
constexpr int kInternalRedirectStatusCode = 307;
net::RedirectInfo redirect_info;
redirect_info.status_code = kInternalRedirectStatusCode;
redirect_info.new_method = request_.method;
redirect_info.new_url = redirect_url_;
redirect_info.new_site_for_cookies = redirect_url_;
network::ResourceResponseHead head;
std::string headers = base::StringPrintf(
"HTTP/1.1 %i Internal Redirect\n"
"Location: %s\n"
"Non-Authoritative-Reason: WebRequest API\n\n",
kInternalRedirectStatusCode, redirect_url_.spec().c_str());
if (base::FeatureList::IsEnabled(network::features::kOutOfBlinkCors)) {
// 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.c_str(), headers.length()));
head.encoded_data_length = 0;
current_response_ = head;
ContinueToBeforeRedirect(redirect_info, net::OK);
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
ContinueToBeforeSendHeaders(int error_code) {
if (error_code != net::OK) {
OnRequestError(network::URLLoaderCompletionStatus(error_code));
return;
}
if (!current_request_uses_header_client_ && !redirect_url_.is_empty()) {
HandleBeforeRequestRedirect();
return;
}
if (proxied_client_binding_.is_bound())
proxied_client_binding_.ResumeIncomingMethodCallProcessing();
if (request_.url.SchemeIsHTTPOrHTTPS()) {
// NOTE: While it does not appear to be documented (and in fact it may be
// intuitive), |onBeforeSendHeaders| is only dispatched for HTTP and HTTPS
// requests.
auto continuation = base::BindRepeating(
&InProgressRequest::ContinueToSendHeaders, weak_factory_.GetWeakPtr());
int result =
ExtensionWebRequestEventRouter::GetInstance()->OnBeforeSendHeaders(
factory_->browser_context_, factory_->info_map_, &info_.value(),
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 binding here to prevent further client message processing.
if (proxied_client_binding_.is_bound())
proxied_client_binding_.PauseIncomingMethodCallProcessing();
return;
}
DCHECK_EQ(net::OK, result);
}
ContinueToSendHeaders(net::OK);
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
ContinueToStartRequest(int error_code) {
if (error_code != net::OK) {
OnRequestError(network::URLLoaderCompletionStatus(error_code));
return;
}
if (current_request_uses_header_client_ && !redirect_url_.is_empty()) {
HandleBeforeRequestRedirect();
return;
}
if (proxied_client_binding_.is_bound())
proxied_client_binding_.ResumeIncomingMethodCallProcessing();
if (header_client_binding_)
header_client_binding_.ResumeIncomingMethodCallProcessing();
if (!target_loader_.is_bound() && factory_->target_factory_.is_bound()) {
// No extensions have cancelled us up to this point, so it's now OK to
// initiate the real network request.
network::mojom::URLLoaderClientPtr proxied_client;
proxied_client_binding_.Bind(mojo::MakeRequest(&proxied_client));
uint32_t options = options_;
// Even if this request does not use the header client, future redirects
// might, so we need to set the option on the loader.
if (has_any_extra_headers_listeners_)
options |= network::mojom::kURLLoadOptionUseHeaderClient;
factory_->target_factory_->CreateLoaderAndStart(
mojo::MakeRequest(&target_loader_), info_->routing_id,
network_service_request_id_, options, request_,
std::move(proxied_client), traffic_annotation_);
}
// From here the lifecycle of this request is driven by subsequent events on
// either |proxy_loader_binding_|, |proxy_client_binding_|, or
// |header_client_binding_|.
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
ContinueToSendHeaders(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);
}
if (proxied_client_binding_.is_bound())
proxied_client_binding_.ResumeIncomingMethodCallProcessing();
if (request_.url.SchemeIsHTTPOrHTTPS()) {
// NOTE: While it does not appear to be documented (and in fact it may be
// intuitive), |onSendHeaders| is only dispatched for HTTP and HTTPS
// requests.
ExtensionWebRequestEventRouter::GetInstance()->OnSendHeaders(
factory_->browser_context_, factory_->info_map_, &info_.value(),
request_.headers);
}
if (!current_request_uses_header_client_)
ContinueToStartRequest(net::OK);
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::ContinueAuthRequest(
net::AuthChallengeInfo* auth_info,
WebRequestAPI::AuthRequestCallback callback,
int error_code) {
if (error_code != net::OK) {
base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(std::move(callback), base::nullopt,
true /* should_cancel */));
return;
}
info_->AddResponseInfoFromResourceResponse(current_response_);
auto continuation =
base::BindRepeating(&InProgressRequest::OnAuthRequestHandled,
weak_factory_.GetWeakPtr(), base::Passed(&callback));
auth_credentials_.emplace();
net::NetworkDelegate::AuthRequiredResponse response =
ExtensionWebRequestEventRouter::GetInstance()->OnAuthRequired(
factory_->browser_context_, factory_->info_map_, &info_.value(),
*auth_info, continuation, &auth_credentials_.value());
// At least one extension has a blocking handler for this request, so we'll
// just wait for them to finish. |OnAuthRequestHandled()| will be invoked
// eventually.
if (response == net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_IO_PENDING)
return;
// We're not touching this auth request. Let the default browser behavior
// proceed.
DCHECK_EQ(response, net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION);
continuation.Run(response);
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
OnAuthRequestHandled(WebRequestAPI::AuthRequestCallback callback,
net::NetworkDelegate::AuthRequiredResponse response) {
if (proxied_client_binding_.is_bound())
proxied_client_binding_.ResumeIncomingMethodCallProcessing();
base::OnceClosure completion;
switch (response) {
case net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION:
// We're not touching this auth request. Let the default browser behavior
// proceed.
completion = base::BindOnce(std::move(callback), base::nullopt,
false /* should_cancel */);
break;
case net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_SET_AUTH:
completion =
base::BindOnce(std::move(callback), auth_credentials_.value(),
false /* should_cancel */);
break;
case net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_CANCEL_AUTH:
completion = base::BindOnce(std::move(callback), base::nullopt,
true /* should_cancel */);
break;
default:
NOTREACHED();
return;
}
auth_credentials_ = base::nullopt;
base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI},
std::move(completion));
}
void WebRequestProxyingURLLoaderFactory::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_;
}
}
std::move(on_headers_received_callback_).Run(net::OK, headers, redirect_url_);
override_headers_ = nullptr;
if (proxied_client_binding_)
proxied_client_binding_.ResumeIncomingMethodCallProcessing();
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
ContinueToResponseStarted(int error_code) {
if (error_code != net::OK) {
OnRequestError(network::URLLoaderCompletionStatus(error_code));
return;
}
DCHECK(!current_request_uses_header_client_ || !override_headers_);
if (override_headers_)
current_response_.headers = override_headers_;
std::string redirect_location;
if (override_headers_ && override_headers_->IsRedirect(&redirect_location)) {
// The response headers may have been overridden by an |onHeadersReceived|
// handler and may have been changed to a redirect. We handle that here
// instead of acting like regular request completion.
//
// Note that we can't actually change how the Network Service handles the
// original request at this point, so our "redirect" is really just
// generating an artificial |onBeforeRedirect| event and starting a new
// request to the Network Service. Our client shouldn't know the difference.
GURL new_url(redirect_location);
net::RedirectInfo redirect_info;
redirect_info.status_code = override_headers_->response_code();
redirect_info.new_method = request_.method;
redirect_info.new_url = new_url;
redirect_info.new_site_for_cookies = new_url;
// These will get re-bound if a new request is initiated by
// |FollowRedirect()|.
proxied_client_binding_.Close();
header_client_binding_.Close();
target_loader_.reset();
on_receive_response_received_ = false;
ContinueToBeforeRedirect(redirect_info, net::OK);
return;
}
info_->AddResponseInfoFromResourceResponse(current_response_);
proxied_client_binding_.ResumeIncomingMethodCallProcessing();
ExtensionWebRequestEventRouter::GetInstance()->OnResponseStarted(
factory_->browser_context_, factory_->info_map_, &info_.value(), net::OK);
on_receive_response_sent_ = true;
target_client_->OnReceiveResponse(current_response_);
}
void WebRequestProxyingURLLoaderFactory::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_binding_.is_bound())
proxied_client_binding_.ResumeIncomingMethodCallProcessing();
ExtensionWebRequestEventRouter::GetInstance()->OnBeforeRedirect(
factory_->browser_context_, factory_->info_map_, &info_.value(),
redirect_info.new_url);
target_client_->OnReceiveRedirect(redirect_info, current_response_);
request_.url = redirect_info.new_url;
request_.method = redirect_info.new_method;
request_.site_for_cookies = redirect_info.new_site_for_cookies;
request_.referrer = GURL(redirect_info.new_referrer);
request_.referrer_policy = redirect_info.new_referrer_policy;
// The request method can be changed to "GET". In this case we need to
// reset the request body manually.
if (request_.method == net::HttpRequestHeaders::kGetMethod)
request_.request_body = nullptr;
request_completed_ = true;
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::
HandleResponseOrRedirectHeaders(
const net::CompletionCallback& continuation) {
override_headers_ = nullptr;
redirect_url_ = GURL();
if (request_.url.SchemeIsHTTPOrHTTPS()) {
int result =
ExtensionWebRequestEventRouter::GetInstance()->OnHeadersReceived(
factory_->browser_context_, factory_->info_map_, &info_.value(),
continuation, 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 binding here to prevent further client message processing.
proxied_client_binding_.PauseIncomingMethodCallProcessing();
return;
}
DCHECK_EQ(net::OK, result);
}
continuation.Run(net::OK);
}
void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnRequestError(
const network::URLLoaderCompletionStatus& status) {
if (!request_completed_) {
target_client_->OnComplete(status);
ExtensionWebRequestEventRouter::GetInstance()->OnErrorOccurred(
factory_->browser_context_, factory_->info_map_, &info_.value(),
true /* started */, status.error_code);
}
// Deletes |this|.
factory_->RemoveRequest(network_service_request_id_, request_id_);
}
// Determines whether it is safe to redirect from |from_url| to |to_url|.
bool WebRequestProxyingURLLoaderFactory::InProgressRequest::IsRedirectSafe(
const GURL& from_url,
const GURL& to_url) {
if (to_url.SchemeIs(extensions::kExtensionScheme)) {
const Extension* extension =
factory_->info_map_->extensions().GetByID(to_url.host());
if (!extension)
return false;
return WebAccessibleResourcesInfo::IsResourceWebAccessible(extension,
to_url.path());
}
return content::IsSafeRedirectTarget(from_url, to_url);
}
WebRequestProxyingURLLoaderFactory::WebRequestProxyingURLLoaderFactory(
void* browser_context,
content::ResourceContext* resource_context,
int render_process_id,
bool is_download,
scoped_refptr<WebRequestAPI::RequestIDGenerator> request_id_generator,
std::unique_ptr<ExtensionNavigationUIData> navigation_ui_data,
InfoMap* info_map,
network::mojom::URLLoaderFactoryRequest loader_request,
network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
network::mojom::TrustedURLLoaderHeaderClientRequest header_client_request,
WebRequestAPI::ProxySet* proxies)
: browser_context_(browser_context),
resource_context_(resource_context),
render_process_id_(render_process_id),
is_download_(is_download),
request_id_generator_(std::move(request_id_generator)),
navigation_ui_data_(std::move(navigation_ui_data)),
info_map_(info_map),
url_loader_header_client_binding_(this),
proxies_(proxies),
weak_factory_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
target_factory_.Bind(std::move(target_factory_info));
target_factory_.set_connection_error_handler(
base::BindOnce(&WebRequestProxyingURLLoaderFactory::OnTargetFactoryError,
base::Unretained(this)));
proxy_bindings_.AddBinding(this, std::move(loader_request));
proxy_bindings_.set_connection_error_handler(base::BindRepeating(
&WebRequestProxyingURLLoaderFactory::OnProxyBindingError,
base::Unretained(this)));
if (header_client_request)
url_loader_header_client_binding_.Bind(std::move(header_client_request));
}
void WebRequestProxyingURLLoaderFactory::StartProxying(
void* browser_context,
content::ResourceContext* resource_context,
int render_process_id,
bool is_download,
scoped_refptr<WebRequestAPI::RequestIDGenerator> request_id_generator,
std::unique_ptr<ExtensionNavigationUIData> navigation_ui_data,
InfoMap* info_map,
network::mojom::URLLoaderFactoryRequest loader_request,
network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
network::mojom::TrustedURLLoaderHeaderClientRequest header_client_request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
auto* proxies =
WebRequestAPI::ProxySet::GetFromResourceContext(resource_context);
auto proxy = std::make_unique<WebRequestProxyingURLLoaderFactory>(
browser_context, resource_context, render_process_id, is_download,
std::move(request_id_generator), std::move(navigation_ui_data), info_map,
std::move(loader_request), std::move(target_factory_info),
std::move(header_client_request), proxies);
proxies->AddProxy(std::move(proxy));
}
void WebRequestProxyingURLLoaderFactory::CreateLoaderAndStart(
network::mojom::URLLoaderRequest loader_request,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// Make sure we are not proxying a browser initiated non-navigation request.
DCHECK(render_process_id_ != -1 || navigation_ui_data_);
// The request ID doesn't really matter in the Network Service path. 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_->Generate();
if (request_id) {
// Only requests with a non-zero request ID can have their proxy associated
// with said ID. This is necessary to support correlation against any auth
// events received by the browser. Requests with a request ID of 0 therefore
// do not support dispatching |WebRequest.onAuthRequired| events.
proxies_->AssociateProxyWithRequestId(
this, content::GlobalRequestID(render_process_id_, 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, request_id, routing_id, options,
request, is_download_, traffic_annotation,
std::move(loader_request), std::move(client)));
result.first->second->Restart();
}
void WebRequestProxyingURLLoaderFactory::Clone(
network::mojom::URLLoaderFactoryRequest loader_request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
proxy_bindings_.AddBinding(this, std::move(loader_request));
}
void WebRequestProxyingURLLoaderFactory::OnLoaderCreated(
int32_t request_id,
network::mojom::TrustedHeaderClientRequest request) {
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(request));
}
void WebRequestProxyingURLLoaderFactory::HandleAuthRequest(
net::AuthChallengeInfo* auth_info,
scoped_refptr<net::HttpResponseHeaders> response_headers,
int32_t request_id,
WebRequestAPI::AuthRequestCallback callback) {
auto it = network_request_id_to_web_request_id_.find(request_id);
if (it == network_request_id_to_web_request_id_.end()) {
base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(std::move(callback), base::nullopt,
true /* should_cancel */));
return;
}
auto request_it = requests_.find(it->second);
DCHECK(request_it != requests_.end());
request_it->second->HandleAuthRequest(auth_info, std::move(response_headers),
std::move(callback));
}
WebRequestProxyingURLLoaderFactory::~WebRequestProxyingURLLoaderFactory() =
default;
void WebRequestProxyingURLLoaderFactory::OnTargetFactoryError() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
target_factory_.reset();
proxy_bindings_.CloseAllBindings();
MaybeRemoveProxy();
}
void WebRequestProxyingURLLoaderFactory::OnProxyBindingError() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (proxy_bindings_.empty())
target_factory_.reset();
MaybeRemoveProxy();
}
void WebRequestProxyingURLLoaderFactory::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);
if (network_service_request_id) {
proxies_->DisassociateProxyWithRequestId(
this, content::GlobalRequestID(render_process_id_,
network_service_request_id));
}
MaybeRemoveProxy();
}
void WebRequestProxyingURLLoaderFactory::MaybeRemoveProxy() {
// 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;
// Deletes |this|.
proxies_->RemoveProxy(this);
}
} // namespace extensions