blob: 8cde995a2f5ce1345a0b067e67bfc0a1a40ec579 [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 "chrome/browser/signin/chrome_signin_proxying_url_loader_factory.h"
#include "base/barrier_closure.h"
#include "chrome/browser/signin/chrome_signin_helper.h"
#include "chrome/browser/signin/header_modification_delegate.h"
#include "components/signin/core/browser/signin_header_helper.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "net/base/net_errors.h"
using content::BrowserThread;
namespace signin {
class ProxyingURLLoaderFactory::InProgressRequest
: public network::mojom::URLLoader,
public network::mojom::URLLoaderClient {
public:
InProgressRequest(
ProxyingURLLoaderFactory* factory,
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);
~InProgressRequest() override {
if (destruction_callback_)
std::move(destruction_callback_).Run();
}
// network::mojom::URLLoader:
void FollowRedirect(
const base::Optional<std::vector<std::string>>& to_be_removed_headers,
const base::Optional<net::HttpRequestHeaders>& modified_request_headers)
override;
void ProceedWithResponse() override { target_loader_->ProceedWithResponse(); }
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 OnReceiveResponse(const network::ResourceResponseHead& head) override;
void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& 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(const std::vector<uint8_t>& data) override {
target_client_->OnReceiveCachedMetadata(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.
ProxyingURLLoaderFactory* const factory_;
// Information about the current request.
GURL request_url_;
GURL response_url_;
GURL referrer_origin_;
net::HttpRequestHeaders headers_;
net::RedirectInfo redirect_info_;
const content::ResourceType resource_type_;
const bool is_main_frame_;
base::OnceClosure destruction_callback_;
// Messages received by |client_binding_| are forwarded to |target_client_|.
mojo::Binding<network::mojom::URLLoaderClient> client_binding_;
network::mojom::URLLoaderClientPtr target_client_;
// Messages received by |loader_binding_| are forwarded to |target_loader_|.
mojo::Binding<network::mojom::URLLoader> loader_binding_;
network::mojom::URLLoaderPtr target_loader_;
DISALLOW_COPY_AND_ASSIGN(InProgressRequest);
};
class ProxyingURLLoaderFactory::InProgressRequest::ProxyRequestAdapter
: public ChromeRequestAdapter {
public:
ProxyRequestAdapter(InProgressRequest* in_progress_request,
const net::HttpRequestHeaders& original_headers,
net::HttpRequestHeaders* modified_headers,
std::vector<std::string>* headers_to_remove)
: ChromeRequestAdapter(nullptr),
in_progress_request_(in_progress_request),
original_headers_(original_headers),
modified_headers_(modified_headers),
headers_to_remove_(headers_to_remove) {
DCHECK(in_progress_request_);
DCHECK(modified_headers_);
DCHECK(headers_to_remove_);
}
~ProxyRequestAdapter() override = default;
// signin::ChromeRequestAdapter
bool IsMainRequestContext(ProfileIOData* io_data) override {
// This code is never reached from other request contexts.
return true;
}
content::ResourceRequestInfo::WebContentsGetter GetWebContentsGetter()
const override {
return in_progress_request_->factory_->web_contents_getter_;
}
content::ResourceType GetResourceType() const override {
return in_progress_request_->resource_type_;
}
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);
}
// signin::RequestAdapter
const GURL& GetUrl() override { return in_progress_request_->request_url_; }
bool HasHeader(const std::string& name) override {
return (original_headers_.HasHeader(name) ||
modified_headers_->HasHeader(name)) &&
!base::ContainsValue(*headers_to_remove_, name);
}
void RemoveRequestHeaderByName(const std::string& name) override {
if (!base::ContainsValue(*headers_to_remove_, name))
headers_to_remove_->push_back(name);
}
void SetExtraHeaderByName(const std::string& name,
const std::string& value) override {
modified_headers_->SetHeader(name, value);
auto it =
std::find(headers_to_remove_->begin(), headers_to_remove_->end(), name);
if (it != headers_to_remove_->end())
headers_to_remove_->erase(it);
}
private:
InProgressRequest* const in_progress_request_;
const net::HttpRequestHeaders& original_headers_;
net::HttpRequestHeaders* const modified_headers_;
std::vector<std::string>* const headers_to_remove_;
DISALLOW_COPY_AND_ASSIGN(ProxyRequestAdapter);
};
class ProxyingURLLoaderFactory::InProgressRequest::ProxyResponseAdapter
: public ResponseAdapter {
public:
ProxyResponseAdapter(const InProgressRequest* in_progress_request,
net::HttpResponseHeaders* headers)
: ResponseAdapter(nullptr),
in_progress_request_(in_progress_request),
headers_(headers) {
DCHECK(in_progress_request_);
DCHECK(headers_);
}
~ProxyResponseAdapter() override = default;
// signin::ResponseAdapter
content::ResourceRequestInfo::WebContentsGetter 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_.GetOrigin();
}
const net::HttpResponseHeaders* GetHeaders() const override {
return headers_;
}
void RemoveHeader(const std::string& name) override {
headers_->RemoveHeader(name);
}
private:
const InProgressRequest* const in_progress_request_;
net::HttpResponseHeaders* const headers_;
DISALLOW_COPY_AND_ASSIGN(ProxyResponseAdapter);
};
ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
ProxyingURLLoaderFactory* factory,
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)
: factory_(factory),
request_url_(request.url),
response_url_(request.url),
referrer_origin_(request.referrer.GetOrigin()),
resource_type_(static_cast<content::ResourceType>(request.resource_type)),
is_main_frame_(request.is_main_frame),
client_binding_(this),
target_client_(std::move(client)),
loader_binding_(this, std::move(loader_request)) {
network::mojom::URLLoaderClientPtr proxy_client;
client_binding_.Bind(mojo::MakeRequest(&proxy_client));
net::HttpRequestHeaders modified_headers;
std::vector<std::string> headers_to_remove;
ProxyRequestAdapter adapter(this, request.headers, &modified_headers,
&headers_to_remove);
factory_->delegate_->ProcessRequest(&adapter, GURL() /* redirect_url */);
if (modified_headers.IsEmpty() && headers_to_remove.empty()) {
factory_->target_factory_->CreateLoaderAndStart(
mojo::MakeRequest(&target_loader_), routing_id, 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);
} else {
network::ResourceRequest request_copy = request;
request_copy.headers.MergeFrom(modified_headers);
for (const std::string& name : headers_to_remove)
request_copy.headers.RemoveHeader(name);
factory_->target_factory_->CreateLoaderAndStart(
mojo::MakeRequest(&target_loader_), routing_id, request_id, options,
request_copy, std::move(proxy_client), traffic_annotation);
headers_.Swap(&request_copy.headers);
}
base::RepeatingClosure closure = base::BarrierClosure(
2, base::BindOnce(&InProgressRequest::OnBindingsClosed,
base::Unretained(this)));
loader_binding_.set_connection_error_handler(closure);
client_binding_.set_connection_error_handler(closure);
}
void ProxyingURLLoaderFactory::InProgressRequest::FollowRedirect(
const base::Optional<std::vector<std::string>>& opt_headers_to_remove,
const base::Optional<net::HttpRequestHeaders>& opt_modified_headers) {
std::vector<std::string> headers_to_remove;
if (opt_headers_to_remove)
headers_to_remove = *opt_headers_to_remove;
net::HttpRequestHeaders modified_headers;
if (opt_modified_headers)
modified_headers.CopyFrom(*opt_modified_headers);
ProxyRequestAdapter adapter(this, headers_, &modified_headers,
&headers_to_remove);
factory_->delegate_->ProcessRequest(&adapter, redirect_info_.new_url);
headers_.MergeFrom(modified_headers);
for (const std::string& name : headers_to_remove)
headers_.RemoveHeader(name);
target_loader_->FollowRedirect(
headers_to_remove.empty() ? base::nullopt
: base::make_optional(headers_to_remove),
modified_headers.IsEmpty() ? base::nullopt
: base::make_optional(modified_headers));
request_url_ = redirect_info_.new_url;
referrer_origin_ = GURL(redirect_info_.new_referrer).GetOrigin();
}
void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveResponse(
const network::ResourceResponseHead& 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(head);
}
void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& 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, 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() = default;
ProxyingURLLoaderFactory::~ProxyingURLLoaderFactory() = default;
// static
void ProxyingURLLoaderFactory::StartProxying(
std::unique_ptr<HeaderModificationDelegate> delegate,
content::ResourceRequestInfo::WebContentsGetter web_contents_getter,
network::mojom::URLLoaderFactoryRequest loader_request,
network::mojom::URLLoaderFactoryPtrInfo target_factory,
base::OnceClosure on_disconnect) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(proxy_bindings_.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_connection_error_handler(base::BindOnce(
&ProxyingURLLoaderFactory::OnTargetFactoryError, base::Unretained(this)));
proxy_bindings_.AddBinding(this, std::move(loader_request));
proxy_bindings_.set_connection_error_handler(base::BindRepeating(
&ProxyingURLLoaderFactory::OnProxyBindingError, base::Unretained(this)));
}
void ProxyingURLLoaderFactory::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) {
requests_.insert(std::make_unique<InProgressRequest>(
this, std::move(loader_request), routing_id, request_id, options, request,
std::move(client), traffic_annotation));
}
void ProxyingURLLoaderFactory::Clone(
network::mojom::URLLoaderFactoryRequest loader_request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
proxy_bindings_.AddBinding(this, std::move(loader_request));
}
void ProxyingURLLoaderFactory::OnTargetFactoryError() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Stop calls to CreateLoaderAndStart() when |target_factory_| is invalid.
target_factory_.reset();
proxy_bindings_.CloseAllBindings();
MaybeDestroySelf();
}
void ProxyingURLLoaderFactory::OnProxyBindingError() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (proxy_bindings_.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() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// 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| after a roundtrip to the UI thread.
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
std::move(on_disconnect_));
}
} // namespace signin