blob: bdec3320a8416b0b21f7baa7a098a8280a4182c8 [file] [log] [blame]
// Copyright 2021 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/aggregation_service/aggregatable_report_sender.h"
#include <stdint.h>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/metrics/dwa/dwa_builders.h"
#include "components/metrics/dwa/dwa_recorder.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/isolation_info.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.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/fetch_api.mojom.h"
#include "url/gurl.h"
namespace content {
namespace {
using DelayType = AggregatableReportRequest::DelayType;
void RecordHistograms(std::optional<DelayType> delay_type,
int net_error,
std::optional<int> http_response_code,
AggregatableReportSender::RequestStatus status,
std::string_view serialized_url) {
// Since net errors are always negative and HTTP errors are always positive,
// it is fine to combine these in a single histogram.
const int http_response_or_net_error =
net_error != net::OK ? net_error : http_response_code.value_or(1);
base::UmaHistogramEnumeration(
"PrivacySandbox.AggregationService.ReportSender.Status", status);
dwa::builders::PrivacySandbox_AggregationService_ReportSenderAttempt()
.SetContent(serialized_url)
.SetStatus(static_cast<int64_t>(status))
.Record(metrics::dwa::DwaRecorder::Get());
base::UmaHistogramSparse(
"PrivacySandbox.AggregationService.ReportSender."
"HttpResponseOrNetErrorCode",
http_response_or_net_error);
if (delay_type.has_value()) {
const std::string_view delay_type_string =
AggregatableReportRequest::DelayTypeToString(*delay_type);
base::UmaHistogramEnumeration(
base::JoinString({"PrivacySandbox.AggregationService.ReportSender",
delay_type_string, "Status"},
"."),
status);
base::UmaHistogramSparse(
base::JoinString({"PrivacySandbox.AggregationService.ReportSender",
delay_type_string, "HttpResponseOrNetErrorCode"},
"."),
http_response_or_net_error);
}
}
} // namespace
AggregatableReportSender::AggregatableReportSender(
StoragePartition* storage_partition)
: storage_partition_(storage_partition) {
CHECK(storage_partition_);
}
AggregatableReportSender::AggregatableReportSender(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
bool enable_debug_logging)
: url_loader_factory_(std::move(url_loader_factory)),
enable_debug_logging_(enable_debug_logging) {
CHECK(url_loader_factory_);
}
AggregatableReportSender::~AggregatableReportSender() = default;
// static
std::unique_ptr<AggregatableReportSender>
AggregatableReportSender::CreateForTesting(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
bool enable_debug_logging) {
return base::WrapUnique(new AggregatableReportSender(
std::move(url_loader_factory), enable_debug_logging));
}
void AggregatableReportSender::SendReport(GURL url,
const base::Value& contents,
std::optional<DelayType> delay_type,
ReportSentCallback callback) {
CHECK(storage_partition_ || url_loader_factory_);
// The browser process URLLoaderFactory is not created by default, so don't
// create it until it is directly needed.
if (!url_loader_factory_) {
url_loader_factory_ =
storage_partition_->GetURLLoaderFactoryForBrowserProcess();
}
std::string serialized_url = url.spec();
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = std::move(url);
resource_request->method = net::HttpRequestHeaders::kPostMethod;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->load_flags =
net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_CACHE;
resource_request->trusted_params = network::ResourceRequest::TrustedParams();
resource_request->trusted_params->isolation_info =
net::IsolationInfo::CreateTransient(/*nonce=*/std::nullopt);
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("aggregation_service_report", R"(
semantics {
sender: "Aggregation Service"
description:
"Sends the aggregatable report to reporting endpoint requested by "
"the Private Aggregation API, see "
"https://github.com/patcg-individual-drafts/private-aggregation-api"
"."
trigger:
"When an aggregatable report has become eligible for reporting."
data:
"The aggregatable report encoded in JSON format."
destination: OTHER
}
policy {
cookies_allowed: NO
setting:
"This feature can be controlled via the 'Ad measurement' setting "
"in the 'Ad privacy' section of 'Privacy and Security'."
chrome_policy {
PrivacySandboxAdMeasurementEnabled {
PrivacySandboxAdMeasurementEnabled: false
}
}
})");
auto simple_url_loader = network::SimpleURLLoader::Create(
std::move(resource_request), traffic_annotation);
network::SimpleURLLoader* simple_url_loader_ptr = simple_url_loader.get();
auto it = loaders_in_progress_.insert(loaders_in_progress_.begin(),
std::move(simple_url_loader));
simple_url_loader_ptr->SetTimeoutDuration(base::Seconds(30));
std::string contents_json;
// TODO(crbug.com/40195940): Check for required fields of contents.
bool succeeded = base::JSONWriter::Write(contents, &contents_json);
CHECK(succeeded);
simple_url_loader_ptr->AttachStringForUpload(std::move(contents_json),
"application/json");
const int kMaxRetries = 1;
// Retry on a network change. A network change during DNS resolution
// results in a DNS error rather than a network change error, so retry in
// those cases as well.
int retry_mode = network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE |
network::SimpleURLLoader::RETRY_ON_NAME_NOT_RESOLVED;
simple_url_loader_ptr->SetRetryOptions(kMaxRetries, retry_mode);
// Allow bodies of non-2xx responses to be returned.
simple_url_loader_ptr->SetAllowHttpErrorResults(true);
// Unretained is safe because the URLLoader is owned by `this` and will be
// deleted before `this`.
simple_url_loader_ptr->DownloadHeadersOnly(
url_loader_factory_.get(),
base::BindOnce(&AggregatableReportSender::OnReportSent,
base::Unretained(this), std::move(it), std::move(callback),
delay_type, std::move(serialized_url)));
}
void AggregatableReportSender::OnReportSent(
UrlLoaderList::iterator it,
ReportSentCallback callback,
std::optional<DelayType> delay_type,
std::string serialized_url,
scoped_refptr<net::HttpResponseHeaders> headers) {
std::optional<int> http_response_code;
if (headers) {
http_response_code = headers->response_code();
}
const int net_error = it->get()->NetError();
RequestStatus status;
if (net_error != net::OK) {
status = RequestStatus::kNetworkError;
} else if (http_response_code == net::HTTP_OK) {
status = RequestStatus::kOk;
} else {
status = RequestStatus::kServerError;
}
if (enable_debug_logging_ && status != RequestStatus::kOk) {
LOG(ERROR) << "Report sending failed, net error: "
<< net::ErrorToShortString(net_error) << ", HTTP response code: "
<< (http_response_code
? base::NumberToString(*http_response_code)
: "N/A");
}
RecordHistograms(delay_type, net_error, http_response_code, status,
serialized_url);
loaders_in_progress_.erase(it);
std::move(callback).Run(status);
}
} // namespace content