| // 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/strings/string_util.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()); |
| 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 |