blob: 0b92e6c682b9819b055fb17ed7173174eaff7395 [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 "content/browser/devtools/devtools_url_loader_interceptor.h"
#include "base/barrier_closure.h"
#include "base/base64.h"
#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "content/browser/devtools/protocol/network.h"
#include "content/browser/devtools/protocol/network_handler.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/loader/download_utils_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/system/data_pipe_drainer.h"
#include "net/base/load_flags.h"
#include "net/base/mime_sniffer.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/http/http_util.h"
#include "net/url_request/url_request.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "third_party/blink/public/platform/resource_request_blocked_reason.h"
namespace content {
namespace {
using RequestInterceptedCallback =
DevToolsNetworkInterceptor::RequestInterceptedCallback;
using ContinueInterceptedRequestCallback =
DevToolsNetworkInterceptor::ContinueInterceptedRequestCallback;
using GetResponseBodyForInterceptionCallback =
DevToolsNetworkInterceptor::GetResponseBodyForInterceptionCallback;
using TakeResponseBodyPipeCallback =
DevToolsNetworkInterceptor::TakeResponseBodyPipeCallback;
using Modifications = DevToolsNetworkInterceptor::Modifications;
using InterceptionStage = DevToolsNetworkInterceptor::InterceptionStage;
using protocol::Response;
using protocol::Network::AuthChallengeResponse;
using GlobalRequestId = std::tuple<int32_t, int32_t, int32_t>;
struct CreateLoaderParameters {
CreateLoaderParameters(
int32_t routing_id,
int32_t request_id,
uint32_t options,
network::ResourceRequest request,
net::MutableNetworkTrafficAnnotationTag traffic_annotation)
: routing_id(routing_id),
request_id(request_id),
options(options),
request(request),
traffic_annotation(traffic_annotation) {}
const int32_t routing_id;
const int32_t request_id;
const uint32_t options;
network::ResourceRequest request;
const net::MutableNetworkTrafficAnnotationTag traffic_annotation;
};
class BodyReader : public mojo::DataPipeDrainer::Client {
public:
explicit BodyReader(base::OnceClosure download_complete_callback)
: download_complete_callback_(std::move(download_complete_callback)) {}
void StartReading(mojo::ScopedDataPipeConsumerHandle body);
void AddCallback(
std::unique_ptr<GetResponseBodyForInterceptionCallback> callback) {
callbacks_.push_back(std::move(callback));
if (data_complete_) {
DCHECK_EQ(1UL, callbacks_.size());
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&BodyReader::DispatchBodyOnUI, std::move(callbacks_),
encoded_body_));
}
}
bool data_complete() const { return data_complete_; }
const std::string& body() const { return body_; }
void CancelWithError(std::string error) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&BodyReader::DispatchErrorOnUI, std::move(callbacks_),
std::move(error)));
}
private:
using CallbackVector =
std::vector<std::unique_ptr<GetResponseBodyForInterceptionCallback>>;
static void DispatchBodyOnUI(const CallbackVector& callbacks,
const std::string& body);
static void DispatchErrorOnUI(const CallbackVector& callbacks,
const std::string& error);
void OnDataAvailable(const void* data, size_t num_bytes) override {
DCHECK(!data_complete_);
body_.append(std::string(static_cast<const char*>(data), num_bytes));
}
void OnDataComplete() override;
std::unique_ptr<mojo::DataPipeDrainer> body_pipe_drainer_;
CallbackVector callbacks_;
base::OnceClosure download_complete_callback_;
std::string body_;
std::string encoded_body_;
bool data_complete_ = false;
};
void BodyReader::StartReading(mojo::ScopedDataPipeConsumerHandle body) {
DCHECK(!callbacks_.empty());
DCHECK(!body_pipe_drainer_);
DCHECK(!data_complete_);
body_pipe_drainer_.reset(new mojo::DataPipeDrainer(this, std::move(body)));
}
void BodyReader::OnDataComplete() {
DCHECK(!data_complete_);
data_complete_ = true;
body_pipe_drainer_.reset();
// TODO(caseq): only encode if necessary.
base::Base64Encode(body_, &encoded_body_);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&BodyReader::DispatchBodyOnUI, std::move(callbacks_),
encoded_body_));
std::move(download_complete_callback_).Run();
}
// static
void BodyReader::DispatchBodyOnUI(const CallbackVector& callbacks,
const std::string& encoded_body) {
for (const auto& cb : callbacks)
cb->sendSuccess(encoded_body, true);
}
// static
void BodyReader::DispatchErrorOnUI(const CallbackVector& callbacks,
const std::string& error) {
for (const auto& cb : callbacks)
cb->sendFailure(Response::Error(error));
}
struct ResponseMetadata {
ResponseMetadata() = default;
explicit ResponseMetadata(const network::ResourceResponseHead& head)
: head(head) {}
network::ResourceResponseHead head;
std::unique_ptr<net::RedirectInfo> redirect_info;
std::vector<uint8_t> cached_metadata;
size_t encoded_length = 0;
size_t transfer_size = 0;
network::URLLoaderCompletionStatus status;
};
class InterceptionJob : public network::mojom::URLLoaderClient,
public network::mojom::URLLoader {
public:
static InterceptionJob* FindByRequestId(
const GlobalRequestId& global_req_id) {
const auto& map = GetInterceptionJobMap();
auto it = map.find(global_req_id);
return it == map.end() ? nullptr : it->second;
}
InterceptionJob(DevToolsURLLoaderInterceptor::Impl* interceptor,
const std::string& id,
const base::UnguessableToken& frame_token,
int32_t process_id,
std::unique_ptr<CreateLoaderParameters> create_loader_params,
bool is_download,
network::mojom::URLLoaderRequest loader_request,
network::mojom::URLLoaderClientPtr client,
network::mojom::URLLoaderFactoryPtr target_factory);
void GetResponseBody(
std::unique_ptr<GetResponseBodyForInterceptionCallback> callback);
void TakeResponseBodyPipe(TakeResponseBodyPipeCallback callback);
void ContinueInterceptedRequest(
std::unique_ptr<Modifications> modifications,
std::unique_ptr<ContinueInterceptedRequestCallback> callback);
void Detach();
void OnAuthRequest(
const scoped_refptr<net::AuthChallengeInfo>& auth_info,
DevToolsURLLoaderInterceptor::HandleAuthRequestCallback callback);
private:
static std::map<GlobalRequestId, InterceptionJob*>& GetInterceptionJobMap() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
static base::NoDestructor<std::map<GlobalRequestId, InterceptionJob*>> inst;
return *inst;
}
~InterceptionJob() override {
size_t erased = GetInterceptionJobMap().erase(global_req_id_);
DCHECK_EQ(1lu, erased);
}
Response InnerContinueRequest(std::unique_ptr<Modifications> modifications);
Response ProcessAuthResponse(AuthChallengeResponse* auth_challenge_response);
Response ProcessResponseOverride(std::string response);
void ProcessRedirectByClient(const GURL& redirect_url);
void ProcessSetCookies(const net::HttpResponseHeaders& response_headers,
base::OnceClosure callback);
void SendResponseAfterCookiesSet(const std::string& response,
int header_size,
size_t body_size);
void SendResponse(const base::StringPiece& body);
void ApplyModificationsToRequest(
std::unique_ptr<Modifications> modifications);
void StartRequest();
void CancelRequest();
void Shutdown();
std::unique_ptr<InterceptedRequestInfo> BuildRequestInfo(
const network::ResourceResponseHead* head);
void NotifyClient(std::unique_ptr<InterceptedRequestInfo> request_info);
void FetchCookies(
base::OnceCallback<void(const std::vector<net::CanonicalCookie>&)>
callback);
void NotifyClientWithCookies(
std::unique_ptr<InterceptedRequestInfo> request_info,
const std::vector<net::CanonicalCookie>& cookie_list);
void ResponseBodyComplete();
bool ShouldBypassForResponse() const {
if (state_ == State::kResponseTaken)
return false;
DCHECK_EQ(!!response_metadata_, !!body_reader_);
DCHECK_EQ(state_, State::kResponseReceived);
return !response_metadata_;
}
// network::mojom::URLLoader methods
void FollowRedirect(const base::Optional<std::vector<std::string>>&
to_be_removed_request_headers,
const base::Optional<net::HttpRequestHeaders>&
modified_request_headers) override;
void ProceedWithResponse() override;
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override;
void PauseReadingBodyFromNet() override;
void ResumeReadingBodyFromNet() override;
// network::mojom::URLLoaderClient methods
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;
void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override;
void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) override;
void OnComplete(const network::URLLoaderCompletionStatus& status) override;
bool CanGetResponseBody(std::string* error_reason);
void UpdateIdAndRegister();
const std::string id_prefix_;
const GlobalRequestId global_req_id_;
const base::UnguessableToken frame_token_;
const base::TimeTicks start_ticks_;
const base::Time start_time_;
const bool report_upload_;
DevToolsURLLoaderInterceptor::Impl* interceptor_;
InterceptionStage stage_;
std::unique_ptr<CreateLoaderParameters> create_loader_params_;
const bool is_download_;
mojo::Binding<network::mojom::URLLoaderClient> client_binding_;
mojo::Binding<network::mojom::URLLoader> loader_binding_;
network::mojom::URLLoaderClientPtr client_;
network::mojom::URLLoaderPtr loader_;
network::mojom::URLLoaderFactoryPtr target_factory_;
enum State {
kNotStarted,
kRequestSent,
kRedirectReceived,
kFollowRedirect,
kAuthRequired,
kResponseReceived,
kResponseTaken,
};
State state_;
bool waiting_for_resolution_;
int redirect_count_;
std::string current_id_;
std::unique_ptr<BodyReader> body_reader_;
std::unique_ptr<ResponseMetadata> response_metadata_;
base::Optional<std::pair<net::RequestPriority, int32_t>> priority_;
DevToolsURLLoaderInterceptor::HandleAuthRequestCallback
pending_auth_callback_;
TakeResponseBodyPipeCallback pending_response_body_pipe_callback_;
base::WeakPtrFactory<InterceptionJob> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(InterceptionJob);
};
} // namespace
class DevToolsURLLoaderInterceptor::Impl
: public base::SupportsWeakPtr<DevToolsURLLoaderInterceptor::Impl> {
public:
explicit Impl(RequestInterceptedCallback callback)
: request_intercepted_callback_(callback) {}
~Impl() {
for (auto const& entry : jobs_)
entry.second->Detach();
}
void SetCookieManager(network::mojom::CookieManagerPtrInfo cookie_manager) {
cookie_manager_.Bind(std::move(cookie_manager));
}
void CreateJob(const base::UnguessableToken& frame_token,
int32_t process_id,
bool is_download,
std::unique_ptr<CreateLoaderParameters> create_params,
network::mojom::URLLoaderRequest loader_request,
network::mojom::URLLoaderClientPtr client,
network::mojom::URLLoaderFactoryPtr target_factory) {
DCHECK(!frame_token.is_empty());
static int last_id = 0;
std::string id = base::StringPrintf("interception-job-%d", ++last_id);
// This class will manage its own life time to match the loader client.
new InterceptionJob(this, std::move(id), frame_token, process_id,
std::move(create_params), is_download,
std::move(loader_request), std::move(client),
std::move(target_factory));
}
void SetPatterns(std::vector<DevToolsNetworkInterceptor::Pattern> patterns) {
patterns_ = std::move(patterns);
}
InterceptionStage GetInterceptionStage(const GURL& url,
ResourceType resource_type) const {
InterceptionStage stage = InterceptionStage::DONT_INTERCEPT;
std::string unused;
std::string url_str =
protocol::NetworkHandler::ExtractFragment(url, &unused);
for (const auto& pattern : patterns_) {
if (pattern.Matches(url_str, resource_type))
stage |= pattern.interception_stage;
}
return stage;
}
void GetResponseBody(
const std::string& interception_id,
std::unique_ptr<GetResponseBodyForInterceptionCallback> callback) {
if (InterceptionJob* job = FindJob(interception_id, &callback))
job->GetResponseBody(std::move(callback));
}
void TakeResponseBodyPipe(
const std::string& interception_id,
DevToolsNetworkInterceptor::TakeResponseBodyPipeCallback callback) {
auto it = jobs_.find(interception_id);
if (it == jobs_.end()) {
std::move(callback).Run(
protocol::Response::InvalidParams("Invalid InterceptionId."),
mojo::ScopedDataPipeConsumerHandle(), std::string());
return;
}
it->second->TakeResponseBodyPipe(std::move(callback));
}
void ContinueInterceptedRequest(
const std::string& interception_id,
std::unique_ptr<Modifications> modifications,
std::unique_ptr<ContinueInterceptedRequestCallback> callback) {
if (InterceptionJob* job = FindJob(interception_id, &callback)) {
job->ContinueInterceptedRequest(std::move(modifications),
std::move(callback));
}
}
private:
friend class content::InterceptionJob;
template <typename Callback>
InterceptionJob* FindJob(const std::string& id,
std::unique_ptr<Callback>* callback) {
auto it = jobs_.find(id);
if (it != jobs_.end())
return it->second;
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(
&Callback::sendFailure, std::move(*callback),
protocol::Response::InvalidParams("Invalid InterceptionId.")));
return nullptr;
}
void RemoveJob(const std::string& id) { jobs_.erase(id); }
void AddJob(const std::string& id, InterceptionJob* job) {
jobs_.emplace(id, job);
}
network::mojom::CookieManagerPtr::Proxy* GetCookieManager() {
return cookie_manager_.get();
}
std::map<std::string, InterceptionJob*> jobs_;
network::mojom::CookieManagerPtr cookie_manager_;
RequestInterceptedCallback request_intercepted_callback_;
std::vector<DevToolsNetworkInterceptor::Pattern> patterns_;
DISALLOW_COPY_AND_ASSIGN(Impl);
};
class DevToolsURLLoaderFactoryProxy : public network::mojom::URLLoaderFactory {
public:
DevToolsURLLoaderFactoryProxy(
const base::UnguessableToken& frame_token,
int32_t process_id,
bool is_download,
network::mojom::URLLoaderFactoryRequest loader_request,
network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
base::WeakPtr<DevToolsURLLoaderInterceptor::Impl> interceptor);
~DevToolsURLLoaderFactoryProxy() override;
private:
// network::mojom::URLLoaderFactory implementation
void CreateLoaderAndStart(network::mojom::URLLoaderRequest loader,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag&
traffic_annotation) override;
void Clone(network::mojom::URLLoaderFactoryRequest request) override;
void StartOnIO(network::mojom::URLLoaderFactoryRequest loader_request,
network::mojom::URLLoaderFactoryPtrInfo target_factory_info);
void OnProxyBindingError();
void OnTargetFactoryError();
const base::UnguessableToken frame_token_;
const int32_t process_id_;
const bool is_download_;
network::mojom::URLLoaderFactoryPtr target_factory_;
base::WeakPtr<DevToolsURLLoaderInterceptor::Impl> interceptor_;
mojo::BindingSet<network::mojom::URLLoaderFactory> bindings_;
SEQUENCE_CHECKER(sequence_checker_);
};
DevToolsURLLoaderFactoryProxy::DevToolsURLLoaderFactoryProxy(
const base::UnguessableToken& frame_token,
int32_t process_id,
bool is_download,
network::mojom::URLLoaderFactoryRequest loader_request,
network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
base::WeakPtr<DevToolsURLLoaderInterceptor::Impl> interceptor)
: frame_token_(frame_token),
process_id_(process_id),
is_download_(is_download),
interceptor_(std::move(interceptor)) {
DETACH_FROM_SEQUENCE(sequence_checker_);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&DevToolsURLLoaderFactoryProxy::StartOnIO,
base::Unretained(this), std::move(loader_request),
std::move(target_factory_info)));
}
DevToolsURLLoaderFactoryProxy::~DevToolsURLLoaderFactoryProxy() {}
void DevToolsURLLoaderFactoryProxy::CreateLoaderAndStart(
network::mojom::URLLoaderRequest loader,
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_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DevToolsURLLoaderInterceptor::Impl* interceptor = interceptor_.get();
if (!interceptor_) {
target_factory_->CreateLoaderAndStart(
std::move(loader), routing_id, request_id, options, request,
std::move(client), traffic_annotation);
return;
}
auto creation_params = std::make_unique<CreateLoaderParameters>(
routing_id, request_id, options, request, traffic_annotation);
network::mojom::URLLoaderFactoryPtr factory_clone;
target_factory_->Clone(MakeRequest(&factory_clone));
interceptor->CreateJob(frame_token_, process_id_, is_download_,
std::move(creation_params), std::move(loader),
std::move(client), std::move(factory_clone));
}
void DevToolsURLLoaderFactoryProxy::StartOnIO(
network::mojom::URLLoaderFactoryRequest loader_request,
network::mojom::URLLoaderFactoryPtrInfo target_factory_info) {
target_factory_.Bind(std::move(target_factory_info));
target_factory_.set_connection_error_handler(
base::BindOnce(&DevToolsURLLoaderFactoryProxy::OnTargetFactoryError,
base::Unretained(this)));
bindings_.AddBinding(this, std::move(loader_request));
bindings_.set_connection_error_handler(
base::BindRepeating(&DevToolsURLLoaderFactoryProxy::OnProxyBindingError,
base::Unretained(this)));
}
void DevToolsURLLoaderFactoryProxy::Clone(
network::mojom::URLLoaderFactoryRequest request) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bindings_.AddBinding(this, std::move(request));
}
void DevToolsURLLoaderFactoryProxy::OnTargetFactoryError() {
delete this;
}
void DevToolsURLLoaderFactoryProxy::OnProxyBindingError() {
if (bindings_.empty())
delete this;
}
// static
void DevToolsURLLoaderInterceptor::HandleAuthRequest(
int32_t process_id,
int32_t routing_id,
int32_t request_id,
const scoped_refptr<net::AuthChallengeInfo>& auth_info,
HandleAuthRequestCallback callback) {
GlobalRequestId req_id = std::make_tuple(process_id, routing_id, request_id);
if (auto* job = InterceptionJob::FindByRequestId(req_id))
job->OnAuthRequest(auth_info, std::move(callback));
else
std::move(callback).Run(true, base::nullopt);
}
DevToolsURLLoaderInterceptor::DevToolsURLLoaderInterceptor(
FrameTreeNode* local_root,
RequestInterceptedCallback callback)
: local_root_(local_root),
enabled_(false),
impl_(new DevToolsURLLoaderInterceptor::Impl(std::move(callback)),
base::OnTaskRunnerDeleter(
base::CreateSingleThreadTaskRunnerWithTraits(
{BrowserThread::IO}))),
weak_impl_(impl_->AsWeakPtr()) {
network::mojom::CookieManagerPtrInfo cookie_manager;
StoragePartition* storage_partition =
local_root->current_frame_host()->GetProcess()->GetStoragePartition();
storage_partition->GetNetworkContext()->GetCookieManager(
mojo::MakeRequest(&cookie_manager));
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&Impl::SetCookieManager, base::Unretained(impl_.get()),
std::move(cookie_manager)));
}
DevToolsURLLoaderInterceptor::~DevToolsURLLoaderInterceptor() {
UpdateSubresourceLoaderFactories();
};
void DevToolsURLLoaderInterceptor::UpdateSubresourceLoaderFactories() {
base::queue<FrameTreeNode*> queue;
queue.push(local_root_);
while (!queue.empty()) {
FrameTreeNode* node = queue.front();
queue.pop();
RenderFrameHostImpl* host = node->current_frame_host();
if (node != local_root_ && host->IsCrossProcessSubframe())
continue;
host->UpdateSubresourceLoaderFactories();
for (size_t i = 0; i < node->child_count(); ++i)
queue.push(node->child_at(i));
}
}
void DevToolsURLLoaderInterceptor::SetPatterns(
std::vector<DevToolsNetworkInterceptor::Pattern> patterns) {
if (enabled_ != !!patterns.size()) {
enabled_ = !!patterns.size();
UpdateSubresourceLoaderFactories();
}
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&Impl::SetPatterns, base::Unretained(impl_.get()),
std::move(patterns)));
}
void DevToolsURLLoaderInterceptor::GetResponseBody(
const std::string& interception_id,
std::unique_ptr<GetResponseBodyForInterceptionCallback> callback) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&Impl::GetResponseBody, base::Unretained(impl_.get()),
interception_id, std::move(callback)));
}
void DevToolsURLLoaderInterceptor::TakeResponseBodyPipe(
const std::string& interception_id,
DevToolsNetworkInterceptor::TakeResponseBodyPipeCallback callback) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&Impl::TakeResponseBodyPipe, base::Unretained(impl_.get()),
interception_id, std::move(callback)));
}
void DevToolsURLLoaderInterceptor::ContinueInterceptedRequest(
const std::string& interception_id,
std::unique_ptr<Modifications> modifications,
std::unique_ptr<ContinueInterceptedRequestCallback> callback) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&Impl::ContinueInterceptedRequest,
base::Unretained(impl_.get()), interception_id,
std::move(modifications), std::move(callback)));
}
bool DevToolsURLLoaderInterceptor::CreateProxyForInterception(
const base::UnguessableToken frame_token,
int process_id,
bool is_download,
network::mojom::URLLoaderFactoryRequest* request) const {
if (!enabled_)
return false;
network::mojom::URLLoaderFactoryRequest original_request =
std::move(*request);
network::mojom::URLLoaderFactoryPtrInfo target_ptr_info;
*request = MakeRequest(&target_ptr_info);
new DevToolsURLLoaderFactoryProxy(frame_token, process_id, is_download,
std::move(original_request),
std::move(target_ptr_info), weak_impl_);
return true;
}
InterceptionJob::InterceptionJob(
DevToolsURLLoaderInterceptor::Impl* interceptor,
const std::string& id,
const base::UnguessableToken& frame_token,
int process_id,
std::unique_ptr<CreateLoaderParameters> create_loader_params,
bool is_download,
network::mojom::URLLoaderRequest loader_request,
network::mojom::URLLoaderClientPtr client,
network::mojom::URLLoaderFactoryPtr target_factory)
: id_prefix_(id),
global_req_id_(
std::make_tuple(process_id,
create_loader_params->request.render_frame_id,
create_loader_params->request_id)),
frame_token_(frame_token),
start_ticks_(base::TimeTicks::Now()),
start_time_(base::Time::Now()),
report_upload_(!!create_loader_params->request.request_body),
interceptor_(interceptor),
create_loader_params_(std::move(create_loader_params)),
is_download_(is_download),
client_binding_(this),
loader_binding_(this),
client_(std::move(client)),
target_factory_(std::move(target_factory)),
state_(kNotStarted),
waiting_for_resolution_(false),
redirect_count_(0),
weak_factory_(this) {
UpdateIdAndRegister();
const network::ResourceRequest& request = create_loader_params_->request;
stage_ = interceptor_->GetInterceptionStage(
request.url, static_cast<ResourceType>(request.resource_type));
loader_binding_.Bind(std::move(loader_request));
loader_binding_.set_connection_error_handler(
base::BindOnce(&InterceptionJob::Shutdown, base::Unretained(this)));
auto& job_map = GetInterceptionJobMap();
bool inserted = job_map.emplace(global_req_id_, this).second;
DCHECK(inserted);
if (stage_ & InterceptionStage::REQUEST) {
NotifyClient(BuildRequestInfo(nullptr));
return;
}
StartRequest();
}
void InterceptionJob::UpdateIdAndRegister() {
current_id_ = id_prefix_ + base::StringPrintf(".%d", redirect_count_);
interceptor_->AddJob(current_id_, this);
}
bool InterceptionJob::CanGetResponseBody(std::string* error_reason) {
if (!(stage_ & InterceptionStage::RESPONSE)) {
*error_reason =
"Can only get response body on HeadersReceived pattern matched "
"requests.";
return false;
}
if (state_ != State::kResponseReceived || !waiting_for_resolution_) {
*error_reason =
"Can only get response body on requests captured after headers "
"received.";
return false;
}
return true;
}
void InterceptionJob::GetResponseBody(
std::unique_ptr<GetResponseBodyForInterceptionCallback> callback) {
std::string error_reason;
if (!CanGetResponseBody(&error_reason)) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&GetResponseBodyForInterceptionCallback::sendFailure,
std::move(callback),
Response::Error(std::move(error_reason))));
return;
}
if (!body_reader_) {
body_reader_ = std::make_unique<BodyReader>(base::BindOnce(
&InterceptionJob::ResponseBodyComplete, base::Unretained(this)));
client_binding_.ResumeIncomingMethodCallProcessing();
loader_->ResumeReadingBodyFromNet();
}
body_reader_->AddCallback(std::move(callback));
}
void InterceptionJob::TakeResponseBodyPipe(
TakeResponseBodyPipeCallback callback) {
std::string error_reason;
if (!CanGetResponseBody(&error_reason)) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(std::move(callback),
Response::Error(std::move(error_reason)),
mojo::ScopedDataPipeConsumerHandle(), std::string()));
return;
}
DCHECK_EQ(state_, State::kResponseReceived);
DCHECK(!!response_metadata_);
state_ = State::kResponseTaken;
pending_response_body_pipe_callback_ = std::move(callback);
client_binding_.ResumeIncomingMethodCallProcessing();
loader_->ResumeReadingBodyFromNet();
}
void InterceptionJob::ContinueInterceptedRequest(
std::unique_ptr<Modifications> modifications,
std::unique_ptr<ContinueInterceptedRequestCallback> callback) {
Response response = InnerContinueRequest(std::move(modifications));
// |this| may be destroyed at this point.
bool success = response.isSuccess();
base::OnceClosure task =
success ? base::BindOnce(&ContinueInterceptedRequestCallback::sendSuccess,
std::move(callback))
: base::BindOnce(&ContinueInterceptedRequestCallback::sendFailure,
std::move(callback), std::move(response));
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, std::move(task));
}
void InterceptionJob::Detach() {
stage_ = InterceptionStage::DONT_INTERCEPT;
interceptor_ = nullptr;
if (!waiting_for_resolution_)
return;
if (state_ == State::kAuthRequired) {
state_ = State::kRequestSent;
waiting_for_resolution_ = false;
std::move(pending_auth_callback_).Run(true, base::nullopt);
return;
}
InnerContinueRequest(std::make_unique<Modifications>());
}
Response InterceptionJob::InnerContinueRequest(
std::unique_ptr<Modifications> modifications) {
if (!waiting_for_resolution_)
return Response::Error("Invalid state for continueInterceptedRequest");
waiting_for_resolution_ = false;
if (state_ == State::kAuthRequired) {
if (!modifications->auth_challenge_response.isJust())
return Response::InvalidParams("authChallengeResponse required.");
return ProcessAuthResponse(
modifications->auth_challenge_response.fromJust());
}
if (modifications->auth_challenge_response.isJust())
return Response::InvalidParams("authChallengeResponse not expected.");
if (modifications->error_reason) {
network::URLLoaderCompletionStatus status(
modifications->error_reason.value());
status.completion_time = base::TimeTicks::Now();
if (modifications->error_reason == net::ERR_BLOCKED_BY_CLIENT) {
// So we know that these modifications originated from devtools
// (also known as inspector), and can therefore annotate the
// request. We only do this for one specific error code thus
// far, to minimize risk of breaking other usages.
status.extended_error_code =
static_cast<int>(blink::ResourceRequestBlockedReason::kInspector);
}
client_->OnComplete(status);
Shutdown();
return Response::OK();
}
if (modifications->raw_response)
return ProcessResponseOverride(std::move(*modifications->raw_response));
if (state_ == State::kFollowRedirect) {
if (modifications->modified_url.isJust()) {
CancelRequest();
// Fall through to the generic logic of re-starting the request
// at the bottom of the method.
} else {
// TODO(caseq): report error if other modifications are present.
state_ = State::kRequestSent;
loader_->FollowRedirect(base::nullopt, base::nullopt);
return Response::OK();
}
}
if (state_ == State::kRedirectReceived) {
// TODO(caseq): report error if other modifications are present.
if (modifications->modified_url.isJust()) {
std::string location = modifications->modified_url.fromJust();
CancelRequest();
auto* headers = response_metadata_->head.headers.get();
headers->RemoveHeader("location");
headers->AddHeader("location: " + location);
GURL redirect_url = create_loader_params_->request.url.Resolve(location);
if (!redirect_url.is_valid())
return Response::Error("Invalid modified URL");
ProcessRedirectByClient(redirect_url);
return Response::OK();
}
client_->OnReceiveRedirect(*response_metadata_->redirect_info,
response_metadata_->head);
return Response::OK();
}
if (body_reader_) {
if (body_reader_->data_complete())
SendResponse(body_reader_->body());
// There are read callbacks pending, so let the reader do its job and come
// back when it's done.
return Response::OK();
}
if (response_metadata_) {
if (state_ == State::kResponseTaken) {
return Response::InvalidParams(
"Unable to continue request as is after body is taken");
}
// TODO(caseq): report error if other modifications are present.
DCHECK_EQ(State::kResponseReceived, state_);
DCHECK(!body_reader_);
client_->OnReceiveResponse(response_metadata_->head);
response_metadata_.reset();
loader_->ResumeReadingBodyFromNet();
client_binding_.ResumeIncomingMethodCallProcessing();
return Response::OK();
}
DCHECK_EQ(State::kNotStarted, state_);
ApplyModificationsToRequest(std::move(modifications));
StartRequest();
return Response::OK();
}
void InterceptionJob::ApplyModificationsToRequest(
std::unique_ptr<Modifications> modifications) {
network::ResourceRequest* request = &create_loader_params_->request;
// Note this redirect is not visible to the page by design. If they want a
// visible redirect they can mock a response with a 302.
if (modifications->modified_url.isJust())
request->url = GURL(modifications->modified_url.fromJust());
if (modifications->modified_method.isJust())
request->method = modifications->modified_method.fromJust();
if (modifications->modified_post_data.isJust()) {
const std::string& post_data = modifications->modified_post_data.fromJust();
request->request_body = network::ResourceRequestBody::CreateFromBytes(
post_data.data(), post_data.size());
}
if (modifications->modified_headers.isJust()) {
request->headers.Clear();
std::unique_ptr<protocol::DictionaryValue> headers =
modifications->modified_headers.fromJust()->toValue();
for (size_t i = 0; i < headers->size(); i++) {
protocol::DictionaryValue::Entry entry = headers->at(i);
std::string value;
if (!entry.second->asString(&value))
continue;
if (base::EqualsCaseInsensitiveASCII(entry.first,
net::HttpRequestHeaders::kReferer)) {
request->referrer = GURL(value);
request->referrer_policy = net::URLRequest::NEVER_CLEAR_REFERRER;
} else {
request->headers.SetHeader(entry.first, value);
}
}
}
}
Response InterceptionJob::ProcessAuthResponse(
AuthChallengeResponse* auth_challenge_response) {
std::string response = auth_challenge_response->GetResponse();
state_ = State::kRequestSent;
if (response == AuthChallengeResponse::ResponseEnum::Default) {
std::move(pending_auth_callback_).Run(true, base::nullopt);
} else if (response == AuthChallengeResponse::ResponseEnum::CancelAuth) {
std::move(pending_auth_callback_).Run(false, base::nullopt);
} else if (response ==
AuthChallengeResponse::ResponseEnum::ProvideCredentials) {
net::AuthCredentials credentials(
base::UTF8ToUTF16(auth_challenge_response->GetUsername("")),
base::UTF8ToUTF16(auth_challenge_response->GetPassword("")));
std::move(pending_auth_callback_).Run(false, std::move(credentials));
} else {
return Response::InvalidParams("Unrecognized authChallengeResponse.");
}
return Response::OK();
}
Response InterceptionJob::ProcessResponseOverride(std::string response) {
CancelRequest();
std::string raw_headers;
int header_size =
net::HttpUtil::LocateEndOfHeaders(response.c_str(), response.size());
if (header_size == -1) {
LOG(WARNING) << "Can't find headers in result";
header_size = 0;
} else {
raw_headers =
net::HttpUtil::AssembleRawHeaders(response.c_str(), header_size);
}
CHECK_LE(static_cast<size_t>(header_size), response.size());
size_t body_size = response.size() - header_size;
response_metadata_ = std::make_unique<ResponseMetadata>();
network::ResourceResponseHead* head = &response_metadata_->head;
head->request_time = start_time_;
head->response_time = base::Time::Now();
head->headers = new net::HttpResponseHeaders(std::move(raw_headers));
head->headers->GetMimeTypeAndCharset(&head->mime_type, &head->charset);
if (head->mime_type.empty()) {
size_t bytes_to_sniff =
std::min(body_size, static_cast<size_t>(net::kMaxBytesToSniff));
net::SniffMimeType(response.data() + header_size, bytes_to_sniff,
create_loader_params_->request.url, "",
net::ForceSniffFileUrlsForHtml::kDisabled,
&head->mime_type);
head->did_mime_sniff = true;
}
head->content_length = body_size;
head->encoded_data_length = header_size;
head->encoded_body_length = 0;
head->request_start = start_ticks_;
head->response_start = base::TimeTicks::Now();
response_metadata_->transfer_size = body_size;
response_metadata_->status.completion_time = base::TimeTicks::Now();
response_metadata_->status.encoded_data_length = response.size();
response_metadata_->status.encoded_body_length = body_size;
response_metadata_->status.decoded_body_length = body_size;
base::OnceClosure continue_after_cookies_set;
std::string location;
if (head->headers->IsRedirect(&location)) {
GURL redirect_url = create_loader_params_->request.url.Resolve(location);
if (redirect_url.is_valid()) {
continue_after_cookies_set =
base::BindOnce(&InterceptionJob::ProcessRedirectByClient,
weak_factory_.GetWeakPtr(), std::move(redirect_url));
}
}
if (!continue_after_cookies_set) {
continue_after_cookies_set =
base::BindOnce(&InterceptionJob::SendResponseAfterCookiesSet,
weak_factory_.GetWeakPtr(), std::move(response),
header_size, body_size);
}
ProcessSetCookies(*head->headers, std::move(continue_after_cookies_set));
return Response::OK();
}
void InterceptionJob::SendResponseAfterCookiesSet(const std::string& response,
int header_size,
size_t body_size) {
SendResponse(base::StringPiece(response.data() + header_size, body_size));
}
void InterceptionJob::ProcessSetCookies(const net::HttpResponseHeaders& headers,
base::OnceClosure callback) {
if (create_loader_params_->request.load_flags &
net::LOAD_DO_NOT_SAVE_COOKIES) {
std::move(callback).Run();
return;
}
const base::StringPiece name("Set-Cookie");
std::string cookie_line;
size_t iter = 0;
net::CookieOptions options;
options.set_include_httponly();
std::vector<std::unique_ptr<net::CanonicalCookie>> cookies;
base::Time response_date;
if (headers.GetDateValue(&response_date))
options.set_server_time(response_date);
base::Time now = base::Time::Now();
while (headers.EnumerateHeader(&iter, name, &cookie_line)) {
std::unique_ptr<net::CanonicalCookie> cookie = net::CanonicalCookie::Create(
create_loader_params_->request.url, cookie_line, now, options);
if (cookie)
cookies.emplace_back(std::move(cookie));
}
auto on_cookie_set = base::BindRepeating(
[](base::RepeatingClosure closure, bool) { closure.Run(); },
base::BarrierClosure(cookies.size(), std::move(callback)));
for (auto& cookie : cookies) {
interceptor_->GetCookieManager()->SetCanonicalCookie(*cookie, true, true,
on_cookie_set);
}
}
void InterceptionJob::ProcessRedirectByClient(const GURL& redirect_url) {
DCHECK(redirect_url.is_valid());
const net::HttpResponseHeaders& headers = *response_metadata_->head.headers;
const network::ResourceRequest& request = create_loader_params_->request;
auto first_party_url_policy =
request.update_first_party_url_on_redirect
? net::URLRequest::FirstPartyURLPolicy::
UPDATE_FIRST_PARTY_URL_ON_REDIRECT
: net::URLRequest::FirstPartyURLPolicy::NEVER_CHANGE_FIRST_PARTY_URL;
response_metadata_->redirect_info = std::make_unique<net::RedirectInfo>(
net::RedirectInfo::ComputeRedirectInfo(
request.method, request.url, request.site_for_cookies,
first_party_url_policy, request.referrer_policy,
request.referrer.spec(), &headers, headers.response_code(),
redirect_url, false /* insecure_scheme_was_upgraded */,
true /* copy_fragment */));
client_->OnReceiveRedirect(*response_metadata_->redirect_info,
response_metadata_->head);
}
void InterceptionJob::SendResponse(const base::StringPiece& body) {
client_->OnReceiveResponse(response_metadata_->head);
// We shouldn't be able to transfer a string that big over the protocol,
// but just in case...
DCHECK_LE(body.size(), UINT32_MAX)
<< "Response bodies larger than " << UINT32_MAX << " are not supported";
mojo::DataPipe pipe(body.size());
uint32_t num_bytes = body.size();
MojoResult res = pipe.producer_handle->WriteData(body.data(), &num_bytes,
MOJO_WRITE_DATA_FLAG_NONE);
DCHECK_EQ(0u, res);
DCHECK_EQ(num_bytes, body.size());
if (!response_metadata_->cached_metadata.empty())
client_->OnReceiveCachedMetadata(response_metadata_->cached_metadata);
client_->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
if (response_metadata_->transfer_size)
client_->OnTransferSizeUpdated(response_metadata_->transfer_size);
client_->OnComplete(response_metadata_->status);
Shutdown();
}
void InterceptionJob::ResponseBodyComplete() {
if (waiting_for_resolution_)
return;
// We're here only if client has already told us to proceed with unmodified
// response.
SendResponse(body_reader_->body());
}
void InterceptionJob::StartRequest() {
DCHECK_EQ(State::kNotStarted, state_);
DCHECK(!response_metadata_);
state_ = State::kRequestSent;
network::mojom::URLLoaderClientPtr loader_client;
client_binding_.Bind(MakeRequest(&loader_client));
client_binding_.set_connection_error_handler(
base::BindOnce(&InterceptionJob::Shutdown, base::Unretained(this)));
target_factory_->CreateLoaderAndStart(
MakeRequest(&loader_), create_loader_params_->routing_id,
create_loader_params_->request_id, create_loader_params_->options,
create_loader_params_->request, std::move(loader_client),
create_loader_params_->traffic_annotation);
if (priority_)
loader_->SetPriority(priority_->first, priority_->second);
}
void InterceptionJob::CancelRequest() {
if (state_ == State::kNotStarted)
return;
client_binding_.Close();
loader_.reset();
if (body_reader_) {
body_reader_->CancelWithError(
"Another command has cancelled the fetch request");
body_reader_.reset();
}
state_ = State::kNotStarted;
}
std::unique_ptr<InterceptedRequestInfo> InterceptionJob::BuildRequestInfo(
const network::ResourceResponseHead* head) {
auto result = std::make_unique<InterceptedRequestInfo>();
result->interception_id = current_id_;
result->frame_id = frame_token_;
ResourceType resource_type =
static_cast<ResourceType>(create_loader_params_->request.resource_type);
result->resource_type = resource_type;
result->is_navigation = resource_type == RESOURCE_TYPE_MAIN_FRAME ||
resource_type == RESOURCE_TYPE_SUB_FRAME;
// TODO(caseq): merge with NetworkHandler::BuildResponse()
if (head && head->headers) {
result->http_response_status_code = head->headers->response_code();
auto headers_dict = protocol::DictionaryValue::create();
size_t iter = 0;
std::string name;
std::string value;
while (head->headers->EnumerateHeaderLines(&iter, &name, &value)) {
std::string old_value;
bool merge_with_another = headers_dict->getString(name, &old_value);
headers_dict->setString(
name, merge_with_another ? old_value + '\n' + value : value);
}
result->response_headers =
protocol::Object::fromValue(headers_dict.get(), nullptr);
}
return result;
}
void InterceptionJob::FetchCookies(
base::OnceCallback<void(const std::vector<net::CanonicalCookie>&)>
callback) {
if (create_loader_params_->request.load_flags &
net::LOAD_DO_NOT_SEND_COOKIES) {
std::move(callback).Run({});
return;
}
net::CookieOptions options;
options.set_include_httponly();
options.set_do_not_update_access_time();
const network::ResourceRequest& request = create_loader_params_->request;
// The below is a copy of the logic in URLRequestHttpJob
// Set SameSiteCookieMode according to the rules laid out in
// https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site:
//
// * Include both "strict" and "lax" same-site cookies if the request's
// |url|, |initiator|, and |site_for_cookies| all have the same
// registrable domain. Note: this also covers the case of a request
// without an initiator (only happens for browser-initiated main frame
// navigations).
//
// * Include only "lax" same-site cookies if the request's |URL| and
// |site_for_cookies| have the same registrable domain, _and_ the
// request's |method| is "safe" ("GET" or "HEAD").
//
// Note that this will generally be the case only for cross-site requests
// which target a top-level browsing context.
//
// * Include both "strict" and "lax" same-site cookies if the request is
// tagged with a flag allowing it.
// Note that this can be the case for requests initiated by extensions,
// which need to behave as though they are made by the document itself,
// but appear like cross-site ones.
//
// * Otherwise, do not include same-site cookies.
using namespace net::registry_controlled_domains;
if (SameDomainOrHost(request.url, request.site_for_cookies,
INCLUDE_PRIVATE_REGISTRIES)) {
if (!request.request_initiator ||
SameDomainOrHost(request.url,
request.request_initiator.value().GetURL(),
INCLUDE_PRIVATE_REGISTRIES) ||
request.attach_same_site_cookies) {
options.set_same_site_cookie_mode(
net::CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX);
} else if (net::HttpUtil::IsMethodSafe(request.method)) {
options.set_same_site_cookie_mode(
net::CookieOptions::SameSiteCookieMode::INCLUDE_LAX);
}
}
interceptor_->GetCookieManager()->GetCookieList(request.url, options,
std::move(callback));
}
void InterceptionJob::NotifyClient(
std::unique_ptr<InterceptedRequestInfo> request_info) {
FetchCookies(base::BindOnce(&InterceptionJob::NotifyClientWithCookies,
weak_factory_.GetWeakPtr(),
std::move(request_info)));
}
void InterceptionJob::NotifyClientWithCookies(
std::unique_ptr<InterceptedRequestInfo> request_info,
const std::vector<net::CanonicalCookie>& cookie_list) {
if (!interceptor_)
return;
std::string cookie_line;
if (!cookie_list.empty())
cookie_line = net::CanonicalCookie::BuildCookieLine(cookie_list);
request_info->network_request =
protocol::NetworkHandler::CreateRequestFromResourceRequest(
create_loader_params_->request, cookie_line);
waiting_for_resolution_ = true;
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(interceptor_->request_intercepted_callback_,
std::move(request_info)));
}
void InterceptionJob::Shutdown() {
if (interceptor_)
interceptor_->RemoveJob(current_id_);
delete this;
}
// URLLoader methods
void InterceptionJob::FollowRedirect(
const base::Optional<std::vector<std::string>>&
to_be_removed_request_headers,
const base::Optional<net::HttpRequestHeaders>& modified_request_headers) {
DCHECK(!modified_request_headers.has_value()) << "Redirect with modified "
"headers was not supported "
"yet. crbug.com/845683";
DCHECK(!waiting_for_resolution_);
network::ResourceRequest* request = &create_loader_params_->request;
const net::RedirectInfo& info = *response_metadata_->redirect_info;
request->method = info.new_method;
request->url = info.new_url;
request->site_for_cookies = info.new_site_for_cookies;
request->referrer_policy = info.new_referrer_policy;
request->referrer = GURL(info.new_referrer);
response_metadata_.reset();
if (interceptor_) {
// Pretend that each redirect hop is a new request -- this is for
// compatibilty with URLRequestJob-based interception implementation.
interceptor_->RemoveJob(current_id_);
redirect_count_++;
UpdateIdAndRegister();
stage_ = interceptor_->GetInterceptionStage(
request->url, static_cast<ResourceType>(request->resource_type));
if (stage_ & InterceptionStage::REQUEST) {
if (state_ == State::kRedirectReceived)
state_ = State::kFollowRedirect;
else
DCHECK_EQ(State::kNotStarted, state_);
NotifyClient(BuildRequestInfo(nullptr));
return;
}
}
if (state_ == State::kRedirectReceived) {
state_ = State::kRequestSent;
loader_->FollowRedirect(base::nullopt, base::nullopt);
return;
}
DCHECK_EQ(State::kNotStarted, state_);
StartRequest();
}
void InterceptionJob::ProceedWithResponse() {
NOTREACHED();
}
void InterceptionJob::SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) {
priority_ = std::make_pair(priority, intra_priority_value);
if (loader_)
loader_->SetPriority(priority, intra_priority_value);
}
void InterceptionJob::PauseReadingBodyFromNet() {
if (!body_reader_ && loader_ && state_ != State::kResponseTaken)
loader_->PauseReadingBodyFromNet();
}
void InterceptionJob::ResumeReadingBodyFromNet() {
if (!body_reader_ && loader_ && state_ != State::kResponseTaken)
loader_->ResumeReadingBodyFromNet();
}
// URLLoaderClient methods
void InterceptionJob::OnReceiveResponse(
const network::ResourceResponseHead& head) {
state_ = State::kResponseReceived;
DCHECK(!response_metadata_);
if (!(stage_ & InterceptionStage::RESPONSE)) {
client_->OnReceiveResponse(head);
return;
}
loader_->PauseReadingBodyFromNet();
client_binding_.PauseIncomingMethodCallProcessing();
response_metadata_ = std::make_unique<ResponseMetadata>(head);
auto request_info = BuildRequestInfo(&head);
const network::ResourceRequest& request = create_loader_params_->request;
request_info->is_download =
request_info->is_navigation && request.allow_download &&
(is_download_ || download_utils::IsDownload(
request.url, head.headers.get(), head.mime_type));
NotifyClient(std::move(request_info));
}
void InterceptionJob::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& head) {
DCHECK_EQ(State::kRequestSent, state_);
state_ = State::kRedirectReceived;
response_metadata_ = std::make_unique<ResponseMetadata>(head);
response_metadata_->redirect_info =
std::make_unique<net::RedirectInfo>(redirect_info);
if (!(stage_ & InterceptionStage::RESPONSE)) {
client_->OnReceiveRedirect(redirect_info, head);
return;
}
std::unique_ptr<InterceptedRequestInfo> request_info =
BuildRequestInfo(&head);
request_info->http_response_status_code = redirect_info.status_code;
request_info->redirect_url = redirect_info.new_url.spec();
NotifyClient(std::move(request_info));
}
void InterceptionJob::OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) {
if (!report_upload_)
return;
client_->OnUploadProgress(current_position, total_size, std::move(callback));
}
void InterceptionJob::OnReceiveCachedMetadata(
const std::vector<uint8_t>& data) {
if (ShouldBypassForResponse())
client_->OnReceiveCachedMetadata(data);
else
response_metadata_->cached_metadata = data;
}
void InterceptionJob::OnTransferSizeUpdated(int32_t transfer_size_diff) {
if (ShouldBypassForResponse())
client_->OnTransferSizeUpdated(transfer_size_diff);
else
response_metadata_->transfer_size += transfer_size_diff;
}
void InterceptionJob::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) {
if (pending_response_body_pipe_callback_) {
DCHECK_EQ(State::kResponseTaken, state_);
DCHECK(!body_reader_);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(std::move(pending_response_body_pipe_callback_),
Response::OK(), std::move(body),
response_metadata_->head.mime_type));
return;
}
DCHECK_EQ(State::kResponseReceived, state_);
if (ShouldBypassForResponse())
client_->OnStartLoadingResponseBody(std::move(body));
else
body_reader_->StartReading(std::move(body));
}
void InterceptionJob::OnComplete(
const network::URLLoaderCompletionStatus& status) {
// Essentially ShouldBypassForResponse(), but skip DCHECKs
// since this may be called in any state during shutdown.
if (!response_metadata_) {
client_->OnComplete(status);
Shutdown();
return;
}
response_metadata_->status = status;
// No need to listen to the channel any more, so just close it, so if the pipe
// is closed by the other end, |shutdown| isn't run.
client_binding_.Close();
loader_.reset();
}
void InterceptionJob::OnAuthRequest(
const scoped_refptr<net::AuthChallengeInfo>& auth_info,
DevToolsURLLoaderInterceptor::HandleAuthRequestCallback callback) {
DCHECK_EQ(kRequestSent, state_);
DCHECK(pending_auth_callback_.is_null());
DCHECK(!waiting_for_resolution_);
if (!(stage_ & InterceptionStage::REQUEST)) {
std::move(callback).Run(true, base::nullopt);
return;
}
state_ = State::kAuthRequired;
auto request_info = BuildRequestInfo(nullptr);
request_info->auth_challenge =
protocol::Network::AuthChallenge::Create()
.SetSource(auth_info->is_proxy
? protocol::Network::AuthChallenge::SourceEnum::Proxy
: protocol::Network::AuthChallenge::SourceEnum::Server)
.SetOrigin(auth_info->challenger.Serialize())
.SetScheme(auth_info->scheme)
.SetRealm(auth_info->realm)
.Build();
pending_auth_callback_ = std::move(callback);
NotifyClient(std::move(request_info));
}
} // namespace content