blob: ad7578acdd6d09e39a1883e62dc7321dd5bccd19 [file] [log] [blame]
// Copyright 2017 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_request_interceptor.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/supports_user_data.h"
#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/devtools/devtools_url_interceptor_request_job.h"
#include "content/browser/devtools/protocol/network_handler.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "net/http/http_request_headers.h"
#include "net/url_request/url_request.h"
namespace content {
namespace {
const char kDevToolsURLRequestInterceptorKeyName[] =
"DevToolsURLRequestInterceptor";
class DevToolsURLRequestInterceptorUserData
: public base::SupportsUserData::Data {
public:
explicit DevToolsURLRequestInterceptorUserData(
DevToolsURLRequestInterceptor* devtools_url_request_interceptor)
: devtools_url_request_interceptor_(devtools_url_request_interceptor) {}
DevToolsURLRequestInterceptor* devtools_url_request_interceptor() const {
return devtools_url_request_interceptor_;
}
private:
DevToolsURLRequestInterceptor* devtools_url_request_interceptor_;
DISALLOW_COPY_AND_ASSIGN(DevToolsURLRequestInterceptorUserData);
};
} // namespace
DevToolsURLRequestInterceptor::DevToolsURLRequestInterceptor(
BrowserContext* browser_context)
: browser_context_(browser_context), state_(new State()) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
browser_context_->SetUserData(
kDevToolsURLRequestInterceptorKeyName,
base::MakeUnique<DevToolsURLRequestInterceptorUserData>(this));
}
DevToolsURLRequestInterceptor::~DevToolsURLRequestInterceptor() {
// The BrowserContext owns us, so we don't need to unregister
// DevToolsURLRequestInterceptorUserData explicitly.
}
net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptRequest(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return state()->MaybeCreateDevToolsURLInterceptorRequestJob(request,
network_delegate);
}
net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptRedirect(
net::URLRequest* request,
net::NetworkDelegate* network_delegate,
const GURL& location) const {
return nullptr;
}
net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptResponse(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const {
return nullptr;
}
DevToolsURLRequestInterceptor::State::State() : next_id_(0) {}
DevToolsURLRequestInterceptor::State::~State() {}
void DevToolsURLRequestInterceptor::State::ContinueInterceptedRequest(
std::string interception_id,
std::unique_ptr<Modifications> modifications,
std::unique_ptr<ContinueInterceptedRequestCallback> callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&DevToolsURLRequestInterceptor::State::
ContinueInterceptedRequestOnIoThread,
this, interception_id, base::Passed(std::move(modifications)),
base::Passed(std::move(callback))));
}
void DevToolsURLRequestInterceptor::State::ContinueInterceptedRequestOnIoThread(
std::string interception_id,
std::unique_ptr<Modifications> modifications,
std::unique_ptr<ContinueInterceptedRequestCallback> callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DevToolsURLInterceptorRequestJob* job = GetJob(interception_id);
if (!job) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(
&ContinueInterceptedRequestCallback::sendFailure,
base::Passed(std::move(callback)),
protocol::Response::InvalidParams("Invalid InterceptionId.")));
return;
}
job->ContinueInterceptedRequest(std::move(modifications),
std::move(callback));
}
DevToolsURLInterceptorRequestJob* DevToolsURLRequestInterceptor::State::
MaybeCreateDevToolsURLInterceptorRequestJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) {
// Bail out if we're not intercepting anything.
if (intercepted_render_frames_.empty()) {
DCHECK(intercepted_frame_tree_nodes_.empty());
return nullptr;
}
// Don't try to intercept blob resources.
if (request->url().SchemeIsBlob())
return nullptr;
const ResourceRequestInfo* resource_request_info =
ResourceRequestInfo::ForRequest(request);
if (!resource_request_info)
return nullptr;
int child_id = resource_request_info->GetChildID();
int frame_tree_node_id = resource_request_info->GetFrameTreeNodeId();
const InterceptedPage* intercepted_page;
if (frame_tree_node_id == -1) {
// |frame_tree_node_id| is not set for renderer side requests, fall back to
// the RenderFrameID.
int render_frame_id = resource_request_info->GetRenderFrameID();
const auto find_it = intercepted_render_frames_.find(
std::make_pair(render_frame_id, child_id));
if (find_it == intercepted_render_frames_.end())
return nullptr;
intercepted_page = &find_it->second;
} else {
// |frame_tree_node_id| is set for browser side navigations, so use that
// because the RenderFrameID isn't known (neither is the ChildID).
const auto find_it = intercepted_frame_tree_nodes_.find(frame_tree_node_id);
if (find_it == intercepted_frame_tree_nodes_.end())
return nullptr;
intercepted_page = &find_it->second;
}
// We don't want to intercept our own sub requests.
if (sub_requests_.find(request) != sub_requests_.end())
return nullptr;
bool is_redirect;
std::string interception_id = GetIdForRequest(request, &is_redirect);
DevToolsURLInterceptorRequestJob* job = new DevToolsURLInterceptorRequestJob(
this, interception_id, request, network_delegate,
intercepted_page->web_contents, intercepted_page->network_handler,
is_redirect, resource_request_info->GetResourceType());
interception_id_to_job_map_[interception_id] = job;
return job;
}
class DevToolsURLRequestInterceptor::State::InterceptedWebContentsObserver
: public WebContentsObserver {
public:
InterceptedWebContentsObserver(
WebContents* web_contents,
scoped_refptr<DevToolsURLRequestInterceptor::State> state,
base::WeakPtr<protocol::NetworkHandler> network_handler)
: WebContentsObserver(web_contents),
state_(state),
network_handler_(network_handler) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void RenderFrameHostChanged(RenderFrameHost* old_host,
RenderFrameHost* new_host) override {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (old_host)
FrameDeleted(old_host);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(
&DevToolsURLRequestInterceptor::State::
StartInterceptingRequestsInternal,
state_, new_host->GetRoutingID(), new_host->GetFrameTreeNodeId(),
new_host->GetProcess()->GetID(), web_contents(), network_handler_));
}
void FrameDeleted(RenderFrameHost* render_frame_host) override {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&DevToolsURLRequestInterceptor::State::
StopInterceptingRequestsInternal,
state_, render_frame_host->GetRoutingID(),
render_frame_host->GetFrameTreeNodeId(),
render_frame_host->GetProcess()->GetID()));
}
private:
scoped_refptr<DevToolsURLRequestInterceptor::State> state_;
base::WeakPtr<protocol::NetworkHandler> network_handler_;
};
void DevToolsURLRequestInterceptor::State::StartInterceptingRequestsInternal(
int render_frame_id,
int frame_tree_node_id,
int process_id,
WebContents* web_contents,
base::WeakPtr<protocol::NetworkHandler> network_handler) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
intercepted_render_frames_.emplace(
std::piecewise_construct,
std::forward_as_tuple(render_frame_id, process_id),
std::forward_as_tuple(web_contents, network_handler));
intercepted_frame_tree_nodes_.emplace(
std::piecewise_construct, std::forward_as_tuple(frame_tree_node_id),
std::forward_as_tuple(web_contents, network_handler));
}
void DevToolsURLRequestInterceptor::State::StopInterceptingRequestsInternal(
int render_frame_id,
int frame_tree_node_id,
int process_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
intercepted_render_frames_.erase(std::make_pair(render_frame_id, process_id));
intercepted_frame_tree_nodes_.erase(frame_tree_node_id);
}
void DevToolsURLRequestInterceptor::State::StartInterceptingRequests(
WebContents* web_contents,
base::WeakPtr<protocol::NetworkHandler> network_handler) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// WebContents methods are UI thread only.
for (RenderFrameHost* render_frame_host : web_contents->GetAllFrames()) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&DevToolsURLRequestInterceptor::State::
StartInterceptingRequestsInternal,
this, render_frame_host->GetRoutingID(),
render_frame_host->GetFrameTreeNodeId(),
render_frame_host->GetProcess()->GetID(), web_contents,
network_handler));
}
// Listen for future updates.
observers_.emplace(web_contents,
base::MakeUnique<InterceptedWebContentsObserver>(
web_contents, this, network_handler));
}
void DevToolsURLRequestInterceptor::State::StopInterceptingRequests(
WebContents* web_contents) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
observers_.erase(web_contents);
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::BindOnce(&DevToolsURLRequestInterceptor::State::
StopInterceptingRequestsOnIoThread,
this, web_contents));
}
void DevToolsURLRequestInterceptor::State::StopInterceptingRequestsOnIoThread(
WebContents* web_contents) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Remove any intercepted render frames associated with |web_contents|.
base::flat_map<std::pair<int, int>, InterceptedPage>
remaining_intercepted_render_frames;
for (const auto pair : intercepted_render_frames_) {
if (pair.second.web_contents == web_contents)
continue;
remaining_intercepted_render_frames.insert(pair);
}
std::swap(remaining_intercepted_render_frames, intercepted_render_frames_);
// Remove any intercepted frame tree nodes associated with |web_contents|.
base::flat_map<int, InterceptedPage> remaining_intercepted_frame_tree_nodes;
for (const auto pair : intercepted_frame_tree_nodes_) {
if (pair.second.web_contents == web_contents)
continue;
remaining_intercepted_frame_tree_nodes.insert(pair);
}
std::swap(remaining_intercepted_frame_tree_nodes,
intercepted_frame_tree_nodes_);
// Tell any jobs associated with |web_contents| to stop intercepting.
for (const auto pair : interception_id_to_job_map_) {
if (pair.second->web_contents() == web_contents)
pair.second->StopIntercepting();
}
}
void DevToolsURLRequestInterceptor::State::RegisterSubRequest(
const net::URLRequest* sub_request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(sub_requests_.find(sub_request) == sub_requests_.end());
sub_requests_.insert(sub_request);
}
void DevToolsURLRequestInterceptor::State::UnregisterSubRequest(
const net::URLRequest* sub_request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(sub_requests_.find(sub_request) != sub_requests_.end());
sub_requests_.erase(sub_request);
}
void DevToolsURLRequestInterceptor::State::ExpectRequestAfterRedirect(
const net::URLRequest* request,
std::string id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
expected_redirects_[request] = id;
}
std::string DevToolsURLRequestInterceptor::State::GetIdForRequest(
const net::URLRequest* request,
bool* is_redirect) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto find_it = expected_redirects_.find(request);
if (find_it == expected_redirects_.end()) {
*is_redirect = false;
return base::StringPrintf("id-%zu", ++next_id_);
}
*is_redirect = true;
std::string id = find_it->second;
expected_redirects_.erase(find_it);
return id;
}
DevToolsURLInterceptorRequestJob* DevToolsURLRequestInterceptor::State::GetJob(
const std::string& interception_id) const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const auto it = interception_id_to_job_map_.find(interception_id);
if (it == interception_id_to_job_map_.end())
return nullptr;
return it->second;
}
void DevToolsURLRequestInterceptor::State::JobFinished(
const std::string& interception_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
interception_id_to_job_map_.erase(interception_id);
}
// static
DevToolsURLRequestInterceptor*
DevToolsURLRequestInterceptor::FromBrowserContext(BrowserContext* context) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return static_cast<DevToolsURLRequestInterceptorUserData*>(
context->GetUserData(kDevToolsURLRequestInterceptorKeyName))
->devtools_url_request_interceptor();
}
DevToolsURLRequestInterceptor::Modifications::Modifications(
base::Optional<net::Error> error_reason,
base::Optional<std::string> raw_response,
protocol::Maybe<std::string> modified_url,
protocol::Maybe<std::string> modified_method,
protocol::Maybe<std::string> modified_post_data,
protocol::Maybe<protocol::Network::Headers> modified_headers,
protocol::Maybe<protocol::Network::AuthChallengeResponse>
auth_challenge_response)
: error_reason(std::move(error_reason)),
raw_response(std::move(raw_response)),
modified_url(std::move(modified_url)),
modified_method(std::move(modified_method)),
modified_post_data(std::move(modified_post_data)),
modified_headers(std::move(modified_headers)),
auth_challenge_response(std::move(auth_challenge_response)) {}
DevToolsURLRequestInterceptor::Modifications::~Modifications() {}
DevToolsURLRequestInterceptor::State::InterceptedPage::InterceptedPage()
: web_contents(nullptr) {}
DevToolsURLRequestInterceptor::State::InterceptedPage::InterceptedPage(
const InterceptedPage& other) = default;
DevToolsURLRequestInterceptor::State::InterceptedPage::InterceptedPage(
WebContents* web_contents,
base::WeakPtr<protocol::NetworkHandler> network_handler)
: web_contents(web_contents), network_handler(network_handler) {}
DevToolsURLRequestInterceptor::State::InterceptedPage::~InterceptedPage() =
default;
} // namespace content