blob: 5675d0d5cb49826db863686d49f461ba06f553ad [file] [log] [blame]
// Copyright 2014 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 "components/metrics/net/net_metrics_log_uploader.h"
#include "base/base64.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "components/data_use_measurement/core/data_use_user_data.h"
#include "components/encrypted_messages/encrypted_message.pb.h"
#include "components/encrypted_messages/message_encrypter.h"
#include "components/metrics/metrics_log_uploader.h"
#include "net/base/load_flags.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/url_fetcher.h"
#include "third_party/metrics_proto/reporting_info.pb.h"
#include "url/gurl.h"
namespace {
const base::Feature kHttpRetryFeature{"UMAHttpRetry",
base::FEATURE_DISABLED_BY_DEFAULT};
// Constants used for encrypting logs that are sent over HTTP. The
// corresponding private key is used by the metrics server to decrypt logs.
const char kEncryptedMessageLabel[] = "metrics log";
const uint8_t kServerPublicKey[] = {
0x51, 0xcc, 0x52, 0x67, 0x42, 0x47, 0x3b, 0x10, 0xe8, 0x63, 0x18,
0x3c, 0x61, 0xa7, 0x96, 0x76, 0x86, 0x91, 0x40, 0x71, 0x39, 0x5f,
0x31, 0x1a, 0x39, 0x5b, 0x76, 0xb1, 0x6b, 0x3d, 0x6a, 0x2b};
const uint32_t kServerPublicKeyVersion = 1;
net::NetworkTrafficAnnotationTag GetNetworkTrafficAnnotation(
const metrics::MetricsLogUploader::MetricServiceType& service_type) {
// The code in this function should remain so that we won't need a default
// case that does not have meaningful annotation.
if (service_type == metrics::MetricsLogUploader::UMA) {
return net::DefineNetworkTrafficAnnotation("metrics_report_uma", R"(
semantics {
sender: "Metrics UMA Log Uploader"
description:
"Report of usage statistics and crash-related data about Chromium. "
"Usage statistics contain information such as preferences, button "
"clicks, and memory usage and do not include web page URLs or "
"personal information. See more at "
"https://www.google.com/chrome/browser/privacy/ under 'Usage "
"statistics and crash reports'. Usage statistics are tied to a "
"pseudonymous machine identifier and not to your email address."
trigger:
"Reports are automatically generated on startup and at intervals "
"while Chromium is running."
data:
"A protocol buffer with usage statistics and crash related data."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"Users can enable or disable this feature by disabling "
"'Automatically send usage statistics and crash reports to Google' "
"in Chromium's settings under Advanced Settings, Privacy. The "
"feature is enabled by default."
chrome_policy {
MetricsReportingEnabled {
policy_options {mode: MANDATORY}
MetricsReportingEnabled: false
}
}
})");
}
DCHECK_EQ(service_type, metrics::MetricsLogUploader::UKM);
return net::DefineNetworkTrafficAnnotation("metrics_report_ukm", R"(
semantics {
sender: "Metrics UKM Log Uploader"
description:
"Report of usage statistics that are keyed by URLs to Chromium, "
"sent only if the profile has History Sync. This includes "
"information about the web pages you visit and your usage of them, "
"such as page load speed. This will also include URLs and "
"statistics related to downloaded files. If Extension Sync is "
"enabled, these statistics will also include information about "
"the extensions that have been installed from Chrome Web Store. "
"Google only stores usage statistics associated with published "
"extensions, and URLs that are known by Google’s search index. "
"Usage statistics are tied to a pseudonymous machine identifier "
"and not to your email address."
trigger:
"Reports are automatically generated on startup and at intervals "
"while Chromium is running with Sync enabled."
data:
"A protocol buffer with usage statistics and associated URLs."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"Users can enable or disable this feature by disabling "
"'Automatically send usage statistics and crash reports to Google' "
"in Chromium's settings under Advanced Settings, Privacy. This is "
"only enabled if all active profiles have History/Extension Sync "
"enabled without a Sync passphrase."
chrome_policy {
MetricsReportingEnabled {
policy_options {mode: MANDATORY}
MetricsReportingEnabled: false
}
}
})");
}
std::string SerializeReportingInfo(
const metrics::ReportingInfo& reporting_info) {
std::string result;
std::string bytes;
bool success = reporting_info.SerializeToString(&bytes);
DCHECK(success);
base::Base64Encode(bytes, &result);
return result;
}
} // namespace
namespace metrics {
NetMetricsLogUploader::NetMetricsLogUploader(
net::URLRequestContextGetter* request_context_getter,
base::StringPiece server_url,
base::StringPiece mime_type,
MetricsLogUploader::MetricServiceType service_type,
const MetricsLogUploader::UploadCallback& on_upload_complete)
: request_context_getter_(request_context_getter),
server_url_(server_url),
mime_type_(mime_type.data(), mime_type.size()),
service_type_(service_type),
on_upload_complete_(on_upload_complete) {}
NetMetricsLogUploader::NetMetricsLogUploader(
net::URLRequestContextGetter* request_context_getter,
base::StringPiece server_url,
base::StringPiece insecure_server_url,
base::StringPiece mime_type,
MetricsLogUploader::MetricServiceType service_type,
const MetricsLogUploader::UploadCallback& on_upload_complete)
: request_context_getter_(request_context_getter),
server_url_(server_url),
insecure_server_url_(insecure_server_url),
mime_type_(mime_type.data(), mime_type.size()),
service_type_(service_type),
on_upload_complete_(on_upload_complete) {}
NetMetricsLogUploader::~NetMetricsLogUploader() {
}
void NetMetricsLogUploader::UploadLog(const std::string& compressed_log_data,
const std::string& log_hash,
const ReportingInfo& reporting_info) {
// If this attempt is a retry, there was a network error, the last attempt was
// over https, and there is an insecure url set, attempt this upload over
// HTTP.
// Currently we only retry over HTTP if the retry-uma-over-http flag is set.
if (!insecure_server_url_.is_empty() && reporting_info.attempt_count() > 1 &&
reporting_info.last_error_code() != 0 &&
reporting_info.last_attempt_was_https() &&
base::FeatureList::IsEnabled(kHttpRetryFeature)) {
UploadLogToURL(compressed_log_data, log_hash, reporting_info,
insecure_server_url_);
return;
}
UploadLogToURL(compressed_log_data, log_hash, reporting_info, server_url_);
}
void NetMetricsLogUploader::UploadLogToURL(
const std::string& compressed_log_data,
const std::string& log_hash,
const ReportingInfo& reporting_info,
const GURL& url) {
DCHECK(!log_hash.empty());
current_fetch_ =
net::URLFetcher::Create(url, net::URLFetcher::POST, this,
GetNetworkTrafficAnnotation(service_type_));
auto service = data_use_measurement::DataUseUserData::UMA;
switch (service_type_) {
case MetricsLogUploader::UMA:
service = data_use_measurement::DataUseUserData::UMA;
break;
case MetricsLogUploader::UKM:
service = data_use_measurement::DataUseUserData::UKM;
break;
}
data_use_measurement::DataUseUserData::AttachToFetcher(current_fetch_.get(),
service);
current_fetch_->SetRequestContext(request_context_getter_);
std::string reporting_info_string = SerializeReportingInfo(reporting_info);
// If we are not using HTTPS for this upload, encrypt it.
if (!url.SchemeIs(url::kHttpsScheme)) {
std::string encrypted_message;
if (!EncryptString(compressed_log_data, &encrypted_message)) {
current_fetch_.reset();
on_upload_complete_.Run(0, net::ERR_FAILED, false);
return;
}
current_fetch_->SetUploadData(mime_type_, encrypted_message);
std::string encrypted_hash;
std::string base64_encoded_hash;
if (!EncryptString(log_hash, &encrypted_hash)) {
current_fetch_.reset();
on_upload_complete_.Run(0, net::ERR_FAILED, false);
return;
}
base::Base64Encode(encrypted_hash, &base64_encoded_hash);
current_fetch_->AddExtraRequestHeader("X-Chrome-UMA-Log-SHA1: " +
base64_encoded_hash);
std::string encrypted_reporting_info;
std::string base64_reporting_info;
if (!EncryptString(reporting_info_string, &encrypted_reporting_info)) {
current_fetch_.reset();
on_upload_complete_.Run(0, net::ERR_FAILED, false);
return;
}
base::Base64Encode(encrypted_reporting_info, &base64_reporting_info);
current_fetch_->AddExtraRequestHeader("X-Chrome-UMA-ReportingInfo: " +
base64_reporting_info);
} else {
current_fetch_->AddExtraRequestHeader("X-Chrome-UMA-Log-SHA1: " + log_hash);
current_fetch_->AddExtraRequestHeader("X-Chrome-UMA-ReportingInfo:" +
reporting_info_string);
current_fetch_->SetUploadData(mime_type_, compressed_log_data);
// Tell the server that we're uploading gzipped protobufs only if we are not
// encrypting, since encrypted messages have to be decrypted server side
// after decryption, not before.
current_fetch_->AddExtraRequestHeader("content-encoding: gzip");
}
// Drop cookies and auth data.
current_fetch_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DO_NOT_SEND_AUTH_DATA |
net::LOAD_DO_NOT_SEND_COOKIES);
current_fetch_->Start();
}
void NetMetricsLogUploader::OnURLFetchComplete(const net::URLFetcher* source) {
// We're not allowed to re-use the existing |URLFetcher|s, so free them here.
// Note however that |source| is aliased to the fetcher, so we should be
// careful not to delete it too early.
DCHECK_EQ(current_fetch_.get(), source);
int response_code = source->GetResponseCode();
if (response_code == net::URLFetcher::RESPONSE_CODE_INVALID)
response_code = -1;
int error_code = 0;
const net::URLRequestStatus& request_status = source->GetStatus();
if (request_status.status() != net::URLRequestStatus::SUCCESS) {
error_code = request_status.error();
}
bool was_https = source->GetURL().SchemeIs(url::kHttpsScheme);
current_fetch_.reset();
on_upload_complete_.Run(response_code, error_code, was_https);
}
bool NetMetricsLogUploader::EncryptString(const std::string& plaintext,
std::string* encrypted) {
encrypted_messages::EncryptedMessage encrypted_message;
if (!encrypted_messages::EncryptSerializedMessage(
kServerPublicKey, kServerPublicKeyVersion, kEncryptedMessageLabel,
plaintext, &encrypted_message) ||
!encrypted_message.SerializeToString(encrypted)) {
return false;
}
return true;
}
} // namespace metrics