// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/browser/android/httpclient/http_client.h"
#include <string>
#include <utility>
#include "base/not_fatal_until.h"
#include "content/public/browser/browser_thread.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace httpclient {
namespace {
constexpr int kTimeoutDurationSeconds = 30;
constexpr size_t kMaxResponseSizeDefault = 4 * 1024 * 1024;
void PopulateRequestBodyAndContentType(network::SimpleURLLoader* loader,
std::vector<uint8_t>&& request_body,
const std::string& content_type) {
std::string request_body_string(
reinterpret_cast<const char*>(request_body.data()), request_body.size());
loader->AttachStringForUpload(request_body_string, content_type);
}
std::unique_ptr<network::SimpleURLLoader> MakeLoader(
const GURL& gurl,
const std::string& request_type,
std::vector<uint8_t>&& request_body,
std::map<std::string, std::string>&& headers,
const net::NetworkTrafficAnnotationTag& network_traffic_annotation) {
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = gurl;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->method = request_type;
std::string content_type;
for (auto const& [key, value] : headers) {
if (0 == base::CompareCaseInsensitiveASCII(
key, net::HttpRequestHeaders::kContentType)) {
// Content-Type will be populated in ::PopulateRequestBodyAndContentType.
content_type = value;
continue;
}
resource_request->headers.SetHeader(key, value);
}
auto simple_loader = network::SimpleURLLoader::Create(
std::move(resource_request), network_traffic_annotation);
simple_loader->SetTimeoutDuration(base::Seconds(kTimeoutDurationSeconds));
if (!request_body.empty()) {
DCHECK(!content_type.empty());
PopulateRequestBodyAndContentType(simple_loader.get(),
std::move(request_body), content_type);
}
return simple_loader;
}
} // namespace
HttpClient::HttpClient(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: loader_factory_(url_loader_factory) {}
HttpClient::~HttpClient() {
url_loaders_.clear();
}
void HttpClient::Send(
const GURL& gurl,
const std::string& request_type,
std::vector<uint8_t>&& request_body,
std::map<std::string, std::string>&& headers,
const net::NetworkTrafficAnnotationTag& network_traffic_annotation,
HttpClient::ResponseCallback callback) {
// SimpleUrlLoader can only be called on the UI thread.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::unique_ptr<network::SimpleURLLoader> simple_loader =
MakeLoader(gurl, request_type, std::move(request_body),
std::move(headers), network_traffic_annotation);
simple_loader->SetAllowHttpErrorResults(true);
// TODO(crbug.com/40169299): Use flag to control the max size limit.
simple_loader->DownloadToString(
loader_factory_.get(),
base::BindOnce(&HttpClient::OnSimpleLoaderComplete,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
simple_loader.get()),
kMaxResponseSizeDefault);
// We need to keep loaders alive, so we store a reference.
url_loaders_.emplace(std::move(simple_loader));
}
void HttpClient::ReleaseUrlLoader(network::SimpleURLLoader* simple_loader) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Release the current loader.
auto loader_iter = url_loaders_.find(simple_loader);
CHECK(loader_iter != url_loaders_.end(), base::NotFatalUntil::M130);
url_loaders_.erase(loader_iter);
}
void HttpClient::OnSimpleLoaderComplete(
HttpClient::ResponseCallback response_callback,
network::SimpleURLLoader* simple_loader,
std::unique_ptr<std::string> response) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
int32_t response_code = 0;
int32_t net_error_code = simple_loader->NetError();
std::map<std::string, std::string> response_headers;
auto* response_info = simple_loader->ResponseInfo();
if (response_info && response_info->headers) {
response_code = response_info->headers->response_code();
size_t iter = 0;
std::string name, value;
while (response_info->headers->EnumerateHeaderLines(&iter, &name, &value)) {
std::string& slot = response_headers[name];
if (slot.empty()) {
slot = std::move(value);
} else {
slot += '\n';
slot += value;
}
}
}
// If the response string is empty, that means a network error exists.
// We'll not populate the response body in that case.
std::vector<uint8_t> response_body;
if (response) {
const uint8_t* begin = reinterpret_cast<const uint8_t*>(response->data());
const uint8_t* end = begin + response->size();
response_body.assign(begin, end);
}
ReleaseUrlLoader(simple_loader);
std::move(response_callback)
.Run(response_code, net_error_code, std::move(response_body),
std::move(response_headers));
}
} // namespace httpclient