[go: nahoru, domu]

blob: 23e94b075d64d94f5dc24bfe1642d4fb758c1b23 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/attribution_reporting/attribution_network_sender_impl.h"
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/check.h"
#include "base/metrics/histogram_functions.h"
#include "content/browser/attribution_reporting/attribution_utils.h"
#include "content/browser/attribution_reporting/send_result.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/resource_request_body.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
namespace content {
namespace {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class Status {
kOk = 0,
// Corresponds to a non-zero NET_ERROR.
kInternalError = 1,
// Corresponds to a non-200 HTTP response code from the reporting endpoint.
kExternalError = 2,
kMaxValue = kExternalError
};
} // namespace
AttributionNetworkSenderImpl::AttributionNetworkSenderImpl(
StoragePartition* storage_partition)
: storage_partition_(storage_partition) {}
AttributionNetworkSenderImpl::~AttributionNetworkSenderImpl() = default;
void AttributionNetworkSenderImpl::SendReport(
GURL report_url,
base::Value report_body,
ReportSentCallback sent_callback) {
// 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();
}
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = std::move(report_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();
// TODO(https://crbug.com/1058018): Update the "policy" field in the traffic
// annotation when a setting to disable the API is properly
// surfaced/implemented.
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("conversion_measurement_report", R"(
semantics {
sender: "Event-level Conversion Measurement API"
description:
"The Conversion Measurement API allows sites to measure "
"conversions (e.g. purchases) and attribute them to clicked ads, "
"without using cross-site persistent identifiers like third party "
"cookies."
trigger:
"When a registered conversion has become eligible for reporting."
data:
"A high-entropy identifier declared by the site in which the user "
"clicked on an impression. A noisy low-entropy data value declared "
"on the conversion site."
destination:OTHER
}
policy {
cookies_allowed: NO
setting:
"This feature cannot be disabled by settings."
policy_exception_justification: "Not implemented."
})");
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));
simple_url_loader_ptr->AttachStringForUpload(
SerializeAttributionJson(report_body), "application/json");
// Retry once on 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.
// TODO(http://crbug.com/1181106): Consider logging metrics for how often this
// retry succeeds/fails.
int retry_mode = network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE |
network::SimpleURLLoader::RETRY_ON_NAME_NOT_RESOLVED;
simple_url_loader_ptr->SetRetryOptions(/*max_retries=*/1, retry_mode);
// 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(&AttributionNetworkSenderImpl::OnReportSent,
base::Unretained(this), std::move(it),
std::move(sent_callback)));
}
void AttributionNetworkSenderImpl::SetURLLoaderFactoryForTesting(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
url_loader_factory_ = url_loader_factory;
}
void AttributionNetworkSenderImpl::OnReportSent(
UrlLoaderList::iterator it,
ReportSentCallback sent_callback,
scoped_refptr<net::HttpResponseHeaders> headers) {
network::SimpleURLLoader* loader = it->get();
// Consider a non-200 HTTP code as a non-internal error.
int net_error = loader->NetError();
bool internal_ok =
net_error == net::OK || net_error == net::ERR_HTTP_RESPONSE_CODE_FAILURE;
int response_code = headers ? headers->response_code() : -1;
bool external_ok = response_code == net::HTTP_OK;
Status status =
internal_ok && external_ok
? Status::kOk
: !internal_ok ? Status::kInternalError : Status::kExternalError;
base::UmaHistogramEnumeration("Conversions.ReportStatus", status);
// Since net errors are always negative and HTTP errors are always positive,
// it is fine to combine these in a single histogram.
base::UmaHistogramSparse("Conversions.Report.HttpResponseOrNetErrorCode",
internal_ok ? response_code : net_error);
if (loader->GetNumRetries() > 0) {
base::UmaHistogramBoolean("Conversions.ReportRetrySucceed",
status == Status::kOk);
}
loaders_in_progress_.erase(it);
// Retry reports that have not received headers and failed with one of the
// specified error codes. These codes are chosen from the
// "Conversions.Report.HttpResponseOrNetErrorCode" histogram. HTTP errors
// should not be retried to prevent over requesting servers.
bool should_retry =
!headers && (net_error == net::ERR_INTERNET_DISCONNECTED ||
net_error == net::ERR_NAME_NOT_RESOLVED ||
net_error == net::ERR_TIMED_OUT ||
net_error == net::ERR_CONNECTION_TIMED_OUT ||
net_error == net::ERR_CONNECTION_ABORTED ||
net_error == net::ERR_CONNECTION_RESET);
SendResult::Status report_status =
(status == Status::kOk)
? SendResult::Status::kSent
: (should_retry ? SendResult::Status::kTransientFailure
: SendResult::Status::kFailure);
std::move(sent_callback)
.Run(SendResult(report_status, headers ? headers->response_code() : 0));
}
} // namespace content