blob: 6fe62c5c137fd1a4818db2e2b6b48b486985af39 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/signin/chrome_signin_proxying_url_loader_factory.h"
#include "base/barrier_closure.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/supports_user_data.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/chrome_signin_helper.h"
#include "chrome/browser/signin/header_modification_delegate.h"
#include "chrome/browser/signin/header_modification_delegate_impl.h"
#include "components/signin/core/browser/signin_header_helper.h"
#include "components/signin/public/base/signin_switches.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
#include "extensions/buildflags/buildflags.h"
#include "google_apis/gaia/gaia_urls.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "net/base/net_errors.h"
#include "services/network/public/cpp/record_ontransfersizeupdate_utils.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/fetch_api.mojom-shared.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/android/tab_web_contents_delegate_android.h"
#endif
namespace signin {
namespace {
// User data key for BrowserContextData.
const void* const kBrowserContextUserDataKey = &kBrowserContextUserDataKey;
// Owns all of the ProxyingURLLoaderFactorys for a given Profile.
class BrowserContextData : public base::SupportsUserData::Data {
public:
BrowserContextData(const BrowserContextData&) = delete;
BrowserContextData& operator=(const BrowserContextData&) = delete;
~BrowserContextData() override {}
static void StartProxying(Profile* profile,
content::WebContents::Getter web_contents_getter,
network::URLLoaderFactoryBuilder& factory_builder) {
auto* self = static_cast<BrowserContextData*>(
profile->GetUserData(kBrowserContextUserDataKey));
if (!self) {
self = new BrowserContextData();
profile->SetUserData(kBrowserContextUserDataKey, base::WrapUnique(self));
}
#if BUILDFLAG(IS_ANDROID)
bool is_custom_tab = false;
content::WebContents* web_contents = web_contents_getter.Run();
if (web_contents) {
auto* delegate =
TabAndroid::FromWebContents(web_contents)
? static_cast<android::TabWebContentsDelegateAndroid*>(
web_contents->GetDelegate())
: nullptr;
is_custom_tab = delegate && delegate->IsCustomTab();
}
auto delegate = std::make_unique<HeaderModificationDelegateImpl>(
profile, /*incognito_enabled=*/!is_custom_tab);
#else
auto delegate = std::make_unique<HeaderModificationDelegateImpl>(profile);
#endif
auto proxy = std::make_unique<ProxyingURLLoaderFactory>(
std::move(delegate), std::move(web_contents_getter), factory_builder,
base::BindOnce(&BrowserContextData::RemoveProxy,
self->weak_factory_.GetWeakPtr()));
self->proxies_.emplace(std::move(proxy));
}
void RemoveProxy(ProxyingURLLoaderFactory* proxy) {
auto it = proxies_.find(proxy);
DCHECK(it != proxies_.end());
proxies_.erase(it);
}
private:
BrowserContextData() {}
std::set<std::unique_ptr<ProxyingURLLoaderFactory>, base::UniquePtrComparator>
proxies_;
base::WeakPtrFactory<BrowserContextData> weak_factory_{this};
};
} // namespace
class ProxyingURLLoaderFactory::InProgressRequest
: public network::mojom::URLLoader,
public network::mojom::URLLoaderClient,
public base::SupportsUserData {
public:
InProgressRequest(
ProxyingURLLoaderFactory* factory,
mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation);
InProgressRequest(const InProgressRequest&) = delete;
InProgressRequest& operator=(const InProgressRequest&) = delete;
~InProgressRequest() override {
if (destruction_callback_)
std::move(destruction_callback_).Run();
}
// network::mojom::URLLoader:
void FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const std::optional<GURL>& new_url) override;
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override {
target_loader_->SetPriority(priority, intra_priority_value);
}
void PauseReadingBodyFromNet() override {
target_loader_->PauseReadingBodyFromNet();
}
void ResumeReadingBodyFromNet() override {
target_loader_->ResumeReadingBodyFromNet();
}
// network::mojom::URLLoaderClient:
void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override {
target_client_->OnReceiveEarlyHints(std::move(early_hints));
}
void OnReceiveResponse(
network::mojom::URLResponseHeadPtr head,
mojo::ScopedDataPipeConsumerHandle body,
std::optional<mojo_base::BigBuffer> cached_metadata) override;
void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr head) override;
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) override {
target_client_->OnUploadProgress(current_position, total_size,
std::move(callback));
}
void OnTransferSizeUpdated(int32_t transfer_size_diff) override {
network::RecordOnTransferSizeUpdatedUMA(
network::OnTransferSizeUpdatedFrom::kProxyingURLLoaderFactory);
target_client_->OnTransferSizeUpdated(transfer_size_diff);
}
void OnComplete(const network::URLLoaderCompletionStatus& status) override {
target_client_->OnComplete(status);
}
private:
class ProxyRequestAdapter;
class ProxyResponseAdapter;
void OnBindingsClosed() {
// Destroys |this|.
factory_->RemoveRequest(this);
}
// Back pointer to the factory which owns this class.
const raw_ptr<ProxyingURLLoaderFactory> factory_;
// Information about the current request.
GURL request_url_;
GURL response_url_;
// Refers to the "last" referrer in the redirect chain.
GURL referrer_;
// The origin that initiated the request. May be empty for browser-initiated
// requests. See network::ResourceRequest::request_initiator for details.
std::optional<url::Origin> request_initiator_;
net::HttpRequestHeaders headers_;
net::HttpRequestHeaders cors_exempt_headers_;
net::RedirectInfo redirect_info_;
const network::mojom::RequestDestination request_destination_;
const bool is_outermost_main_frame_;
const bool is_fetch_like_api_;
base::OnceClosure destruction_callback_;
// Messages received by |client_receiver_| are forwarded to |target_client_|.
mojo::Receiver<network::mojom::URLLoaderClient> client_receiver_{this};
mojo::Remote<network::mojom::URLLoaderClient> target_client_;
// Messages received by |loader_receiver_| are forwarded to |target_loader_|.
mojo::Receiver<network::mojom::URLLoader> loader_receiver_;
mojo::Remote<network::mojom::URLLoader> target_loader_;
};
class ProxyingURLLoaderFactory::InProgressRequest::ProxyRequestAdapter
: public ChromeRequestAdapter {
public:
// Does not take |modified_cors_exempt_headers| just because we don't have a
// use-case to modify it in this class now.
ProxyRequestAdapter(InProgressRequest* in_progress_request,
const net::HttpRequestHeaders& original_headers,
net::HttpRequestHeaders* modified_headers,
std::vector<std::string>* removed_headers)
: ChromeRequestAdapter(in_progress_request->request_url_,
original_headers,
modified_headers,
removed_headers),
in_progress_request_(in_progress_request) {
DCHECK(in_progress_request_);
}
ProxyRequestAdapter(const ProxyRequestAdapter&) = delete;
ProxyRequestAdapter& operator=(const ProxyRequestAdapter&) = delete;
~ProxyRequestAdapter() override = default;
content::WebContents::Getter GetWebContentsGetter() const override {
return in_progress_request_->factory_->web_contents_getter_;
}
network::mojom::RequestDestination GetRequestDestination() const override {
return in_progress_request_->request_destination_;
}
bool IsOutermostMainFrame() const override {
return in_progress_request_->is_outermost_main_frame_;
}
bool IsFetchLikeAPI() const override {
return in_progress_request_->is_fetch_like_api_;
}
GURL GetReferrer() const override { return in_progress_request_->referrer_; }
void SetDestructionCallback(base::OnceClosure closure) override {
if (!in_progress_request_->destruction_callback_)
in_progress_request_->destruction_callback_ = std::move(closure);
}
private:
const raw_ptr<InProgressRequest> in_progress_request_;
};
class ProxyingURLLoaderFactory::InProgressRequest::ProxyResponseAdapter
: public ResponseAdapter {
public:
ProxyResponseAdapter(InProgressRequest* in_progress_request,
net::HttpResponseHeaders* headers)
: in_progress_request_(in_progress_request), headers_(headers) {
DCHECK(in_progress_request_);
DCHECK(headers_);
}
ProxyResponseAdapter(const ProxyResponseAdapter&) = delete;
ProxyResponseAdapter& operator=(const ProxyResponseAdapter&) = delete;
~ProxyResponseAdapter() override = default;
// signin::ResponseAdapter
content::WebContents::Getter GetWebContentsGetter() const override {
return in_progress_request_->factory_->web_contents_getter_;
}
bool IsOutermostMainFrame() const override {
return in_progress_request_->is_outermost_main_frame_;
}
GURL GetUrl() const override { return in_progress_request_->response_url_; }
std::optional<url::Origin> GetRequestInitiator() const override {
return in_progress_request_->request_initiator_;
}
const net::HttpResponseHeaders* GetHeaders() const override {
return headers_;
}
void RemoveHeader(const std::string& name) override {
headers_->RemoveHeader(name);
}
base::SupportsUserData::Data* GetUserData(const void* key) const override {
return in_progress_request_->GetUserData(key);
}
void SetUserData(
const void* key,
std::unique_ptr<base::SupportsUserData::Data> data) override {
in_progress_request_->SetUserData(key, std::move(data));
}
private:
const raw_ptr<InProgressRequest, DanglingUntriaged> in_progress_request_;
const raw_ptr<net::HttpResponseHeaders, DanglingUntriaged> headers_;
};
ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
ProxyingURLLoaderFactory* factory,
mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
: factory_(factory),
request_url_(request.url),
response_url_(request.url),
referrer_(request.referrer),
request_initiator_(request.request_initiator),
request_destination_(request.destination),
is_outermost_main_frame_(request.is_outermost_main_frame),
is_fetch_like_api_(request.is_fetch_like_api),
target_client_(std::move(client)),
loader_receiver_(this, std::move(loader_receiver)) {
mojo::PendingRemote<network::mojom::URLLoaderClient> proxy_client =
client_receiver_.BindNewPipeAndPassRemote();
net::HttpRequestHeaders modified_headers;
std::vector<std::string> removed_headers;
ProxyRequestAdapter adapter(this, request.headers, &modified_headers,
&removed_headers);
factory_->delegate_->ProcessRequest(&adapter, GURL() /* redirect_url */);
if (modified_headers.IsEmpty() && removed_headers.empty()) {
factory_->target_factory_->CreateLoaderAndStart(
target_loader_.BindNewPipeAndPassReceiver(), request_id, options,
request, std::move(proxy_client), traffic_annotation);
// We need to keep a full copy of the request headers in case there is a
// redirect and the request headers need to be modified again.
headers_ = request.headers;
cors_exempt_headers_ = request.cors_exempt_headers;
} else {
network::ResourceRequest request_copy = request;
request_copy.headers.MergeFrom(modified_headers);
for (const std::string& name : removed_headers) {
request_copy.headers.RemoveHeader(name);
request_copy.cors_exempt_headers.RemoveHeader(name);
}
factory_->target_factory_->CreateLoaderAndStart(
target_loader_.BindNewPipeAndPassReceiver(), request_id, options,
request_copy, std::move(proxy_client), traffic_annotation);
headers_.Swap(&request_copy.headers);
cors_exempt_headers_.Swap(&request_copy.cors_exempt_headers);
}
base::RepeatingClosure closure = base::BarrierClosure(
2, base::BindOnce(&InProgressRequest::OnBindingsClosed,
base::Unretained(this)));
loader_receiver_.set_disconnect_handler(closure);
client_receiver_.set_disconnect_handler(closure);
}
void ProxyingURLLoaderFactory::InProgressRequest::FollowRedirect(
const std::vector<std::string>& removed_headers_ext,
const net::HttpRequestHeaders& modified_headers_ext,
const net::HttpRequestHeaders& modified_cors_exempt_headers_ext,
const std::optional<GURL>& opt_new_url) {
std::vector<std::string> removed_headers = removed_headers_ext;
net::HttpRequestHeaders modified_headers = modified_headers_ext;
net::HttpRequestHeaders modified_cors_exempt_headers =
modified_cors_exempt_headers_ext;
ProxyRequestAdapter adapter(this, headers_, &modified_headers,
&removed_headers);
factory_->delegate_->ProcessRequest(&adapter, redirect_info_.new_url);
headers_.MergeFrom(modified_headers);
cors_exempt_headers_.MergeFrom(modified_cors_exempt_headers);
for (const std::string& name : removed_headers) {
headers_.RemoveHeader(name);
cors_exempt_headers_.RemoveHeader(name);
}
target_loader_->FollowRedirect(removed_headers, modified_headers,
modified_cors_exempt_headers, opt_new_url);
request_url_ = redirect_info_.new_url;
referrer_ = GURL(redirect_info_.new_referrer);
}
void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveResponse(
network::mojom::URLResponseHeadPtr head,
mojo::ScopedDataPipeConsumerHandle body,
std::optional<mojo_base::BigBuffer> cached_metadata) {
// Even though |head| is const we can get a non-const pointer to the headers
// and modifications we made are passed to the target client.
ProxyResponseAdapter adapter(this, head->headers.get());
factory_->delegate_->ProcessResponse(&adapter, GURL() /* redirect_url */);
target_client_->OnReceiveResponse(std::move(head), std::move(body),
std::move(cached_metadata));
}
void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr head) {
// Even though |head| is const we can get a non-const pointer to the headers
// and modifications we made are passed to the target client.
ProxyResponseAdapter adapter(this, head->headers.get());
factory_->delegate_->ProcessResponse(&adapter, redirect_info.new_url);
target_client_->OnReceiveRedirect(redirect_info, std::move(head));
// The request URL returned by ProxyResponseAdapter::GetURL() is updated
// immediately but the URL and referrer
redirect_info_ = redirect_info;
response_url_ = redirect_info.new_url;
}
ProxyingURLLoaderFactory::ProxyingURLLoaderFactory(
std::unique_ptr<HeaderModificationDelegate> delegate,
content::WebContents::Getter web_contents_getter,
network::URLLoaderFactoryBuilder& factory_builder,
DisconnectCallback on_disconnect) {
DCHECK(proxy_receivers_.empty());
DCHECK(!delegate_);
DCHECK(!web_contents_getter_);
DCHECK(!on_disconnect_);
auto [loader_receiver, target_factory] = factory_builder.Append();
DCHECK(!target_factory_.is_bound());
delegate_ = std::move(delegate);
web_contents_getter_ = std::move(web_contents_getter);
on_disconnect_ = std::move(on_disconnect);
target_factory_.Bind(std::move(target_factory));
target_factory_.set_disconnect_handler(base::BindOnce(
&ProxyingURLLoaderFactory::OnTargetFactoryError, base::Unretained(this)));
proxy_receivers_.Add(this, std::move(loader_receiver));
proxy_receivers_.set_disconnect_handler(base::BindRepeating(
&ProxyingURLLoaderFactory::OnProxyBindingError, base::Unretained(this)));
}
ProxyingURLLoaderFactory::~ProxyingURLLoaderFactory() = default;
// static
void ProxyingURLLoaderFactory::MaybeProxyRequest(
content::RenderFrameHost* render_frame_host,
bool is_navigation,
const url::Origin& request_initiator,
network::URLLoaderFactoryBuilder& factory_builder) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Navigation requests are handled using signin::URLLoaderThrottle.
if (is_navigation)
return;
if (!render_frame_host)
return;
// This proxy should only be installed for subresource requests from a frame
// that is rendering the GAIA signon realm.
if (request_initiator != GaiaUrls::GetInstance()->gaia_origin())
return;
auto* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
auto* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
if (profile->IsOffTheRecord()) {
#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
if (!switches::IsBoundSessionCredentialsEnabled(profile->GetPrefs())) {
return;
}
#else
return;
#endif
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Most requests from guest web views are ignored.
if (HeaderModificationDelegateImpl::ShouldIgnoreGuestWebViewRequest(
web_contents)) {
return;
}
#endif
auto web_contents_getter =
base::BindRepeating(&content::WebContents::FromFrameTreeNodeId,
render_frame_host->GetFrameTreeNodeId());
BrowserContextData::StartProxying(profile, std::move(web_contents_getter),
factory_builder);
}
void ProxyingURLLoaderFactory::CreateLoaderAndStart(
mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
requests_.insert(std::make_unique<InProgressRequest>(
this, std::move(loader_receiver), request_id, options, request,
std::move(client), traffic_annotation));
}
void ProxyingURLLoaderFactory::Clone(
mojo::PendingReceiver<network::mojom::URLLoaderFactory> loader_receiver) {
proxy_receivers_.Add(this, std::move(loader_receiver));
}
void ProxyingURLLoaderFactory::OnTargetFactoryError() {
// Stop calls to CreateLoaderAndStart() when |target_factory_| is invalid.
target_factory_.reset();
proxy_receivers_.Clear();
MaybeDestroySelf();
}
void ProxyingURLLoaderFactory::OnProxyBindingError() {
if (proxy_receivers_.empty())
target_factory_.reset();
MaybeDestroySelf();
}
void ProxyingURLLoaderFactory::RemoveRequest(InProgressRequest* request) {
auto it = requests_.find(request);
DCHECK(it != requests_.end());
requests_.erase(it);
MaybeDestroySelf();
}
void ProxyingURLLoaderFactory::MaybeDestroySelf() {
// 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|.
std::move(on_disconnect_).Run(this);
}
} // namespace signin