blob: aa9a7a3cf9e0647f8c5eb03612522fd0357f5927 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/devtools/protocol/fetch_handler.h"
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/devtools/devtools_io_context.h"
#include "content/browser/devtools/devtools_stream_pipe.h"
#include "content/browser/devtools/devtools_url_loader_interceptor.h"
#include "content/browser/devtools/protocol/network_handler.h"
#include "content/public/browser/browser_thread.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/header_util.h"
namespace content {
namespace protocol {
// static
std::vector<FetchHandler*> FetchHandler::ForAgentHost(
DevToolsAgentHostImpl* host) {
return host->HandlersByName<FetchHandler>(Fetch::Metainfo::domainName);
}
FetchHandler::FetchHandler(
DevToolsIOContext* io_context,
UpdateLoaderFactoriesCallback update_loader_factories_callback,
base::OnceClosure cleanup_after_modifications_callback)
: DevToolsDomainHandler(Fetch::Metainfo::domainName),
io_context_(io_context),
update_loader_factories_callback_(
std::move(update_loader_factories_callback)),
cleanup_after_modifications_callback_(
std::move(cleanup_after_modifications_callback)) {}
FetchHandler::~FetchHandler() {
if (did_modifications_ && cleanup_after_modifications_callback_) {
std::move(cleanup_after_modifications_callback_).Run();
}
}
void FetchHandler::Wire(UberDispatcher* dispatcher) {
frontend_ = std::make_unique<Fetch::Frontend>(dispatcher->channel());
Fetch::Dispatcher::wire(dispatcher, this);
}
DevToolsURLLoaderInterceptor::InterceptionStage RequestStageToInterceptorStage(
const Fetch::RequestStage& stage) {
if (stage == Fetch::RequestStageEnum::Request) {
return DevToolsURLLoaderInterceptor::kRequest;
}
if (stage == Fetch::RequestStageEnum::Response) {
return DevToolsURLLoaderInterceptor::kResponse;
}
NOTREACHED();
}
Response ToInterceptionPatterns(
std::unique_ptr<Array<Fetch::RequestPattern>>& maybe_patterns,
std::vector<DevToolsURLLoaderInterceptor::Pattern>* result) {
result->clear();
if (!maybe_patterns) {
result->emplace_back("*", base::flat_set<blink::mojom::ResourceType>(),
DevToolsURLLoaderInterceptor::kRequest);
return Response::Success();
}
for (const auto& pattern : *maybe_patterns) {
base::flat_set<blink::mojom::ResourceType> resource_types;
std::string resource_type = pattern->GetResourceType("");
if (!resource_type.empty()) {
if (!NetworkHandler::AddInterceptedResourceType(resource_type,
&resource_types)) {
return Response::InvalidParams(
base::StringPrintf("Unknown resource type in fetch filter: '%s'",
resource_type.c_str()));
}
}
result->emplace_back(
pattern->GetUrlPattern("*"), std::move(resource_types),
RequestStageToInterceptorStage(
pattern->GetRequestStage(Fetch::RequestStageEnum::Request)));
}
return Response::Success();
}
bool FetchHandler::MaybeCreateProxyForInterception(
int process_id,
StoragePartition* storage_partition,
const base::UnguessableToken& frame_token,
bool is_navigation,
bool is_download,
network::mojom::URLLoaderFactoryOverride* intercepting_factory) {
return interceptor_ && interceptor_->CreateProxyForInterception(
process_id, storage_partition, frame_token,
is_navigation, is_download, intercepting_factory);
}
void FetchHandler::Enable(
std::unique_ptr<Array<Fetch::RequestPattern>> patterns,
std::optional<bool> handleAuth,
std::unique_ptr<EnableCallback> callback) {
if (!interceptor_) {
interceptor_ =
std::make_unique<DevToolsURLLoaderInterceptor>(base::BindRepeating(
&FetchHandler::RequestIntercepted, weak_factory_.GetWeakPtr()));
}
std::vector<DevToolsURLLoaderInterceptor::Pattern> interception_patterns;
Response response = ToInterceptionPatterns(patterns, &interception_patterns);
if (!response.IsSuccess()) {
callback->sendFailure(response);
return;
}
if (!interception_patterns.size() && handleAuth.value_or(false)) {
callback->sendFailure(Response::InvalidParams(
"Can\'t specify empty patterns with handleAuth set"));
return;
}
interceptor_->SetPatterns(std::move(interception_patterns),
handleAuth.value_or(false));
update_loader_factories_callback_.Run(
base::BindOnce(&EnableCallback::sendSuccess, std::move(callback)));
}
Response FetchHandler::Disable() {
const bool was_enabled = !!interceptor_;
interceptor_.reset();
if (was_enabled) {
update_loader_factories_callback_.Run(base::DoNothing());
}
return Response::Success();
}
namespace {
using ContinueInterceptedRequestCallback =
DevToolsURLLoaderInterceptor::ContinueInterceptedRequestCallback;
template <typename Callback, typename Base, typename... Args>
class CallbackWrapper : public Base {
public:
explicit CallbackWrapper(std::unique_ptr<Callback> callback)
: callback_(std::move(callback)) {}
private:
friend typename std::unique_ptr<CallbackWrapper>::deleter_type;
void sendSuccess(Args... args) override {
callback_->sendSuccess(std::forward<Args>(args)...);
}
void sendFailure(const DispatchResponse& response) override {
callback_->sendFailure(response);
}
void fallThrough() override { NOTREACHED(); }
~CallbackWrapper() override {}
std::unique_ptr<Callback> callback_;
};
template <typename Callback>
std::unique_ptr<CallbackWrapper<Callback, ContinueInterceptedRequestCallback>>
WrapCallback(std::unique_ptr<Callback> cb) {
return std::make_unique<
CallbackWrapper<Callback, ContinueInterceptedRequestCallback>>(
std::move(cb));
}
template <typename Callback>
bool ValidateHeaders(Fetch::HeaderEntry* entry, Callback* callback) {
if (!net::HttpUtil::IsValidHeaderName(entry->GetName()) ||
!net::HttpUtil::IsValidHeaderValue(entry->GetValue())) {
callback->sendFailure(
Response::InvalidParams("Invalid header: " + entry->GetName()));
return false;
}
return true;
}
bool ValidateHeadersForRequest(
Fetch::HeaderEntry* entry,
Fetch::Backend::ContinueRequestCallback* callback) {
if (!ValidateHeaders(entry, callback)) {
return false;
}
if (!network::IsRequestHeaderSafe(entry->GetName(), entry->GetValue())) {
callback->sendFailure(
Response::InvalidParams("Unsafe header: " + entry->GetName()));
return false;
}
return true;
}
} // namespace
void FetchHandler::FailRequest(const String& requestId,
const String& errorReason,
std::unique_ptr<FailRequestCallback> callback) {
if (!interceptor_) {
callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
return;
}
bool ok = false;
net::Error reason = NetworkHandler::NetErrorFromString(errorReason, &ok);
if (!ok) {
callback->sendFailure(Response::InvalidParams("Unknown errorReason"));
return;
}
auto modifications =
std::make_unique<DevToolsURLLoaderInterceptor::Modifications>(reason);
did_modifications_ = true;
interceptor_->ContinueInterceptedRequest(requestId, std::move(modifications),
WrapCallback(std::move(callback)));
}
namespace {
std::string GetReasonPhrase(int responseCode) {
if (const char* phrase = net::TryToGetHttpReasonPhrase(
static_cast<net::HttpStatusCode>(responseCode))) {
return phrase;
}
return "";
}
} // namespace
void FetchHandler::FulfillRequest(
const String& requestId,
int responseCode,
std::unique_ptr<Array<Fetch::HeaderEntry>> responseHeaders,
std::optional<Binary> binaryResponseHeaders,
std::optional<Binary> body,
std::optional<String> responsePhrase,
std::unique_ptr<FulfillRequestCallback> callback) {
if (!interceptor_) {
callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
return;
}
const std::string status_phrase = responsePhrase.has_value()
? responsePhrase.value()
: GetReasonPhrase(responseCode);
if (status_phrase.empty()) {
callback->sendFailure(
Response::InvalidParams("Invalid http status code or phrase"));
return;
}
std::string headers =
base::StringPrintf("HTTP/1.1 %d %s", responseCode, status_phrase.c_str());
headers.append(1, '\0');
if (responseHeaders) {
if (binaryResponseHeaders) {
callback->sendFailure(Response::InvalidParams(
"Only one of responseHeaders or binaryHeaders may be present"));
return;
}
for (const auto& entry : *responseHeaders) {
if (!ValidateHeaders(entry.get(), callback.get())) {
return;
}
headers.append(entry->GetName());
headers.append(":");
headers.append(entry->GetValue());
headers.append(1, '\0');
}
} else if (binaryResponseHeaders) {
Binary response_headers = std::move(*binaryResponseHeaders);
headers.append(reinterpret_cast<const char*>(response_headers.data()),
response_headers.size());
if (headers.back() != '\0') {
headers.append(1, '\0');
}
}
headers.append(1, '\0');
auto modifications =
std::make_unique<DevToolsURLLoaderInterceptor::Modifications>(
base::MakeRefCounted<net::HttpResponseHeaders>(headers),
body ? body->bytes() : nullptr);
did_modifications_ = true;
interceptor_->ContinueInterceptedRequest(requestId, std::move(modifications),
WrapCallback(std::move(callback)));
}
void FetchHandler::ContinueRequest(
const String& requestId,
std::optional<String> url,
std::optional<String> method,
std::optional<protocol::Binary> postData,
std::unique_ptr<Array<Fetch::HeaderEntry>> headers,
std::optional<bool> interceptResponse,
std::unique_ptr<ContinueRequestCallback> callback) {
if (!interceptor_) {
callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
return;
}
std::unique_ptr<DevToolsURLLoaderInterceptor::Modifications::HeadersVector>
request_headers;
if (headers) {
request_headers = std::make_unique<
DevToolsURLLoaderInterceptor::Modifications::HeadersVector>();
for (const auto& entry : *headers) {
if (!ValidateHeadersForRequest(entry.get(), callback.get())) {
return;
}
request_headers->emplace_back(entry->GetName(), entry->GetValue());
}
}
did_modifications_ = url.has_value() || method.has_value() ||
postData.has_value() || request_headers;
auto modifications =
std::make_unique<DevToolsURLLoaderInterceptor::Modifications>(
std::move(url), std::move(method), std::move(postData),
std::move(request_headers), std::move(interceptResponse));
interceptor_->ContinueInterceptedRequest(requestId, std::move(modifications),
WrapCallback(std::move(callback)));
}
void FetchHandler::ContinueWithAuth(
const String& requestId,
std::unique_ptr<protocol::Fetch::AuthChallengeResponse>
authChallengeResponse,
std::unique_ptr<ContinueWithAuthCallback> callback) {
if (!interceptor_) {
callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
return;
}
using AuthChallengeResponse =
DevToolsURLLoaderInterceptor::AuthChallengeResponse;
std::unique_ptr<AuthChallengeResponse> auth_response;
std::string type = authChallengeResponse->GetResponse();
if (type == Network::AuthChallengeResponse::ResponseEnum::Default) {
auth_response = std::make_unique<AuthChallengeResponse>(
AuthChallengeResponse::kDefault);
} else if (type == Network::AuthChallengeResponse::ResponseEnum::CancelAuth) {
auth_response = std::make_unique<AuthChallengeResponse>(
AuthChallengeResponse::kCancelAuth);
} else if (type ==
Network::AuthChallengeResponse::ResponseEnum::ProvideCredentials) {
auth_response = std::make_unique<AuthChallengeResponse>(
base::UTF8ToUTF16(authChallengeResponse->GetUsername("")),
base::UTF8ToUTF16(authChallengeResponse->GetPassword("")));
} else {
callback->sendFailure(
Response::InvalidParams("Unrecognized authChallengeResponse"));
return;
}
auto modifications =
std::make_unique<DevToolsURLLoaderInterceptor::Modifications>(
std::move(auth_response));
did_modifications_ = true;
interceptor_->ContinueInterceptedRequest(requestId, std::move(modifications),
WrapCallback(std::move(callback)));
}
void FetchHandler::ContinueResponse(
const String& requestId,
std::optional<int> responseCode,
std::optional<String> responsePhrase,
std::unique_ptr<Array<Fetch::HeaderEntry>> responseHeaders,
std::optional<Binary> binaryResponseHeaders,
std::unique_ptr<ContinueResponseCallback> callback) {
if (!interceptor_) {
callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
return;
}
if (responseCode.has_value() && (responseHeaders || binaryResponseHeaders)) {
auto wrapped_callback = std::make_unique<
CallbackWrapper<ContinueResponseCallback, FulfillRequestCallback>>(
std::move(callback));
FulfillRequest(requestId, *responseCode, std::move(responseHeaders),
std::move(binaryResponseHeaders), {},
std::move(responsePhrase), std::move(wrapped_callback));
return;
}
if (!responseCode.has_value() && !responsePhrase.has_value() &&
!responseHeaders && !binaryResponseHeaders) {
interceptor_->ContinueInterceptedRequest(
requestId,
std::make_unique<DevToolsURLLoaderInterceptor::Modifications>(),
WrapCallback(std::move(callback)));
return;
}
callback->sendFailure(Response::ServerError(
"Cannot override only status or headers, both should be provided"));
}
void FetchHandler::GetResponseBody(
const String& requestId,
std::unique_ptr<GetResponseBodyCallback> callback) {
if (!interceptor_) {
callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
return;
}
auto wrapped_callback = std::make_unique<CallbackWrapper<
GetResponseBodyCallback,
DevToolsURLLoaderInterceptor::GetResponseBodyForInterceptionCallback,
const std::string&, bool>>(std::move(callback));
interceptor_->GetResponseBody(requestId, std::move(wrapped_callback));
}
void FetchHandler::TakeResponseBodyAsStream(
const String& requestId,
std::unique_ptr<TakeResponseBodyAsStreamCallback> callback) {
if (!interceptor_) {
callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
return;
}
interceptor_->TakeResponseBodyPipe(
requestId,
base::BindOnce(&FetchHandler::OnResponseBodyPipeTaken,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void FetchHandler::OnResponseBodyPipeTaken(
std::unique_ptr<TakeResponseBodyAsStreamCallback> callback,
Response response,
mojo::ScopedDataPipeConsumerHandle pipe,
const std::string& mime_type) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(response.IsSuccess(), pipe.is_valid());
if (!response.IsSuccess()) {
callback->sendFailure(std::move(response));
return;
}
// The pipe stream is owned only by io_context after we return.
auto stream = DevToolsStreamPipe::Create(io_context_, std::move(pipe));
callback->sendSuccess(stream->handle());
}
namespace {
std::unique_ptr<Array<Fetch::HeaderEntry>> ToHeaderEntryArray(
scoped_refptr<net::HttpResponseHeaders> headers) {
auto result = std::make_unique<Array<Fetch::HeaderEntry>>();
size_t iterator = 0;
std::string name;
std::string value;
while (headers->EnumerateHeaderLines(&iterator, &name, &value)) {
result->emplace_back(
Fetch::HeaderEntry::Create().SetName(name).SetValue(value).Build());
}
return result;
}
} // namespace
void FetchHandler::RequestIntercepted(
std::unique_ptr<InterceptedRequestInfo> info) {
std::optional<protocol::Network::ErrorReason> error_reason;
if (info->response_error_code < 0) {
error_reason = NetworkHandler::NetErrorToString(info->response_error_code);
}
std::optional<int> status_code;
std::optional<std::string> status_text;
std::unique_ptr<Array<Fetch::HeaderEntry>> response_headers;
if (info->response_headers) {
status_code = info->response_headers->response_code();
status_text = info->response_headers->GetStatusText();
response_headers = ToHeaderEntryArray(info->response_headers);
}
if (info->auth_challenge) {
auto auth_challenge =
Fetch::AuthChallenge::Create()
.SetSource(info->auth_challenge->is_proxy
? Network::AuthChallenge::SourceEnum::Proxy
: Network::AuthChallenge::SourceEnum::Server)
.SetOrigin(info->auth_challenge->challenger.Serialize())
.SetScheme(info->auth_challenge->scheme)
.SetRealm(info->auth_challenge->realm)
.Build();
frontend_->AuthRequired(
info->interception_id, std::move(info->network_request),
info->frame_id.ToString(),
NetworkHandler::ResourceTypeToString(info->resource_type),
std::move(auth_challenge));
return;
}
frontend_->RequestPaused(
info->interception_id, std::move(info->network_request),
info->frame_id.ToString(),
NetworkHandler::ResourceTypeToString(info->resource_type),
std::move(error_reason), std::move(status_code), std::move(status_text),
std::move(response_headers), std::move(info->renderer_request_id),
std::move(info->redirected_request_id));
}
} // namespace protocol
} // namespace content