| // 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/base/url_util.h" | 
 | #include "net/traffic_annotation/network_traffic_annotation.h" | 
 | #include "net/url_request/url_fetcher.h" | 
 | #include "services/network/public/cpp/shared_url_loader_factory.h" | 
 | #include "services/network/public/cpp/simple_url_loader.h" | 
 | #include "third_party/metrics_proto/reporting_info.pb.h" | 
 | #include "url/gurl.h" | 
 |  | 
 | namespace { | 
 |  | 
 | const base::Feature kHttpRetryFeature{"UMAHttpRetry", | 
 |                                       base::FEATURE_ENABLED_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; | 
 | } | 
 |  | 
 | void RecordUploadSizeForServiceTypeHistograms( | 
 |     int64_t content_length, | 
 |     metrics::MetricsLogUploader::MetricServiceType service_type) { | 
 |   switch (service_type) { | 
 |     case metrics::MetricsLogUploader::UMA: | 
 |       UMA_HISTOGRAM_COUNTS_1M("UMA.LogUploader.UploadSize", content_length); | 
 |       break; | 
 |     case metrics::MetricsLogUploader::UKM: | 
 |       UMA_HISTOGRAM_COUNTS_1M("UKM.LogUploader.UploadSize", content_length); | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | namespace metrics { | 
 |  | 
 | NetMetricsLogUploader::NetMetricsLogUploader( | 
 |     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, | 
 |     base::StringPiece server_url, | 
 |     base::StringPiece mime_type, | 
 |     MetricsLogUploader::MetricServiceType service_type, | 
 |     const MetricsLogUploader::UploadCallback& on_upload_complete) | 
 |     : url_loader_factory_(std::move(url_loader_factory)), | 
 |       server_url_(server_url), | 
 |       mime_type_(mime_type.data(), mime_type.size()), | 
 |       service_type_(service_type), | 
 |       on_upload_complete_(on_upload_complete) {} | 
 |  | 
 | NetMetricsLogUploader::NetMetricsLogUploader( | 
 |     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, | 
 |     base::StringPiece server_url, | 
 |     base::StringPiece insecure_server_url, | 
 |     base::StringPiece mime_type, | 
 |     MetricsLogUploader::MetricServiceType service_type, | 
 |     const MetricsLogUploader::UploadCallback& on_upload_complete) | 
 |     : url_loader_factory_(std::move(url_loader_factory)), | 
 |       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()); | 
 |  | 
 |   // TODO(crbug.com/808498): Restore the data use measurement when bug is fixed. | 
 |  | 
 |   auto resource_request = std::make_unique<network::ResourceRequest>(); | 
 |   resource_request->url = url; | 
 |   // Drop cookies and auth data. | 
 |   resource_request->allow_credentials = false; | 
 |   resource_request->method = "POST"; | 
 |  | 
 |   std::string reporting_info_string = SerializeReportingInfo(reporting_info); | 
 |   // If we are not using HTTPS for this upload, encrypt it. We do not encrypt | 
 |   // requests to localhost to allow testing with a local collector that doesn't | 
 |   // have decryption enabled. | 
 |   bool should_encrypt = | 
 |       !url.SchemeIs(url::kHttpsScheme) && !net::IsLocalhost(url); | 
 |   if (should_encrypt) { | 
 |     std::string encrypted_hash; | 
 |     std::string base64_encoded_hash; | 
 |     if (!EncryptString(log_hash, &encrypted_hash)) { | 
 |       on_upload_complete_.Run(0, net::ERR_FAILED, false); | 
 |       return; | 
 |     } | 
 |     base::Base64Encode(encrypted_hash, &base64_encoded_hash); | 
 |     resource_request->headers.SetHeader("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)) { | 
 |       on_upload_complete_.Run(0, net::ERR_FAILED, false); | 
 |       return; | 
 |     } | 
 |     base::Base64Encode(encrypted_reporting_info, &base64_reporting_info); | 
 |     resource_request->headers.SetHeader("X-Chrome-UMA-ReportingInfo", | 
 |                                         base64_reporting_info); | 
 |   } else { | 
 |     resource_request->headers.SetHeader("X-Chrome-UMA-Log-SHA1", log_hash); | 
 |     resource_request->headers.SetHeader("X-Chrome-UMA-ReportingInfo", | 
 |                                         reporting_info_string); | 
 |     // 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. | 
 |     resource_request->headers.SetHeader("content-encoding", "gzip"); | 
 |   } | 
 |  | 
 |   url_loader_ = network::SimpleURLLoader::Create( | 
 |       std::move(resource_request), GetNetworkTrafficAnnotation(service_type_)); | 
 |  | 
 |   if (should_encrypt) { | 
 |     std::string encrypted_message; | 
 |     if (!EncryptString(compressed_log_data, &encrypted_message)) { | 
 |       url_loader_.reset(); | 
 |       on_upload_complete_.Run(0, net::ERR_FAILED, false); | 
 |       return; | 
 |     } | 
 |     url_loader_->AttachStringForUpload(encrypted_message, mime_type_); | 
 |     RecordUploadSizeForServiceTypeHistograms(encrypted_message.size(), | 
 |                                              service_type_); | 
 |   } else { | 
 |     url_loader_->AttachStringForUpload(compressed_log_data, mime_type_); | 
 |     RecordUploadSizeForServiceTypeHistograms(compressed_log_data.size(), | 
 |                                              service_type_); | 
 |   } | 
 |  | 
 |   // It's safe to use |base::Unretained(this)| here, because |this| owns | 
 |   // the |url_loader_|, and the callback will be cancelled if the |url_loader_| | 
 |   // is destroyed. | 
 |   url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie( | 
 |       url_loader_factory_.get(), | 
 |       base::BindOnce(&NetMetricsLogUploader::OnURLLoadComplete, | 
 |                      base::Unretained(this))); | 
 | } | 
 |  | 
 | // The callback is only invoked if |url_loader_| it was bound against is alive. | 
 | void NetMetricsLogUploader::OnURLLoadComplete( | 
 |     std::unique_ptr<std::string> response_body) { | 
 |   int response_code = -1; | 
 |   if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers) | 
 |     response_code = url_loader_->ResponseInfo()->headers->response_code(); | 
 |  | 
 |   int error_code = url_loader_->NetError(); | 
 |  | 
 |   bool was_https = url_loader_->GetFinalURL().SchemeIs(url::kHttpsScheme); | 
 |   url_loader_.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 |