| // Copyright 2016 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/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/macros.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/histogram_tester.h" |
| #include "base/test/mock_entropy_provider.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_config_test_utils.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_metrics.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_mutable_config_values.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers_test_utils.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params_test_utils.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_pref_names.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_server.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h" |
| #include "components/data_reduction_proxy/proto/client_config.pb.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/network_change_notifier.h" |
| #include "net/base/proxy_delegate.h" |
| #include "net/base/proxy_server.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_util.h" |
| #include "net/proxy_resolution/proxy_config.h" |
| #include "net/socket/socket_test_util.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::_; |
| |
| namespace data_reduction_proxy { |
| |
| namespace { |
| |
| // Constructs and returns a proxy with the specified scheme. |
| net::ProxyServer GetProxyWithScheme(net::ProxyServer::Scheme scheme) { |
| switch (scheme) { |
| case net::ProxyServer::SCHEME_HTTP: |
| return net::ProxyServer::FromURI("origin.net:443", |
| net::ProxyServer::SCHEME_HTTP); |
| case net::ProxyServer::SCHEME_HTTPS: |
| return net::ProxyServer::FromURI("https://origin.net:443", |
| net::ProxyServer::SCHEME_HTTP); |
| case net::ProxyServer::SCHEME_QUIC: |
| return net::ProxyServer::FromURI("quic://origin.net:443", |
| net::ProxyServer::SCHEME_QUIC); |
| case net::ProxyServer::SCHEME_DIRECT: |
| return net::ProxyServer::Direct(); |
| default: |
| NOTREACHED(); |
| return net::ProxyServer::FromURI("", net::ProxyServer::SCHEME_INVALID); |
| } |
| } |
| |
| class TestDataReductionProxyDelegate : public DataReductionProxyDelegate { |
| public: |
| TestDataReductionProxyDelegate( |
| DataReductionProxyConfig* config, |
| const DataReductionProxyConfigurator* configurator, |
| DataReductionProxyEventCreator* event_creator, |
| DataReductionProxyBypassStats* bypass_stats, |
| bool proxy_supports_quic, |
| net::NetLog* net_log) |
| : DataReductionProxyDelegate(config, |
| configurator, |
| event_creator, |
| bypass_stats, |
| net_log), |
| proxy_supports_quic_(proxy_supports_quic) {} |
| |
| ~TestDataReductionProxyDelegate() override {} |
| |
| bool SupportsQUIC(const net::ProxyServer& proxy_server) const override { |
| return proxy_supports_quic_; |
| } |
| |
| // Verifies if the histograms related to use of QUIC proxy are recorded |
| // correctly. |
| void VerifyQuicHistogramCounts(const base::HistogramTester& histogram_tester, |
| bool expect_alternative_proxy_server, |
| bool supports_quic, |
| bool broken) const { |
| if (expect_alternative_proxy_server && !broken) { |
| histogram_tester.ExpectUniqueSample( |
| "DataReductionProxy.Quic.ProxyStatus", |
| TestDataReductionProxyDelegate::QuicProxyStatus:: |
| QUIC_PROXY_STATUS_AVAILABLE, |
| 1); |
| } else if (!supports_quic && !broken) { |
| histogram_tester.ExpectUniqueSample( |
| "DataReductionProxy.Quic.ProxyStatus", |
| TestDataReductionProxyDelegate::QuicProxyStatus:: |
| QUIC_PROXY_NOT_SUPPORTED, |
| 1); |
| } else { |
| ASSERT_TRUE(broken); |
| histogram_tester.ExpectUniqueSample( |
| "DataReductionProxy.Quic.ProxyStatus", |
| TestDataReductionProxyDelegate::QuicProxyStatus:: |
| QUIC_PROXY_STATUS_MARKED_AS_BROKEN, |
| 1); |
| } |
| } |
| |
| using DataReductionProxyDelegate::QuicProxyStatus; |
| |
| private: |
| const bool proxy_supports_quic_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestDataReductionProxyDelegate); |
| }; |
| |
| #if defined(OS_ANDROID) |
| const Client kClient = Client::CHROME_ANDROID; |
| #elif defined(OS_IOS) |
| const Client kClient = Client::CHROME_IOS; |
| #elif defined(OS_MACOSX) |
| const Client kClient = Client::CHROME_MAC; |
| #elif defined(OS_CHROMEOS) |
| const Client kClient = Client::CHROME_CHROMEOS; |
| #elif defined(OS_LINUX) |
| const Client kClient = Client::CHROME_LINUX; |
| #elif defined(OS_WIN) |
| const Client kClient = Client::CHROME_WINDOWS; |
| #elif defined(OS_FREEBSD) |
| const Client kClient = Client::CHROME_FREEBSD; |
| #elif defined(OS_OPENBSD) |
| const Client kClient = Client::CHROME_OPENBSD; |
| #elif defined(OS_SOLARIS) |
| const Client kClient = Client::CHROME_SOLARIS; |
| #elif defined(OS_QNX) |
| const Client kClient = Client::CHROME_QNX; |
| #else |
| const Client kClient = Client::UNKNOWN; |
| #endif |
| |
| class TestLoFiUIService : public LoFiUIService { |
| public: |
| TestLoFiUIService() {} |
| ~TestLoFiUIService() override {} |
| |
| void OnLoFiReponseReceived(const net::URLRequest& request) override {} |
| }; |
| |
| class DataReductionProxyDelegateTest : public testing::Test { |
| public: |
| DataReductionProxyDelegateTest() |
| : context_(true), |
| context_storage_(&context_), |
| test_context_(DataReductionProxyTestContext::Builder() |
| .WithClient(kClient) |
| .WithMockClientSocketFactory(&mock_socket_factory_) |
| .WithURLRequestContext(&context_) |
| .Build()) { |
| context_.set_client_socket_factory(&mock_socket_factory_); |
| test_context_->AttachToURLRequestContext(&context_storage_); |
| |
| std::unique_ptr<TestLoFiUIService> lofi_ui_service(new TestLoFiUIService()); |
| lofi_ui_service_ = lofi_ui_service.get(); |
| test_context_->io_data()->set_lofi_ui_service(std::move(lofi_ui_service)); |
| |
| // Create a mock network change notifier to make it possible to call its |
| // static methods. |
| network_change_notifier_.reset(net::NetworkChangeNotifier::CreateMock()); |
| base::RunLoop().RunUntilIdle(); |
| |
| proxy_delegate_ = test_context_->io_data()->CreateProxyDelegate(); |
| context_.set_proxy_delegate(proxy_delegate_.get()); |
| |
| context_.Init(); |
| proxy_delegate_->InitializeOnIOThread(test_context_->io_data()); |
| |
| test_context_->DisableWarmupURLFetch(); |
| test_context_->EnableDataReductionProxyWithSecureProxyCheckSuccess(); |
| } |
| |
| // Each line in |response_headers| should end with "\r\n" and not '\0', and |
| // the last line should have a second "\r\n". |
| // An empty |response_headers| is allowed. It works by making this look like |
| // an HTTP/0.9 response, since HTTP/0.9 responses don't have headers. |
| std::unique_ptr<net::URLRequest> FetchURLRequest( |
| const GURL& url, |
| net::HttpRequestHeaders* request_headers, |
| const std::string& response_headers, |
| int64_t response_content_length) { |
| const std::string response_body( |
| base::checked_cast<size_t>(response_content_length), ' '); |
| net::MockRead reads[] = {net::MockRead(response_headers.c_str()), |
| net::MockRead(response_body.c_str()), |
| net::MockRead(net::SYNCHRONOUS, net::OK)}; |
| net::StaticSocketDataProvider socket(reads, base::span<net::MockWrite>()); |
| mock_socket_factory_.AddSocketDataProvider(&socket); |
| |
| net::TestDelegate delegate; |
| std::unique_ptr<net::URLRequest> request = context_.CreateRequest( |
| url, net::IDLE, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS); |
| if (request_headers) |
| request->SetExtraRequestHeaders(*request_headers); |
| |
| request->Start(); |
| base::RunLoop().RunUntilIdle(); |
| return request; |
| } |
| |
| int64_t total_received_bytes() const { |
| test_context_->RunUntilIdle(); |
| return GetSessionNetworkStatsInfoInt64("session_received_content_length"); |
| } |
| |
| int64_t total_original_received_bytes() const { |
| test_context_->RunUntilIdle(); |
| return GetSessionNetworkStatsInfoInt64("session_original_content_length"); |
| } |
| |
| net::MockClientSocketFactory* mock_socket_factory() { |
| return &mock_socket_factory_; |
| } |
| |
| net::TestURLRequestContext* context() { return &context_; } |
| |
| TestDataReductionProxyParams* params() const { |
| return test_context_->config()->test_params(); |
| } |
| |
| TestDataReductionProxyConfig* config() const { |
| return test_context_->config(); |
| } |
| |
| TestDataReductionProxyIOData* io_data() const { |
| return test_context_->io_data(); |
| } |
| |
| DataReductionProxyDelegate* proxy_delegate() const { |
| return proxy_delegate_.get(); |
| } |
| |
| private: |
| int64_t GetSessionNetworkStatsInfoInt64(const char* key) const { |
| std::unique_ptr<base::DictionaryValue> session_network_stats_info = |
| base::DictionaryValue::From(test_context_->settings() |
| ->data_reduction_proxy_service() |
| ->compression_stats() |
| ->SessionNetworkStatsInfoToValue()); |
| EXPECT_TRUE(session_network_stats_info); |
| |
| std::string string_value; |
| EXPECT_TRUE(session_network_stats_info->GetString(key, &string_value)); |
| int64_t value = 0; |
| EXPECT_TRUE(base::StringToInt64(string_value, &value)); |
| return value; |
| } |
| |
| base::MessageLoopForIO message_loop_; |
| net::MockClientSocketFactory mock_socket_factory_; |
| net::TestURLRequestContext context_; |
| net::URLRequestContextStorage context_storage_; |
| |
| TestLoFiUIService* lofi_ui_service_; |
| |
| std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_; |
| std::unique_ptr<DataReductionProxyDelegate> proxy_delegate_; |
| std::unique_ptr<DataReductionProxyTestContext> test_context_; |
| }; |
| |
| TEST_F(DataReductionProxyDelegateTest, OnResolveProxy) { |
| GURL url("http://www.google.com/"); |
| params()->UseNonSecureProxiesForHttp(); |
| |
| // Other proxy info |
| net::ProxyInfo other_proxy_info; |
| other_proxy_info.UseNamedProxy("proxy.com"); |
| EXPECT_FALSE(other_proxy_info.is_empty()); |
| |
| // Direct |
| net::ProxyInfo direct_proxy_info; |
| direct_proxy_info.UseDirect(); |
| EXPECT_TRUE(direct_proxy_info.is_direct()); |
| |
| // Empty retry info map |
| net::ProxyRetryInfoMap empty_proxy_retry_info; |
| |
| // Retry info map with the data reduction proxy; |
| net::ProxyRetryInfoMap data_reduction_proxy_retry_info; |
| net::ProxyRetryInfo retry_info; |
| retry_info.current_delay = base::TimeDelta::FromSeconds(1000); |
| retry_info.bad_until = base::TimeTicks().Now() + retry_info.current_delay; |
| retry_info.try_while_bad = false; |
| data_reduction_proxy_retry_info |
| [params()->proxies_for_http().front().proxy_server().ToURI()] = |
| retry_info; |
| |
| net::ProxyInfo result; |
| // Another proxy is used. It should be used afterwards. |
| result.Use(other_proxy_info); |
| proxy_delegate()->OnResolveProxy(url, "GET", empty_proxy_retry_info, &result); |
| EXPECT_EQ(other_proxy_info.proxy_server(), result.proxy_server()); |
| |
| // A direct connection is used. The data reduction proxy should be used |
| // afterwards. |
| // Another proxy is used. It should be used afterwards. |
| result.Use(direct_proxy_info); |
| proxy_delegate()->OnResolveProxy(url, "GET", empty_proxy_retry_info, &result); |
| EXPECT_EQ(params()->proxies_for_http().front().proxy_server(), |
| result.proxy_server()); |
| |
| // A direct connection is used, but the data reduction proxy is on the retry |
| // list. A direct connection should be used afterwards. |
| result.Use(direct_proxy_info); |
| proxy_delegate()->OnResolveProxy(GURL("ws://echo.websocket.org/"), "GET", |
| data_reduction_proxy_retry_info, &result); |
| EXPECT_TRUE(result.proxy_server().is_direct()); |
| |
| // Test that ws:// and wss:// URLs bypass the data reduction proxy. |
| result.UseDirect(); |
| proxy_delegate()->OnResolveProxy(GURL("wss://echo.websocket.org/"), "GET", |
| empty_proxy_retry_info, &result); |
| EXPECT_TRUE(result.is_direct()); |
| |
| result.UseDirect(); |
| proxy_delegate()->OnResolveProxy(GURL("wss://echo.websocket.org/"), "GET", |
| empty_proxy_retry_info, &result); |
| EXPECT_TRUE(result.is_direct()); |
| |
| // POST methods go direct. |
| result.UseDirect(); |
| proxy_delegate()->OnResolveProxy(url, "POST", empty_proxy_retry_info, |
| &result); |
| EXPECT_TRUE(result.is_direct()); |
| } |
| |
| TEST_F(DataReductionProxyDelegateTest, OnResolveProxyWarmupURL) { |
| const struct { |
| bool is_secure_proxy; |
| bool is_core_proxy; |
| bool use_warmup_url; |
| } tests[] = { |
| {false, false, false}, {false, true, false}, {true, false, false}, |
| {true, true, false}, {false, false, true}, {false, true, true}, |
| {true, false, true}, {true, true, true}, |
| }; |
| |
| for (const auto& test : tests) { |
| config()->SetInFlightWarmupProxyDetails( |
| std::make_pair(test.is_secure_proxy, test.is_core_proxy)); |
| GURL url; |
| if (test.use_warmup_url) { |
| url = params::GetWarmupURL(); |
| } else { |
| url = GURL("http://www.google.com"); |
| } |
| params()->UseNonSecureProxiesForHttp(); |
| |
| // A regular HTTP URL (i.e., a non-warmup URL) should always be fetched |
| // using the data reduction proxies configured by this test framework. |
| bool expect_data_reduction_proxy_used = true; |
| |
| if (test.use_warmup_url) { |
| // This test framework only sets insecure, core proxies in the data |
| // reduction proxy configuration. When the in-flight warmup proxy details |
| // are set to a proxy that is either secure or non-core, then all |
| // configured data saver proxies should be removed when doing proxy |
| // resolution for the warmup URL. Hence, the warmup URL will be fetched |
| // directly in all cases except when the in-flight warmup proxy details |
| // match the properties of the data saver proxies configured by this test. |
| expect_data_reduction_proxy_used = |
| !test.is_secure_proxy && test.is_core_proxy; |
| } |
| |
| // Other proxy info |
| net::ProxyInfo other_proxy_info; |
| other_proxy_info.UseNamedProxy("proxy.com"); |
| EXPECT_FALSE(other_proxy_info.is_empty()); |
| |
| // Direct |
| net::ProxyInfo direct_proxy_info; |
| direct_proxy_info.UseDirect(); |
| EXPECT_TRUE(direct_proxy_info.is_direct()); |
| |
| // Empty retry info map |
| net::ProxyRetryInfoMap empty_proxy_retry_info; |
| |
| net::ProxyInfo result; |
| // Another proxy is used. It should be used afterwards. |
| result.Use(other_proxy_info); |
| proxy_delegate()->OnResolveProxy(url, "GET", empty_proxy_retry_info, |
| &result); |
| EXPECT_EQ(other_proxy_info.proxy_server(), result.proxy_server()); |
| |
| // A direct connection is used. The data reduction proxy should be used |
| // afterwards. |
| result.Use(direct_proxy_info); |
| proxy_delegate()->OnResolveProxy(url, "GET", empty_proxy_retry_info, |
| &result); |
| // |
| if (expect_data_reduction_proxy_used) { |
| EXPECT_EQ(params()->proxies_for_http().front().proxy_server(), |
| result.proxy_server()); |
| } else { |
| EXPECT_TRUE(result.proxy_server().is_direct()); |
| } |
| } |
| } |
| |
| // Verifies that DataReductionProxyDelegate correctly implements |
| // alternative proxy functionality. |
| TEST_F(DataReductionProxyDelegateTest, AlternativeProxy) { |
| const struct { |
| bool is_in_quic_field_trial; |
| bool proxy_supports_quic; |
| net::ProxyServer::Scheme first_proxy_scheme; |
| net::ProxyServer::Scheme second_proxy_scheme; |
| } tests[] = {{false, true, net::ProxyServer::SCHEME_HTTPS, |
| net::ProxyServer::SCHEME_HTTP}, |
| {true, true, net::ProxyServer::SCHEME_HTTPS, |
| net::ProxyServer::SCHEME_HTTP}, |
| {true, true, net::ProxyServer::SCHEME_HTTP, |
| net::ProxyServer::SCHEME_HTTPS}, |
| {true, true, net::ProxyServer::SCHEME_QUIC, |
| net::ProxyServer::SCHEME_HTTP}, |
| {true, true, net::ProxyServer::SCHEME_QUIC, |
| net::ProxyServer::SCHEME_HTTPS}, |
| {true, false, net::ProxyServer::SCHEME_HTTPS, |
| net::ProxyServer::SCHEME_HTTP}, |
| {true, false, net::ProxyServer::SCHEME_HTTP, |
| net::ProxyServer::SCHEME_HTTPS}}; |
| GURL url("http://www.example.com"); |
| |
| for (const auto test : tests) { |
| // True if there should exist a valid alternative proxy server corresponding |
| // to the first proxy in the list of proxies available to the data reduction |
| // proxy. |
| const bool expect_alternative_proxy_server_to_first_proxy = |
| test.is_in_quic_field_trial && test.proxy_supports_quic && |
| test.first_proxy_scheme == net::ProxyServer::SCHEME_HTTPS; |
| |
| // True if there should exist a valid alternative proxy server corresponding |
| // to the second proxy in the list of proxies available to the data |
| // reduction proxy. |
| const bool expect_alternative_proxy_server_to_second_proxy = |
| test.is_in_quic_field_trial && test.proxy_supports_quic && |
| test.second_proxy_scheme == net::ProxyServer::SCHEME_HTTPS; |
| |
| std::vector<DataReductionProxyServer> proxies_for_http; |
| |
| net::ProxyServer first_proxy = GetProxyWithScheme(test.first_proxy_scheme); |
| proxies_for_http.push_back( |
| DataReductionProxyServer(first_proxy, ProxyServer::CORE)); |
| |
| net::ProxyServer second_proxy = |
| GetProxyWithScheme(test.second_proxy_scheme); |
| proxies_for_http.push_back( |
| DataReductionProxyServer(second_proxy, ProxyServer::UNSPECIFIED_TYPE)); |
| |
| params()->SetProxiesForHttpForTesting(proxies_for_http); |
| |
| TestDataReductionProxyDelegate delegate( |
| config(), io_data()->configurator(), io_data()->event_creator(), |
| io_data()->bypass_stats(), test.proxy_supports_quic, |
| io_data()->net_log()); |
| |
| base::FieldTrialList field_trial_list(nullptr); |
| base::FieldTrialList::CreateFieldTrial( |
| params::GetQuicFieldTrialName(), |
| test.is_in_quic_field_trial ? "Enabled" : "Control"); |
| |
| net::ProxyInfo proxy_info; |
| net::ProxyRetryInfoMap empty_proxy_retry_info; |
| net::ProxyServer alternative_proxy_server_to_first_proxy; |
| net::ProxyServer alternative_proxy_server_to_second_proxy; |
| |
| { |
| proxy_info.UseDirect(); |
| |
| // Test if the alternative proxy is correctly set if the resolved proxy is |
| // |first_proxy|. |
| base::HistogramTester histogram_tester; |
| delegate.OnResolveProxy(url, "GET", empty_proxy_retry_info, &proxy_info); |
| ASSERT_EQ(first_proxy, proxy_info.proxy_server()); |
| alternative_proxy_server_to_first_proxy = proxy_info.alternative_proxy(); |
| EXPECT_EQ(expect_alternative_proxy_server_to_first_proxy, |
| alternative_proxy_server_to_first_proxy.is_valid()); |
| |
| // Verify that the metrics are recorded correctly. |
| if (test.is_in_quic_field_trial && |
| test.first_proxy_scheme == net::ProxyServer::SCHEME_HTTPS) { |
| delegate.VerifyQuicHistogramCounts( |
| histogram_tester, expect_alternative_proxy_server_to_first_proxy, |
| test.proxy_supports_quic, false); |
| } else { |
| if (!test.is_in_quic_field_trial) { |
| histogram_tester.ExpectUniqueSample( |
| "DataReductionProxy.Quic.ProxyStatus", |
| 3 /* QUIC_PROXY_DISABLED_VIA_FIELD_TRIAL */, 1); |
| } else { |
| histogram_tester.ExpectTotalCount( |
| "DataReductionProxy.Quic.ProxyStatus", 0); |
| } |
| } |
| } |
| |
| { |
| // Test if the alternative proxy is correctly set if the resolved proxy is |
| // |second_proxy|. Mark the first proxy as failed so that the second is |
| // selected. |
| proxy_info.UseDirect(); |
| net::ProxyRetryInfoMap proxy_retry_info; |
| net::ProxyRetryInfo bad_proxy_info; |
| bad_proxy_info.bad_until = base::TimeTicks() + base::TimeDelta::Max(); |
| proxy_retry_info[first_proxy.ToURI()] = bad_proxy_info; |
| |
| base::HistogramTester histogram_tester; |
| delegate.OnResolveProxy(url, "GET", proxy_retry_info, &proxy_info); |
| EXPECT_EQ(second_proxy, proxy_info.proxy_server()); |
| alternative_proxy_server_to_second_proxy = proxy_info.alternative_proxy(); |
| EXPECT_EQ(expect_alternative_proxy_server_to_first_proxy, |
| alternative_proxy_server_to_first_proxy.is_valid()); |
| EXPECT_EQ(expect_alternative_proxy_server_to_second_proxy, |
| alternative_proxy_server_to_second_proxy.is_valid()); |
| |
| // Verify that the metrics are recorded correctly. |
| if (test.is_in_quic_field_trial && |
| test.second_proxy_scheme == net::ProxyServer::SCHEME_HTTPS) { |
| delegate.VerifyQuicHistogramCounts( |
| histogram_tester, expect_alternative_proxy_server_to_second_proxy, |
| test.proxy_supports_quic, false); |
| } else { |
| if (!test.is_in_quic_field_trial) { |
| histogram_tester.ExpectUniqueSample( |
| "DataReductionProxy.Quic.ProxyStatus", |
| 3 /* QUIC_PROXY_DISABLED_VIA_FIELD_TRIAL */, 1); |
| } else { |
| histogram_tester.ExpectTotalCount( |
| "DataReductionProxy.Quic.ProxyStatus", 0); |
| } |
| } |
| } |
| |
| { |
| // Test if the alternative proxy is correctly set if the resolved proxy is |
| // a not a data reduction proxy. |
| net::ProxyServer non_drp_proxy_server = net::ProxyServer::FromURI( |
| "not.data.reduction.proxy.net:443", net::ProxyServer::SCHEME_HTTPS); |
| proxy_info.UseProxyServer(non_drp_proxy_server); |
| |
| base::HistogramTester histogram_tester; |
| delegate.OnResolveProxy(url, "GET", empty_proxy_retry_info, &proxy_info); |
| EXPECT_EQ(non_drp_proxy_server, proxy_info.proxy_server()); |
| EXPECT_FALSE(proxy_info.alternative_proxy().is_valid()); |
| |
| // Verify that the metrics are recorded correctly. |
| if (!test.is_in_quic_field_trial) { |
| histogram_tester.ExpectUniqueSample( |
| "DataReductionProxy.Quic.ProxyStatus", |
| 3 /* QUIC_PROXY_DISABLED_VIA_FIELD_TRIAL */, 1); |
| } else { |
| histogram_tester.ExpectTotalCount("DataReductionProxy.Quic.ProxyStatus", |
| 0); |
| } |
| } |
| |
| // Test if the alternative proxy is correctly marked as broken. |
| if (expect_alternative_proxy_server_to_first_proxy) { |
| base::HistogramTester histogram_tester; |
| proxy_info.UseDirect(); |
| |
| // Verify that when the alternative proxy server is reported as broken, |
| // then it is no longer returned when OnResolveProxy is called. |
| net::ProxyRetryInfoMap proxy_retry_info; |
| net::ProxyRetryInfo bad_proxy_info; |
| bad_proxy_info.bad_until = base::TimeTicks() + base::TimeDelta::Max(); |
| bad_proxy_info.try_while_bad = false; |
| net::ProxyServer bad_proxy_server(net::ProxyServer::SCHEME_QUIC, |
| first_proxy.host_port_pair()); |
| proxy_retry_info[bad_proxy_server.ToURI()] = bad_proxy_info; |
| |
| delegate.OnResolveProxy(url, "GET", proxy_retry_info, &proxy_info); |
| ASSERT_EQ(first_proxy, proxy_info.proxy_server()); |
| EXPECT_FALSE(proxy_info.alternative_proxy().is_valid()); |
| |
| delegate.VerifyQuicHistogramCounts( |
| histogram_tester, expect_alternative_proxy_server_to_first_proxy, |
| test.proxy_supports_quic, true); |
| } |
| } |
| } |
| |
| // Verifies that requests that were not proxied through data saver proxy due to |
| // missing config are recorded properly. |
| TEST_F(DataReductionProxyDelegateTest, HTTPRequests) { |
| const struct { |
| const char* url; |
| bool enabled_by_user; |
| bool expect_histogram; |
| } test_cases[] = { |
| { |
| // Request should not be logged because data saver is disabled. |
| "http://www.example.com/", false, false, |
| }, |
| { |
| "http://www.example.com/", true, true, |
| }, |
| { |
| // Request should not be logged because request is HTTPS. |
| "https://www.example.com/", true, false, |
| }, |
| { |
| // Request to localhost should not be logged. |
| "http://127.0.0.1/", true, false, |
| }, |
| { |
| // Special use IPv4 address for testing purposes (RFC 5735). |
| "http://198.51.100.1/", true, true, |
| }, |
| }; |
| |
| for (const auto& test : test_cases) { |
| ASSERT_TRUE(test.enabled_by_user || !test.expect_histogram); |
| base::HistogramTester histogram_tester; |
| GURL url(test.url); |
| |
| config()->UpdateConfigForTesting(test.enabled_by_user /* enabled */, |
| false /* secure_proxies_allowed */, |
| true /* insecure_proxies_allowed */); |
| |
| net::ProxyRetryInfoMap empty_proxy_retry_info; |
| |
| net::ProxyInfo direct_proxy_info; |
| direct_proxy_info.UseDirect(); |
| EXPECT_TRUE(direct_proxy_info.is_direct()); |
| |
| net::ProxyInfo result; |
| result.Use(direct_proxy_info); |
| proxy_delegate()->OnResolveProxy(url, "GET", empty_proxy_retry_info, |
| &result); |
| histogram_tester.ExpectTotalCount( |
| "DataReductionProxy.ConfigService.HTTPRequests", |
| test.expect_histogram ? 1 : 0); |
| |
| if (test.expect_histogram) { |
| histogram_tester.ExpectUniqueSample( |
| "DataReductionProxy.ConfigService.HTTPRequests", 1, 1); |
| } |
| } |
| } |
| |
| TEST_F(DataReductionProxyDelegateTest, OnCompletedSizeFor200) { |
| const struct { |
| const std::string DrpResponseHeaders; |
| } test_cases[] = { |
| { |
| "HTTP/1.1 200 OK\r\n" |
| "Date: Wed, 28 Nov 2007 09:40:09 GMT\r\n" |
| "Warning: 199 Misc-Agent \"some warning text\"\r\n" |
| "Via:\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy-Suffix, 9.9 other-proxy\r\n" |
| "Via: 2.2 Chrome-Compression-Proxy\r\n" |
| "Warning: 214 Chrome-Compression-Proxy \"Transformation Applied\"\r\n" |
| "Chrome-Proxy: q=low,ofcl=10000\r\n" |
| "Content-Length: 1000\r\n\r\n", |
| }, |
| { |
| "HTTP/1.1 200 OK\r\n" |
| "Date: Wed, 28 Nov 2007 09:40:09 GMT\r\n" |
| "Warning: 199 Misc-Agent \"some warning text\"\r\n" |
| "Via:\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy-Suffix, 9.9 other-proxy\r\n" |
| "Via: 2.2 Chrome-Compression-Proxy\r\n" |
| "Warning: 214 Chrome-Compression-Proxy \"Transformation Applied\"\r\n" |
| "Chrome-Proxy: q=low,ofcl=10000\r\n" |
| "Content-Length: 1000\r\n\r\n", |
| }}; |
| |
| params()->UseNonSecureProxiesForHttp(); |
| |
| for (const auto& test : test_cases) { |
| base::HistogramTester histogram_tester; |
| int64_t baseline_received_bytes = total_received_bytes(); |
| int64_t baseline_original_received_bytes = total_original_received_bytes(); |
| |
| std::unique_ptr<net::URLRequest> request = |
| FetchURLRequest(GURL("http://example.com/path/"), nullptr, |
| test.DrpResponseHeaders.c_str(), 1000); |
| |
| EXPECT_EQ(request->GetTotalReceivedBytes(), |
| total_received_bytes() - baseline_received_bytes); |
| |
| const std::string raw_headers = net::HttpUtil::AssembleRawHeaders( |
| test.DrpResponseHeaders.c_str(), test.DrpResponseHeaders.size()); |
| EXPECT_EQ( |
| static_cast<int64_t>(raw_headers.size() + |
| 10000 /* original_response_body */), |
| total_original_received_bytes() - baseline_original_received_bytes); |
| |
| histogram_tester.ExpectUniqueSample( |
| "DataReductionProxy.ConfigService.HTTPRequests", 1, 1); |
| } |
| } |
| |
| TEST_F(DataReductionProxyDelegateTest, Holdback) { |
| const char kResponseHeaders[] = |
| "HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy-Suffix\r\n" |
| "Content-Length: 10\r\n\r\n"; |
| |
| const struct { |
| bool holdback; |
| } tests[] = { |
| { |
| true, |
| }, |
| { |
| false, |
| }, |
| }; |
| for (const auto& test : tests) { |
| if (!test.holdback) |
| params()->UseNonSecureProxiesForHttp(); |
| |
| base::FieldTrialList field_trial_list(nullptr); |
| ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial( |
| "DataCompressionProxyHoldback", test.holdback ? "Enabled" : "Control")); |
| |
| base::HistogramTester histogram_tester; |
| FetchURLRequest(GURL("http://example.com/path/"), nullptr, kResponseHeaders, |
| 10); |
| histogram_tester.ExpectTotalCount( |
| "DataReductionProxy.SuccessfulRequestCompletionCounts", |
| test.holdback ? 0 : 1); |
| } |
| } |
| |
| TEST_F(DataReductionProxyDelegateTest, OnCompletedSizeFor304) { |
| const struct { |
| const std::string DrpResponseHeaders; |
| } test_cases[] = {{ |
| "HTTP/1.1 304 Not Modified\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Chrome-Proxy: ofcl=10000\r\n\r\n", |
| }, |
| { |
| "HTTP/1.1 304 Not Modified\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Chrome-Proxy: ofcl=10000\r\n\r\n", |
| }}; |
| |
| params()->UseNonSecureProxiesForHttp(); |
| |
| for (const auto& test : test_cases) { |
| int64_t baseline_received_bytes = total_received_bytes(); |
| int64_t baseline_original_received_bytes = total_original_received_bytes(); |
| |
| std::unique_ptr<net::URLRequest> request = |
| FetchURLRequest(GURL("http://example.com/path/"), nullptr, |
| test.DrpResponseHeaders.c_str(), 0); |
| |
| EXPECT_EQ(request->GetTotalReceivedBytes(), |
| total_received_bytes() - baseline_received_bytes); |
| |
| const std::string raw_headers = net::HttpUtil::AssembleRawHeaders( |
| test.DrpResponseHeaders.c_str(), test.DrpResponseHeaders.size()); |
| EXPECT_EQ( |
| static_cast<int64_t>(raw_headers.size() + |
| 10000 /* original_response_body */), |
| total_original_received_bytes() - baseline_original_received_bytes); |
| } |
| } |
| |
| TEST_F(DataReductionProxyDelegateTest, OnCompletedSizeForWriteError) { |
| int64_t baseline_received_bytes = total_received_bytes(); |
| int64_t baseline_original_received_bytes = total_original_received_bytes(); |
| |
| params()->UseNonSecureProxiesForHttp(); |
| net::MockWrite writes[] = { |
| net::MockWrite("GET http://example.com/path/ HTTP/1.1\r\n" |
| "Host: example.com\r\n"), |
| net::MockWrite(net::ASYNC, net::ERR_ABORTED)}; |
| net::StaticSocketDataProvider socket(base::span<net::MockRead>(), writes); |
| mock_socket_factory()->AddSocketDataProvider(&socket); |
| |
| net::TestDelegate delegate; |
| std::unique_ptr<net::URLRequest> request = |
| context()->CreateRequest(GURL("http://example.com/path/"), net::IDLE, |
| &delegate, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request->Start(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(request->GetTotalReceivedBytes(), |
| total_received_bytes() - baseline_received_bytes); |
| EXPECT_EQ(request->GetTotalReceivedBytes(), |
| total_original_received_bytes() - baseline_original_received_bytes); |
| } |
| |
| TEST_F(DataReductionProxyDelegateTest, OnCompletedSizeForReadError) { |
| int64_t baseline_received_bytes = total_received_bytes(); |
| int64_t baseline_original_received_bytes = total_original_received_bytes(); |
| |
| params()->UseNonSecureProxiesForHttp(); |
| net::MockRead reads[] = {net::MockRead("HTTP/1.1 "), |
| net::MockRead(net::ASYNC, net::ERR_ABORTED)}; |
| net::StaticSocketDataProvider socket(reads, base::span<net::MockWrite>()); |
| mock_socket_factory()->AddSocketDataProvider(&socket); |
| |
| net::TestDelegate delegate; |
| std::unique_ptr<net::URLRequest> request = |
| context()->CreateRequest(GURL("http://example.com/path/"), net::IDLE, |
| &delegate, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request->Start(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(request->GetTotalReceivedBytes(), |
| total_received_bytes() - baseline_received_bytes); |
| EXPECT_EQ(request->GetTotalReceivedBytes(), |
| total_original_received_bytes() - baseline_original_received_bytes); |
| } |
| |
| TEST_F(DataReductionProxyDelegateTest, PartialRangeSavings) { |
| const struct { |
| std::string response_headers; |
| size_t received_content_length; |
| int64_t expected_original_content_length; |
| } test_cases[] = { |
| {"HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Content-Length: 1000\r\n" |
| "Chrome-Proxy: ofcl=3000\r\n\r\n", |
| 100, 300}, |
| {"HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Content-Length: 1000\r\n" |
| "Chrome-Proxy: ofcl=1000\r\n\r\n", |
| 100, 100}, |
| {"HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Content-Length: 3000\r\n" |
| "Chrome-Proxy: ofcl=1000\r\n\r\n", |
| 300, 100}, |
| {"HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Content-Length: 1000\r\n\r\n", |
| 100, 100}, |
| {"HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Content-Length: 1000\r\n" |
| "Chrome-Proxy: ofcl=nonsense\r\n\r\n", |
| 100, 100}, |
| {"HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Content-Length: 0\r\n" |
| "Chrome-Proxy: ofcl=1000\r\n\r\n", |
| 0, 1000}, |
| {"HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Chrome-Proxy: ofcl=1000\r\n\r\n", |
| 100, 100}, |
| {"HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Content-Length: nonsense\r\n" |
| "Chrome-Proxy: ofcl=3000\r\n\r\n", |
| 100, 100}, |
| {"HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Content-Length: 1000\r\n" |
| "Chrome-Proxy: ofcl=0\r\n\r\n", |
| 100, 0}, |
| {"HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Content-Length: 1000\r\n" |
| "Chrome-Proxy: ofcl=0\r\n\r\n", |
| 0, 0}, |
| {"HTTP/1.1 200 OK\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Content-Length: " + |
| base::Int64ToString(static_cast<int64_t>(1) << 60) + |
| "\r\n" |
| "Chrome-Proxy: ofcl=" + |
| base::Int64ToString((static_cast<int64_t>(1) << 60) * 3) + |
| "\r\n\r\n", |
| 100, 300}, |
| {"HTTP/1.1 206 Partial Content\r\n" |
| "Content-Range: bytes 0-19/40\r\n" |
| "Content-Length: 20\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Chrome-Proxy: ofcl=160\r\n\r\n", |
| 20, 80}, |
| {"HTTP/1.1 206 Partial Content\r\n" |
| "Content-Range: bytes 0-9/40\r\n" |
| "Content-Length: 10\r\n" |
| "Via: 1.1 Chrome-Compression-Proxy\r\n" |
| "Chrome-Proxy: ofcl=160\r\n\r\n", |
| 10, 40}, |
| }; |
| |
| params()->UseNonSecureProxiesForHttp(); |
| for (const auto& test : test_cases) { |
| base::HistogramTester histogram_tester; |
| int64_t baseline_received_bytes = total_received_bytes(); |
| int64_t baseline_original_received_bytes = total_original_received_bytes(); |
| |
| std::string response_body(test.received_content_length, 'a'); |
| |
| net::MockRead reads[] = { |
| net::MockRead(net::ASYNC, test.response_headers.data(), |
| test.response_headers.size()), |
| net::MockRead(net::ASYNC, response_body.data(), response_body.size()), |
| net::MockRead(net::SYNCHRONOUS, net::ERR_ABORTED)}; |
| net::StaticSocketDataProvider socket(reads, base::span<net::MockWrite>()); |
| mock_socket_factory()->AddSocketDataProvider(&socket); |
| |
| net::TestDelegate test_delegate; |
| std::unique_ptr<net::URLRequest> request = |
| context()->CreateRequest(GURL("http://example.com"), net::IDLE, |
| &test_delegate, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request->Start(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| int64_t expected_original_size = |
| net::HttpUtil::AssembleRawHeaders(test.response_headers.data(), |
| test.response_headers.size()) |
| .size() + |
| test.expected_original_content_length; |
| |
| EXPECT_EQ(request->GetTotalReceivedBytes(), |
| total_received_bytes() - baseline_received_bytes) |
| << (&test - test_cases); |
| EXPECT_EQ(expected_original_size, total_original_received_bytes() - |
| baseline_original_received_bytes) |
| << (&test - test_cases); |
| histogram_tester.ExpectUniqueSample( |
| "DataReductionProxy.ConfigService.HTTPRequests", 1, 1); |
| } |
| } |
| |
| } // namespace |
| |
| } // namespace data_reduction_proxy |