blob: 5251f116d4f377f4f804cb32ff4b7667896b948a [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/devtools/devtools_http_service_handler.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/net_errors.h"
#include "net/base/url_util.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
class DevToolsHttpServiceHandler::DevToolsStreamConsumer
: public network::SimpleURLLoaderStreamConsumer {
public:
DevToolsStreamConsumer(DevToolsHttpServiceHandler::StreamWriter stream_writer,
DevToolsHttpServiceHandler::Callback callback,
network::SimpleURLLoader* loader,
base::OnceClosure cleanup)
: stream_writer_(std::move(stream_writer)),
callback_(std::move(callback)),
loader_(loader),
cleanup_(std::move(cleanup)) {
CHECK(loader_);
}
~DevToolsStreamConsumer() override = default;
void OnDataReceived(std::string_view chunk,
base::OnceClosure resume) override {
stream_writer_.Run(chunk);
std::move(resume).Run();
}
void OnComplete(bool success) override {
CHECK(loader_);
auto result = std::make_unique<DevToolsHttpServiceHandler::Result>();
result->net_error = loader_->NetError();
if (loader_->ResponseInfo() && loader_->ResponseInfo()->headers) {
result->http_status = loader_->ResponseInfo()->headers->response_code();
}
if (result->net_error != net::OK) {
result->error = DevToolsHttpServiceHandler::Result::Error::kNetworkError;
} else if (result->http_status == -1) {
result->error = DevToolsHttpServiceHandler::Result::Error::kNetworkError;
} else if (result->http_status < 200 || result->http_status >= 300) {
result->error = DevToolsHttpServiceHandler::Result::Error::kHttpError;
} else if (!success) {
// There was an error and we don't know why, we default to network
// error for such cases.
result->error = DevToolsHttpServiceHandler::Result::Error::kNetworkError;
}
// Run completion callback
std::move(callback_).Run(std::move(result));
// The cleanup callback destroys the ActiveStreamRequest, which owns this
// Consumer. We must post the task to ensure destruction happens
// asynchronously after the current stack frame unwinds, avoiding
// a potential use-after-free of `this`.
if (cleanup_) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(cleanup_));
}
}
void OnRetry(base::OnceClosure start_retry) override { NOTREACHED(); }
private:
DevToolsHttpServiceHandler::StreamWriter stream_writer_;
DevToolsHttpServiceHandler::Callback callback_;
raw_ptr<network::SimpleURLLoader> loader_ = nullptr;
base::OnceClosure cleanup_;
};
DevToolsHttpServiceHandler::Result::Result() = default;
DevToolsHttpServiceHandler::Result::~Result() = default;
DevToolsHttpServiceHandler::Result::Result(Result&&) = default;
DevToolsHttpServiceHandler::Result&
DevToolsHttpServiceHandler::Result::operator=(Result&&) = default;
DevToolsHttpServiceHandler::ActiveStreamRequest::ActiveStreamRequest() =
default;
DevToolsHttpServiceHandler::ActiveStreamRequest::~ActiveStreamRequest() =
default;
DevToolsHttpServiceHandler::ActiveStreamRequest::ActiveStreamRequest(
ActiveStreamRequest&&) = default;
DevToolsHttpServiceHandler::ActiveStreamRequest&
DevToolsHttpServiceHandler::ActiveStreamRequest::operator=(
ActiveStreamRequest&&) = default;
DevToolsHttpServiceHandler::~DevToolsHttpServiceHandler() = default;
DevToolsHttpServiceHandler::DevToolsHttpServiceHandler() = default;
void DevToolsHttpServiceHandler::Request(
Profile* profile,
const DevToolsDispatchHttpRequestParams& params,
std::optional<StreamWriter> stream_writer,
Callback callback) {
CanMakeRequest(profile,
base::BindOnce(&DevToolsHttpServiceHandler::OnValidationDone,
weak_factory_.GetWeakPtr(), std::move(callback),
profile, std::move(stream_writer), params));
}
void DevToolsHttpServiceHandler::CanMakeRequest(
Profile* profile,
base::OnceCallback<void(bool success)> callback) {
std::move(callback).Run(profile && !profile->IsOffTheRecord());
}
void DevToolsHttpServiceHandler::OnValidationDone(
Callback callback,
Profile* profile,
std::optional<StreamWriter> stream_writer,
const DevToolsDispatchHttpRequestParams& params,
bool validation_success) {
if (!validation_success) {
auto result = std::make_unique<Result>();
result->error = Result::Error::kValidationFailed;
std::move(callback).Run(std::move(result));
return;
}
auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
auto fetcher_id = base::UnguessableToken::Create();
auto access_token_fetcher =
identity_manager->CreateAccessTokenFetcherForAccount(
identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSignin),
OAuthConsumerId(),
base::BindOnce(&DevToolsHttpServiceHandler::OnTokenFetched,
weak_factory_.GetWeakPtr(), std::move(callback),
profile, std::move(stream_writer), params, fetcher_id),
signin::AccessTokenFetcher::Mode::kImmediate);
access_token_fetchers_.insert({
fetcher_id,
std::move(access_token_fetcher),
});
}
void DevToolsHttpServiceHandler::OnTokenFetched(
Callback callback,
Profile* profile,
std::optional<StreamWriter> stream_writer,
const DevToolsDispatchHttpRequestParams& params,
base::UnguessableToken fetcher_id,
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info) {
access_token_fetchers_.erase(fetcher_id);
if (error.state() != GoogleServiceAuthError::NONE) {
auto result = std::make_unique<Result>();
result->error = Result::Error::kTokenFetchFailed;
result->error_detail = error.ToString();
std::move(callback).Run(std::move(result));
return;
}
auto resource_request = std::make_unique<network::ResourceRequest>();
GURL url = BaseURL().Resolve(params.path);
for (const auto& pair : params.query_params) {
const std::string& key = pair.first;
for (const std::string& value : pair.second) {
url = net::AppendQueryParameter(url, key, value);
}
}
resource_request->url = url;
resource_request->method = params.method;
resource_request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
"Bearer " + access_token_info.token);
auto simple_url_loader = network::SimpleURLLoader::Create(
std::move(resource_request), NetworkTrafficAnnotationTag());
simple_url_loader->SetAllowHttpErrorResults(true);
if (params.body.has_value()) {
simple_url_loader->AttachStringForUpload(params.body.value(),
"application/json");
}
if (stream_writer) {
auto token = base::UnguessableToken::Create();
auto& request = active_streams_[token];
request.loader = std::move(simple_url_loader);
auto cleanup_callback = base::BindOnce(
[](base::WeakPtr<DevToolsHttpServiceHandler> self,
base::UnguessableToken token) {
if (self) {
self->active_streams_.erase(token);
}
},
weak_factory_.GetWeakPtr(), token);
auto consumer = std::make_unique<DevToolsStreamConsumer>(
std::move(*stream_writer), std::move(callback), request.loader.get(),
std::move(cleanup_callback));
request.consumer = std::move(consumer);
request.loader->DownloadAsStream(
profile->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess()
.get(),
request.consumer.get());
return;
}
network::SimpleURLLoader* loader_ptr = simple_url_loader.get();
loader_ptr->DownloadToString(
profile->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess()
.get(),
base::BindOnce(&DevToolsHttpServiceHandler::OnRequestComplete,
weak_factory_.GetWeakPtr(), std::move(callback),
std::move(simple_url_loader)),
network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
}
void DevToolsHttpServiceHandler::OnRequestComplete(
Callback callback,
std::unique_ptr<network::SimpleURLLoader> simple_url_loader,
std::optional<std::string> response_body) {
auto result = std::make_unique<Result>();
result->net_error = simple_url_loader->NetError();
result->response_body = std::move(response_body);
if (simple_url_loader->ResponseInfo() &&
simple_url_loader->ResponseInfo()->headers) {
result->http_status =
simple_url_loader->ResponseInfo()->headers->response_code();
}
if (result->net_error != net::OK) {
result->error = Result::Error::kNetworkError;
} else if (result->http_status < net::HTTP_OK ||
result->http_status >= net::HTTP_MULTIPLE_CHOICES) {
result->error = Result::Error::kHttpError;
}
std::move(callback).Run(std::move(result));
}