| // Copyright 2015 The Chromium Authors |
| // 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 <memory> |
| |
| #include "base/base64.h" |
| #include "base/functional/bind.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/task_environment.h" |
| #include "components/encrypted_messages/encrypted_message.pb.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "services/network/test/test_utils.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/metrics_proto/reporting_info.pb.h" |
| #include "third_party/zlib/google/compression_utils.h" |
| #include "url/gurl.h" |
| |
| namespace metrics { |
| |
| class NetMetricsLogUploaderTest : public testing::Test { |
| public: |
| NetMetricsLogUploaderTest() |
| : on_upload_complete_count_(0), |
| test_shared_url_loader_factory_( |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory_)) { |
| test_url_loader_factory_.SetInterceptor(base::BindLambdaForTesting( |
| [&](const network::ResourceRequest& request) { |
| upload_data_ = network::GetUploadData(request); |
| headers_ = request.headers; |
| loop_.Quit(); |
| })); |
| } |
| |
| NetMetricsLogUploaderTest(const NetMetricsLogUploaderTest&) = delete; |
| NetMetricsLogUploaderTest& operator=(const NetMetricsLogUploaderTest&) = |
| delete; |
| |
| void CreateAndOnUploadCompleteReuseUploader() { |
| ReportingInfo reporting_info; |
| reporting_info.set_attempt_count(10); |
| uploader_ = std::make_unique<NetMetricsLogUploader>( |
| test_shared_url_loader_factory_, GURL("https://dummy_server"), |
| "dummy_mime", MetricsLogUploader::UMA, |
| base::BindRepeating( |
| &NetMetricsLogUploaderTest::OnUploadCompleteReuseUploader, |
| base::Unretained(this))); |
| uploader_->UploadLog("initial_dummy_data", LogMetadata(), |
| "initial_dummy_hash", "initial_dummy_signature", |
| reporting_info); |
| } |
| |
| void CreateUploaderAndUploadToSecureURL(const std::string& url) { |
| ReportingInfo dummy_reporting_info; |
| uploader_ = std::make_unique<NetMetricsLogUploader>( |
| test_shared_url_loader_factory_, GURL(url), "dummy_mime", |
| MetricsLogUploader::UMA, |
| base::BindRepeating(&NetMetricsLogUploaderTest::DummyOnUploadComplete, |
| base::Unretained(this))); |
| uploader_->UploadLog("dummy_data", LogMetadata(), "dummy_hash", |
| "dummy_signature", dummy_reporting_info); |
| } |
| |
| void CreateUploaderAndUploadToInsecureURL() { |
| ReportingInfo dummy_reporting_info; |
| uploader_ = std::make_unique<NetMetricsLogUploader>( |
| test_shared_url_loader_factory_, GURL("http://dummy_insecure_server"), |
| "dummy_mime", MetricsLogUploader::UMA, |
| base::BindRepeating(&NetMetricsLogUploaderTest::DummyOnUploadComplete, |
| base::Unretained(this))); |
| std::string compressed_message; |
| // Compress the data since the encryption code expects a compressed log, |
| // and tries to decompress it before encrypting it. |
| compression::GzipCompress("dummy_data", &compressed_message); |
| uploader_->UploadLog(compressed_message, LogMetadata(), "dummy_hash", |
| "dummy_signature", dummy_reporting_info); |
| } |
| |
| void DummyOnUploadComplete(int response_code, |
| int error_code, |
| bool was_https, |
| bool force_discard, |
| base::StringPiece force_discard_reason) { |
| log_was_force_discarded_ = force_discard; |
| } |
| |
| void OnUploadCompleteReuseUploader(int response_code, |
| int error_code, |
| bool was_https, |
| bool force_discard, |
| base::StringPiece force_discard_reason) { |
| ++on_upload_complete_count_; |
| if (on_upload_complete_count_ == 1) { |
| ReportingInfo reporting_info; |
| reporting_info.set_attempt_count(20); |
| uploader_->UploadLog("dummy_data", LogMetadata(), "dummy_hash", |
| "dummy_signature", reporting_info); |
| } |
| log_was_force_discarded_ = force_discard; |
| } |
| |
| int on_upload_complete_count() const { |
| return on_upload_complete_count_; |
| } |
| |
| network::TestURLLoaderFactory* test_url_loader_factory() { |
| return &test_url_loader_factory_; |
| } |
| |
| const net::HttpRequestHeaders& last_request_headers() { return headers_; } |
| |
| const std::string& last_upload_data() { return upload_data_; } |
| |
| void WaitForRequest() { loop_.Run(); } |
| |
| bool log_was_force_discarded() { return log_was_force_discarded_; } |
| |
| private: |
| std::unique_ptr<NetMetricsLogUploader> uploader_; |
| int on_upload_complete_count_; |
| |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| scoped_refptr<network::SharedURLLoaderFactory> |
| test_shared_url_loader_factory_; |
| |
| base::test::TaskEnvironment task_environment_; |
| |
| base::RunLoop loop_; |
| std::string upload_data_; |
| net::HttpRequestHeaders headers_; |
| bool log_was_force_discarded_ = false; |
| }; |
| |
| void CheckReportingInfoHeader(net::HttpRequestHeaders headers, |
| int expected_attempt_count) { |
| std::string reporting_info_base64; |
| EXPECT_TRUE( |
| headers.GetHeader("X-Chrome-UMA-ReportingInfo", &reporting_info_base64)); |
| std::string reporting_info_string; |
| EXPECT_TRUE( |
| base::Base64Decode(reporting_info_base64, &reporting_info_string)); |
| ReportingInfo reporting_info; |
| EXPECT_TRUE(reporting_info.ParseFromString(reporting_info_string)); |
| EXPECT_EQ(reporting_info.attempt_count(), expected_attempt_count); |
| } |
| |
| TEST_F(NetMetricsLogUploaderTest, OnUploadCompleteReuseUploader) { |
| CreateAndOnUploadCompleteReuseUploader(); |
| WaitForRequest(); |
| |
| // Mimic the initial fetcher callback. |
| CheckReportingInfoHeader(last_request_headers(), 10); |
| auto* pending_request_0 = test_url_loader_factory()->GetPendingRequest(0); |
| test_url_loader_factory()->SimulateResponseWithoutRemovingFromPendingList( |
| pending_request_0, ""); |
| |
| // Mimic the second fetcher callback. |
| CheckReportingInfoHeader(last_request_headers(), 20); |
| auto* pending_request_1 = test_url_loader_factory()->GetPendingRequest(1); |
| test_url_loader_factory()->SimulateResponseWithoutRemovingFromPendingList( |
| pending_request_1, ""); |
| |
| EXPECT_EQ(on_upload_complete_count(), 2); |
| EXPECT_FALSE(log_was_force_discarded()); |
| } |
| |
| // Verifies that when no server URLs are specified, the logs are forcibly |
| // discarded. |
| TEST_F(NetMetricsLogUploaderTest, ForceDiscard) { |
| CreateUploaderAndUploadToSecureURL(/*url=*/""); |
| WaitForRequest(); |
| |
| // Mimic the initial fetcher callback. |
| auto* pending_request_0 = test_url_loader_factory()->GetPendingRequest(0); |
| test_url_loader_factory()->SimulateResponseWithoutRemovingFromPendingList( |
| pending_request_0, ""); |
| |
| EXPECT_TRUE(log_was_force_discarded()); |
| } |
| |
| // Test that attempting to upload to an HTTP URL results in an encrypted |
| // message. |
| TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPIsEncrypted) { |
| CreateUploaderAndUploadToInsecureURL(); |
| WaitForRequest(); |
| encrypted_messages::EncryptedMessage message; |
| EXPECT_TRUE(message.ParseFromString(last_upload_data())); |
| } |
| |
| // Test that attempting to upload to an HTTPS URL results in an unencrypted |
| // message. |
| TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPSIsNotEncrypted) { |
| CreateUploaderAndUploadToSecureURL("https://dummy_secure_server"); |
| WaitForRequest(); |
| EXPECT_EQ(last_upload_data(), "dummy_data"); |
| } |
| |
| // Test that attempting to upload to localhost over http results in an |
| // unencrypted message. |
| TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPLocalhostIsNotEncrypted) { |
| CreateUploaderAndUploadToSecureURL("http://localhost"); |
| WaitForRequest(); |
| EXPECT_EQ(last_upload_data(), "dummy_data"); |
| } |
| |
| } // namespace metrics |