// 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 "chrome/browser/signin/chrome_signin_proxying_url_loader_factory.h" #include "base/barrier_closure.h" #include "base/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 "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_auth_util.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/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 defined(OS_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, mojo::PendingReceiver receiver, mojo::PendingRemote target_factory) { auto* self = static_cast( profile->GetUserData(kBrowserContextUserDataKey)); if (!self) { self = new BrowserContextData(); profile->SetUserData(kBrowserContextUserDataKey, base::WrapUnique(self)); } #if defined(OS_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( web_contents->GetDelegate()) : nullptr; is_custom_tab = delegate && delegate->IsCustomTab(); } auto delegate = std::make_unique( profile, /*incognito_enabled=*/!is_custom_tab); #else auto delegate = std::make_unique(profile); #endif auto proxy = std::make_unique( std::move(delegate), std::move(web_contents_getter), std::move(receiver), std::move(target_factory), 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, base::UniquePtrComparator> proxies_; base::WeakPtrFactory weak_factory_{this}; }; } // namespace class ProxyingURLLoaderFactory::InProgressRequest : public network::mojom::URLLoader, public network::mojom::URLLoaderClient, public base::SupportsUserData { public: InProgressRequest( ProxyingURLLoaderFactory* factory, mojo::PendingReceiver loader_receiver, int32_t request_id, uint32_t options, const network::ResourceRequest& request, mojo::PendingRemote 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& removed_headers, const net::HttpRequestHeaders& modified_headers, const net::HttpRequestHeaders& modified_cors_exempt_headers, const absl::optional& 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) 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 OnReceiveCachedMetadata(mojo_base::BigBuffer data) override { target_client_->OnReceiveCachedMetadata(std::move(data)); } void OnTransferSizeUpdated(int32_t transfer_size_diff) override { target_client_->OnTransferSizeUpdated(transfer_size_diff); } void OnStartLoadingResponseBody( mojo::ScopedDataPipeConsumerHandle body) override { target_client_->OnStartLoadingResponseBody(std::move(body)); } 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 factory_; // Information about the current request. GURL request_url_; GURL response_url_; GURL referrer_origin_; net::HttpRequestHeaders headers_; net::HttpRequestHeaders cors_exempt_headers_; net::RedirectInfo redirect_info_; const network::mojom::RequestDestination request_destination_; const bool is_main_frame_; const bool is_fetch_like_api_; base::OnceClosure destruction_callback_; // Messages received by |client_receiver_| are forwarded to |target_client_|. mojo::Receiver client_receiver_{this}; mojo::Remote target_client_; // Messages received by |loader_receiver_| are forwarded to |target_loader_|. mojo::Receiver loader_receiver_; mojo::Remote 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* 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 IsFetchLikeAPI() const override { return in_progress_request_->is_fetch_like_api_; } GURL GetReferrerOrigin() const override { return in_progress_request_->referrer_origin_; } void SetDestructionCallback(base::OnceClosure closure) override { if (!in_progress_request_->destruction_callback_) in_progress_request_->destruction_callback_ = std::move(closure); } private: const raw_ptr 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 IsMainFrame() const override { return in_progress_request_->is_main_frame_; } GURL GetOrigin() const override { return in_progress_request_->response_url_.DeprecatedGetOriginAsURL(); } 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 data) override { in_progress_request_->SetUserData(key, std::move(data)); } private: const raw_ptr in_progress_request_; const raw_ptr headers_; }; ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest( ProxyingURLLoaderFactory* factory, mojo::PendingReceiver loader_receiver, int32_t request_id, uint32_t options, const network::ResourceRequest& request, mojo::PendingRemote client, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) : factory_(factory), request_url_(request.url), response_url_(request.url), referrer_origin_(request.referrer.DeprecatedGetOriginAsURL()), request_destination_(request.destination), is_main_frame_(request.is_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 proxy_client = client_receiver_.BindNewPipeAndPassRemote(); net::HttpRequestHeaders modified_headers; std::vector 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_.CopyFrom(request.headers); cors_exempt_headers_.CopyFrom(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& removed_headers_ext, const net::HttpRequestHeaders& modified_headers_ext, const net::HttpRequestHeaders& modified_cors_exempt_headers_ext, const absl::optional& opt_new_url) { std::vector 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_origin_ = GURL(redirect_info_.new_referrer).DeprecatedGetOriginAsURL(); } void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveResponse( 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, GURL() /* redirect_url */); target_client_->OnReceiveResponse(std::move(head)); } 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::GetOrigin() is updated // immediately but the URL and referrer redirect_info_ = redirect_info; response_url_ = redirect_info.new_url; } ProxyingURLLoaderFactory::ProxyingURLLoaderFactory( std::unique_ptr delegate, content::WebContents::Getter web_contents_getter, mojo::PendingReceiver loader_receiver, mojo::PendingRemote target_factory, DisconnectCallback on_disconnect) { DCHECK(proxy_receivers_.empty()); DCHECK(!target_factory_.is_bound()); DCHECK(!delegate_); DCHECK(!web_contents_getter_); DCHECK(!on_disconnect_); 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 bool ProxyingURLLoaderFactory::MaybeProxyRequest( content::RenderFrameHost* render_frame_host, bool is_navigation, const url::Origin& request_initiator, mojo::PendingReceiver* factory_receiver) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // Navigation requests are handled using signin::URLLoaderThrottle. if (is_navigation) return false; if (!render_frame_host) return false; // This proxy should only be installed for subresource requests from a frame // that is rendering the GAIA signon realm. if (!gaia::IsGaiaSignonRealm(request_initiator.GetURL())) return false; auto* web_contents = content::WebContents::FromRenderFrameHost(render_frame_host); auto* profile = Profile::FromBrowserContext(web_contents->GetBrowserContext()); if (profile->IsOffTheRecord()) return false; #if BUILDFLAG(ENABLE_EXTENSIONS) // Most requests from guest web views are ignored. if (HeaderModificationDelegateImpl::ShouldIgnoreGuestWebViewRequest( web_contents)) { return false; } #endif auto proxied_receiver = std::move(*factory_receiver); // TODO(crbug.com/955171): Replace this with PendingRemote. mojo::PendingRemote target_factory_remote; *factory_receiver = target_factory_remote.InitWithNewPipeAndPassReceiver(); auto web_contents_getter = base::BindRepeating(&content::WebContents::FromFrameTreeNodeId, render_frame_host->GetFrameTreeNodeId()); BrowserContextData::StartProxying(profile, std::move(web_contents_getter), std::move(proxied_receiver), std::move(target_factory_remote)); return true; } void ProxyingURLLoaderFactory::CreateLoaderAndStart( mojo::PendingReceiver loader_receiver, int32_t request_id, uint32_t options, const network::ResourceRequest& request, mojo::PendingRemote client, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) { requests_.insert(std::make_unique( this, std::move(loader_receiver), request_id, options, request, std::move(client), traffic_annotation)); } void ProxyingURLLoaderFactory::Clone( mojo::PendingReceiver 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