| // Copyright 2018 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 <tuple> |
| |
| #include "base/bind.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/metrics/subprocess_metrics_provider.h" |
| #include "chrome/browser/page_load_metrics/page_load_metrics_test_waiter.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client_test_utils.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_util.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_features.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_pref_names.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h" |
| #include "components/data_reduction_proxy/core/common/uma_util.h" |
| #include "components/data_reduction_proxy/proto/client_config.pb.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/proxy_config/proxy_config_dictionary.h" |
| #include "components/proxy_config/proxy_config_pref_names.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/common/network_service_util.h" |
| #include "content/public/common/service_manager_connection.h" |
| #include "content/public/common/service_names.mojom.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/controllable_http_response.h" |
| #include "net/test/embedded_test_server/default_handlers.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/network_switches.h" |
| #include "services/network/public/mojom/network_service_test.mojom.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| namespace data_reduction_proxy { |
| namespace { |
| |
| using testing::HasSubstr; |
| using testing::Not; |
| |
| constexpr char kSessionKey[] = "TheSessionKeyYay!"; |
| constexpr char kMockHost[] = "mock.host"; |
| constexpr char kDummyBody[] = "dummy"; |
| constexpr char kPrimaryResponse[] = "primary"; |
| constexpr char kSecondaryResponse[] = "secondary"; |
| |
| std::unique_ptr<net::test_server::HttpResponse> BasicResponse( |
| const std::string& content, |
| const net::test_server::HttpRequest& request) { |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_content(content); |
| response->set_content_type("text/plain"); |
| return response; |
| } |
| |
| std::unique_ptr<net::test_server::HttpResponse> IncrementRequestCount( |
| const std::string& relative_url, |
| int* request_count, |
| const net::test_server::HttpRequest& request) { |
| if (request.relative_url == relative_url) |
| (*request_count)++; |
| return std::make_unique<net::test_server::BasicHttpResponse>(); |
| } |
| |
| void SimulateNetworkChange(network::mojom::ConnectionType type) { |
| if (base::FeatureList::IsEnabled(network::features::kNetworkService) && |
| !content::IsInProcessNetworkService()) { |
| network::mojom::NetworkServiceTestPtr network_service_test; |
| content::ServiceManagerConnection::GetForProcess() |
| ->GetConnector() |
| ->BindInterface(content::mojom::kNetworkServiceName, |
| &network_service_test); |
| base::RunLoop run_loop; |
| network_service_test->SimulateNetworkChange(type, run_loop.QuitClosure()); |
| run_loop.Run(); |
| return; |
| } |
| net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests( |
| net::NetworkChangeNotifier::ConnectionType(type)); |
| } |
| |
| ClientConfig CreateConfigForServer(const net::EmbeddedTestServer& server) { |
| net::HostPortPair host_port_pair = server.host_port_pair(); |
| return CreateConfig( |
| kSessionKey, 1000, 0, ProxyServer_ProxyScheme_HTTP, host_port_pair.host(), |
| host_port_pair.port(), ProxyServer::CORE, ProxyServer_ProxyScheme_HTTP, |
| "fallback.net", 80, ProxyServer::UNSPECIFIED_TYPE, 0.5f, false); |
| } |
| |
| } // namespace |
| |
| class DataReductionProxyBrowsertestBase : public InProcessBrowserTest { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitchASCII( |
| network::switches::kForceEffectiveConnectionType, "4G"); |
| |
| secure_proxy_check_server_.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, "OK")); |
| ASSERT_TRUE(secure_proxy_check_server_.Start()); |
| command_line->AppendSwitchASCII( |
| switches::kDataReductionProxySecureProxyCheckURL, |
| secure_proxy_check_server_.base_url().spec()); |
| |
| config_server_.RegisterRequestHandler(base::BindRepeating( |
| &DataReductionProxyBrowsertestBase::GetConfigResponse, |
| base::Unretained(this))); |
| ASSERT_TRUE(config_server_.Start()); |
| command_line->AppendSwitchASCII(switches::kDataReductionProxyConfigURL, |
| config_server_.base_url().spec()); |
| } |
| |
| void SetUp() override { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kDataReductionProxyEnabledWithNetworkService); |
| param_feature_list_.InitAndEnableFeatureWithParameters( |
| features::kDataReductionProxyRobustConnection, |
| {{params::GetMissingViaBypassParamName(), "true"}, |
| {"warmup_fetch_callback_enabled", "true"}}); |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| // Make sure the favicon doesn't mess with the tests. |
| favicon_catcher_ = |
| std::make_unique<net::test_server::ControllableHttpResponse>( |
| embedded_test_server(), "/favicon.ico"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| // Set a default proxy config if one isn't set yet. |
| if (!config_.has_proxy_config()) |
| SetConfig(CreateConfigForServer(*embedded_test_server())); |
| |
| host_resolver()->AddRule(kMockHost, "127.0.0.1"); |
| |
| EnableDataSaver(true); |
| // Make sure initial config has been loaded. |
| WaitForConfig(); |
| } |
| |
| protected: |
| void EnableDataSaver(bool enabled) { |
| PrefService* prefs = browser()->profile()->GetPrefs(); |
| prefs->SetBoolean(::prefs::kDataSaverEnabled, enabled); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| std::string GetBody() { return GetBody(browser()); } |
| |
| std::string GetBody(Browser* browser) { |
| std::string body; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString( |
| browser->tab_strip_model()->GetActiveWebContents(), |
| "window.domAutomationController.send(document.body.textContent);", |
| &body)); |
| return body; |
| } |
| |
| GURL GetURLWithMockHost(const net::EmbeddedTestServer& server, |
| const std::string& relative_url) { |
| GURL server_base_url = server.base_url(); |
| GURL base_url = |
| GURL(base::StrCat({server_base_url.scheme(), "://", kMockHost, ":", |
| server_base_url.port()})); |
| EXPECT_TRUE(base_url.is_valid()) << base_url.possibly_invalid_spec(); |
| return base_url.Resolve(relative_url); |
| } |
| |
| void SetConfig(const ClientConfig& config) { |
| config_run_loop_ = std::make_unique<base::RunLoop>(); |
| config_ = config; |
| } |
| |
| void WaitForConfig() { config_run_loop_->Run(); } |
| |
| private: |
| std::unique_ptr<net::test_server::HttpResponse> GetConfigResponse( |
| const net::test_server::HttpRequest& request) { |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_content(config_.SerializeAsString()); |
| response->set_content_type("text/plain"); |
| if (config_run_loop_) |
| config_run_loop_->Quit(); |
| return response; |
| } |
| |
| ClientConfig config_; |
| std::unique_ptr<base::RunLoop> config_run_loop_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| base::test::ScopedFeatureList param_feature_list_; |
| net::EmbeddedTestServer secure_proxy_check_server_; |
| net::EmbeddedTestServer config_server_; |
| std::unique_ptr<net::test_server::ControllableHttpResponse> favicon_catcher_; |
| }; |
| |
| class DataReductionProxyBrowsertest : public DataReductionProxyBrowsertestBase { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| DataReductionProxyBrowsertestBase::SetUpCommandLine(command_line); |
| command_line->AppendSwitch( |
| switches::kDisableDataReductionProxyWarmupURLFetch); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertest, UpdateConfig) { |
| net::EmbeddedTestServer original_server; |
| original_server.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, kPrimaryResponse)); |
| ASSERT_TRUE(original_server.Start()); |
| |
| SetConfig(CreateConfigForServer(original_server)); |
| // A network change forces the config to be fetched. |
| SimulateNetworkChange(network::mojom::ConnectionType::CONNECTION_3G); |
| WaitForConfig(); |
| |
| ui_test_utils::NavigateToURL(browser(), GURL("http://does.not.resolve/foo")); |
| |
| EXPECT_EQ(GetBody(), kPrimaryResponse); |
| |
| net::EmbeddedTestServer new_server; |
| new_server.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, kSecondaryResponse)); |
| ASSERT_TRUE(new_server.Start()); |
| |
| SetConfig(CreateConfigForServer(new_server)); |
| // A network change forces the config to be fetched. |
| SimulateNetworkChange(network::mojom::ConnectionType::CONNECTION_2G); |
| WaitForConfig(); |
| |
| ui_test_utils::NavigateToURL(browser(), GURL("http://does.not.resolve/foo")); |
| |
| EXPECT_EQ(GetBody(), kSecondaryResponse); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertest, ChromeProxyHeaderSet) { |
| // Proxy will be used, so it shouldn't matter if the host cannot be resolved. |
| ui_test_utils::NavigateToURL( |
| browser(), GURL("http://does.not.resolve/echoheader?Chrome-Proxy")); |
| |
| std::string body = GetBody(); |
| EXPECT_THAT(body, HasSubstr(kSessionKey)); |
| EXPECT_THAT(body, HasSubstr("pid=")); |
| } |
| |
| // Gets the response body for an XHR to |url| (as seen by the renderer). |
| std::string ReadSubresourceFromRenderer(Browser* browser, const GURL& url) { |
| std::string script = R"((url => { |
| var xhr = new XMLHttpRequest(); |
| xhr.open('GET', url, true); |
| xhr.onload = () => domAutomationController.send(xhr.responseText); |
| xhr.send(); |
| }))"; |
| std::string result; |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| browser->tab_strip_model()->GetActiveWebContents(), |
| script + "('" + url.spec() + "')", &result)); |
| return result; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertest, DisabledOnIncognito) { |
| net::EmbeddedTestServer test_server; |
| test_server.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, kDummyBody)); |
| ASSERT_TRUE(test_server.Start()); |
| |
| Browser* incognito = CreateIncognitoBrowser(); |
| ui_test_utils::NavigateToURL( |
| incognito, GetURLWithMockHost(test_server, "/echoheader?Chrome-Proxy")); |
| EXPECT_EQ(GetBody(incognito), kDummyBody); |
| |
| // Make sure subresource doesn't use DRP either. |
| std::string result = ReadSubresourceFromRenderer( |
| incognito, GetURLWithMockHost(test_server, "/echoheader?Chrome-Proxy")); |
| |
| EXPECT_EQ(result, kDummyBody); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertest, |
| ChromeProxyHeaderSetForSubresource) { |
| net::EmbeddedTestServer test_server; |
| test_server.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, kDummyBody)); |
| ASSERT_TRUE(test_server.Start()); |
| |
| ui_test_utils::NavigateToURL(browser(), |
| GetURLWithMockHost(test_server, "/echo")); |
| |
| std::string result = ReadSubresourceFromRenderer( |
| browser(), GetURLWithMockHost(test_server, "/echoheader?Chrome-Proxy")); |
| |
| EXPECT_THAT(result, HasSubstr(kSessionKey)); |
| EXPECT_THAT(result, Not(HasSubstr("pid="))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertest, ChromeProxyEctHeaderSet) { |
| // Proxy will be used, so it shouldn't matter if the host cannot be resolved. |
| ui_test_utils::NavigateToURL( |
| browser(), GURL("http://does.not.resolve/echoheader?Chrome-Proxy-Ect")); |
| |
| EXPECT_EQ(GetBody(), "4G"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertest, |
| ProxyNotUsedWhenDisabled) { |
| net::EmbeddedTestServer test_server; |
| test_server.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, kDummyBody)); |
| ASSERT_TRUE(test_server.Start()); |
| |
| ui_test_utils::NavigateToURL( |
| browser(), GetURLWithMockHost(test_server, "/echoheader?Chrome-Proxy")); |
| EXPECT_THAT(GetBody(), testing::HasSubstr(kSessionKey)); |
| |
| EnableDataSaver(false); |
| |
| // |test_server| only has the BasicResponse handler, so should return the |
| // dummy response no matter what the URL if it is not being proxied. |
| ui_test_utils::NavigateToURL( |
| browser(), GetURLWithMockHost(test_server, "/echoheader?Chrome-Proxy")); |
| EXPECT_EQ(GetBody(), kDummyBody); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertest, |
| ProxyNotUsedForWebSocket) { |
| // Expect the WebSocket handshake to be attempted with |test_server| |
| // directly. |
| base::RunLoop web_socket_handshake_loop; |
| net::EmbeddedTestServer test_server; |
| test_server.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, kDummyBody)); |
| test_server.RegisterRequestMonitor(base::BindLambdaForTesting( |
| [&web_socket_handshake_loop]( |
| const net::test_server::HttpRequest& request) { |
| if (request.headers.count("upgrade") > 0u) |
| web_socket_handshake_loop.Quit(); |
| })); |
| ASSERT_TRUE(test_server.Start()); |
| |
| // If the DRP client (erroneously) decides to proxy the WebSocket handshake, |
| // it will attempt to establish a tunnel through |drp_server|. |
| net::EmbeddedTestServer drp_server; |
| drp_server.AddDefaultHandlers( |
| base::FilePath(FILE_PATH_LITERAL("chrome/test/data"))); |
| bool tunnel_attempted = false; |
| drp_server.RegisterRequestMonitor(base::BindLambdaForTesting( |
| [&tunnel_attempted, &web_socket_handshake_loop]( |
| const net::test_server::HttpRequest& request) { |
| if (request.method == net::test_server::METHOD_CONNECT) { |
| tunnel_attempted = true; |
| web_socket_handshake_loop.Quit(); |
| } |
| })); |
| ASSERT_TRUE(drp_server.Start()); |
| SetConfig(CreateConfigForServer(drp_server)); |
| // A network change forces the config to be fetched. |
| SimulateNetworkChange(network::mojom::ConnectionType::CONNECTION_3G); |
| WaitForConfig(); |
| |
| ui_test_utils::NavigateToURL(browser(), |
| GetURLWithMockHost(test_server, "/echo")); |
| |
| const std::string url = |
| base::StrCat({"ws://", kMockHost, ":", test_server.base_url().port()}); |
| const std::string script = R"((url => { |
| var ws = new WebSocket(url); |
| }))"; |
| EXPECT_TRUE( |
| ExecuteScript(browser()->tab_strip_model()->GetActiveWebContents(), |
| script + "('" + url + "')")); |
| web_socket_handshake_loop.Run(); |
| EXPECT_FALSE(tunnel_attempted); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertest, |
| DoesNotOverrideExistingProxyConfig) { |
| // When there's a proxy configuration provided to the browser already (system |
| // proxy, command line, etc.), the DRP proxy must not override it. |
| net::EmbeddedTestServer existing_proxy_server; |
| existing_proxy_server.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, kDummyBody)); |
| ASSERT_TRUE(existing_proxy_server.Start()); |
| |
| browser()->profile()->GetPrefs()->Set( |
| proxy_config::prefs::kProxy, |
| ProxyConfigDictionary::CreateFixedServers( |
| existing_proxy_server.host_port_pair().ToString(), "")); |
| |
| EnableDataSaver(true); |
| |
| // Proxy will be used, so it shouldn't matter if the host cannot be resolved. |
| ui_test_utils::NavigateToURL(browser(), |
| GURL("http://does.not.resolve.com/echo")); |
| |
| EXPECT_EQ(GetBody(), kDummyBody); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertest, UMAMetricsRecorded) { |
| base::HistogramTester histogram_tester; |
| |
| // Make sure we wait for timing information. |
| page_load_metrics::PageLoadMetricsTestWaiter waiter( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| waiter.AddPageExpectation( |
| page_load_metrics::PageLoadMetricsTestWaiter::TimingField::kFirstPaint); |
| |
| // Proxy will be used, so it shouldn't matter if the host cannot be resolved. |
| ui_test_utils::NavigateToURL(browser(), GURL("http://does.not.resolve/echo")); |
| waiter.Wait(); |
| |
| SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| histogram_tester.ExpectUniqueSample("DataReductionProxy.ProxySchemeUsed", |
| ProxyScheme::PROXY_SCHEME_HTTP, 1); |
| histogram_tester.ExpectTotalCount( |
| "PageLoad.Clients.DataReductionProxy.PaintTiming." |
| "NavigationToFirstContentfulPaint", |
| 1); |
| } |
| |
| class DataReductionProxyBrowsertestWithNetworkService |
| : public DataReductionProxyBrowsertest { |
| public: |
| void SetUp() override { |
| scoped_feature_list_.InitAndEnableFeature( |
| network::features::kNetworkService); |
| DataReductionProxyBrowsertest::SetUp(); |
| } |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertestWithNetworkService, |
| DataUsePrefsRecorded) { |
| PrefService* prefs = browser()->profile()->GetPrefs(); |
| |
| // Make sure we wait for timing information. |
| page_load_metrics::PageLoadMetricsTestWaiter waiter( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| waiter.AddPageExpectation( |
| page_load_metrics::PageLoadMetricsTestWaiter::TimingField::kFirstPaint); |
| |
| // Proxy will be used, so it shouldn't matter if the host cannot be resolved. |
| ui_test_utils::NavigateToURL(browser(), GURL("http://does.not.resolve/echo")); |
| waiter.Wait(); |
| |
| ASSERT_GE(0, prefs->GetInt64( |
| data_reduction_proxy::prefs::kHttpReceivedContentLength)); |
| ASSERT_GE(0, prefs->GetInt64( |
| data_reduction_proxy::prefs::kHttpOriginalContentLength)); |
| } |
| |
| class DataReductionProxyFallbackBrowsertest |
| : public DataReductionProxyBrowsertest { |
| public: |
| using ResponseHook = |
| base::RepeatingCallback<void(net::test_server::BasicHttpResponse*)>; |
| |
| void SetUpOnMainThread() override { |
| // Set up a primary server which will return the Chrome-Proxy header set by |
| // SetHeader() and status set by SetStatusCode(). Secondary server will just |
| // return the secondary response. |
| primary_server_.RegisterRequestHandler(base::BindRepeating( |
| &DataReductionProxyFallbackBrowsertest::AddChromeProxyHeader, |
| base::Unretained(this))); |
| ASSERT_TRUE(primary_server_.Start()); |
| |
| secondary_server_.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, kSecondaryResponse)); |
| ASSERT_TRUE(secondary_server_.Start()); |
| |
| net::HostPortPair primary_host_port_pair = primary_server_.host_port_pair(); |
| net::HostPortPair secondary_host_port_pair = |
| secondary_server_.host_port_pair(); |
| SetConfig(CreateConfig( |
| kSessionKey, 1000, 0, ProxyServer_ProxyScheme_HTTP, |
| primary_host_port_pair.host(), primary_host_port_pair.port(), |
| ProxyServer::CORE, ProxyServer_ProxyScheme_HTTP, |
| secondary_host_port_pair.host(), secondary_host_port_pair.port(), |
| ProxyServer::CORE, 0.5f, false)); |
| |
| DataReductionProxyBrowsertest::SetUpOnMainThread(); |
| } |
| |
| void SetResponseHook(ResponseHook response_hook) { |
| response_hook_ = response_hook; |
| } |
| |
| void SetHeader(const std::string& header) { header_ = header; } |
| |
| void SetStatusCode(net::HttpStatusCode status_code) { |
| status_code_ = status_code; |
| } |
| |
| private: |
| std::unique_ptr<net::test_server::HttpResponse> AddChromeProxyHeader( |
| const net::test_server::HttpRequest& request) { |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| if (!header_.empty()) |
| response->AddCustomHeader(chrome_proxy_header(), header_); |
| if (response_hook_) |
| response_hook_.Run(response.get()); |
| response->set_code(status_code_); |
| response->set_content(kPrimaryResponse); |
| response->set_content_type("text/plain"); |
| return response; |
| } |
| |
| net::HttpStatusCode status_code_ = net::HTTP_OK; |
| std::string header_; |
| ResponseHook response_hook_; |
| net::EmbeddedTestServer primary_server_; |
| net::EmbeddedTestServer secondary_server_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest, |
| FallbackProxyUsedOnNetError) { |
| SetResponseHook( |
| base::BindRepeating([](net::test_server::BasicHttpResponse* response) { |
| response->AddCustomHeader("Content-Disposition", "inline"); |
| response->AddCustomHeader("Content-Disposition", "form-data"); |
| })); |
| base::HistogramTester histogram_tester; |
| ui_test_utils::NavigateToURL( |
| browser(), GURL("http://does.not.resolve/echoheader?Chrome-Proxy")); |
| EXPECT_THAT(GetBody(), kSecondaryResponse); |
| histogram_tester.ExpectUniqueSample( |
| "DataReductionProxy.InvalidResponseHeadersReceived.NetError", |
| -net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION, 1); |
| |
| // Bad proxy should still be bypassed. |
| SetResponseHook(ResponseHook()); |
| ui_test_utils::NavigateToURL( |
| browser(), GURL("http://does.not.resolve/echoheader?Chrome-Proxy")); |
| EXPECT_THAT(GetBody(), kSecondaryResponse); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest, |
| FallbackProxyUsedOn500Status) { |
| base::HistogramTester histogram_tester; |
| // Should fall back to the secondary proxy if a 500 error occurs. |
| SetStatusCode(net::HTTP_INTERNAL_SERVER_ERROR); |
| ui_test_utils::NavigateToURL( |
| browser(), GURL("http://does.not.resolve/echoheader?Chrome-Proxy")); |
| EXPECT_THAT(GetBody(), kSecondaryResponse); |
| histogram_tester.ExpectUniqueSample( |
| "DataReductionProxy.BypassTypePrimary", |
| BYPASS_EVENT_TYPE_STATUS_500_HTTP_INTERNAL_SERVER_ERROR, 1); |
| |
| // Bad proxy should still be bypassed. |
| SetStatusCode(net::HTTP_OK); |
| ui_test_utils::NavigateToURL( |
| browser(), GURL("http://does.not.resolve/echoheader?Chrome-Proxy")); |
| EXPECT_THAT(GetBody(), kSecondaryResponse); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest, |
| FallbackProxyUsedWhenBypassHeaderSent) { |
| base::HistogramTester histogram_tester; |
| // Should fall back to the secondary proxy if the bypass header is set. |
| SetHeader("bypass=100"); |
| ui_test_utils::NavigateToURL( |
| browser(), GURL("http://does.not.resolve/echoheader?Chrome-Proxy")); |
| EXPECT_THAT(GetBody(), kSecondaryResponse); |
| histogram_tester.ExpectUniqueSample("DataReductionProxy.BypassTypePrimary", |
| BYPASS_EVENT_TYPE_MEDIUM, 1); |
| |
| // Bad proxy should still be bypassed. |
| SetHeader(""); |
| ui_test_utils::NavigateToURL( |
| browser(), GURL("http://does.not.resolve/echoheader?Chrome-Proxy")); |
| EXPECT_THAT(GetBody(), kSecondaryResponse); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest, |
| BadProxiesResetWhenDisabled) { |
| base::HistogramTester histogram_tester; |
| SetHeader("bypass=100"); |
| ui_test_utils::NavigateToURL( |
| browser(), GURL("http://does.not.resolve/echoheader?Chrome-Proxy")); |
| EXPECT_THAT(GetBody(), kSecondaryResponse); |
| histogram_tester.ExpectUniqueSample("DataReductionProxy.BypassTypePrimary", |
| BYPASS_EVENT_TYPE_MEDIUM, 1); |
| |
| // Disabling and enabling DRP should clear the bypass. |
| EnableDataSaver(false); |
| EnableDataSaver(true); |
| |
| SetHeader(""); |
| ui_test_utils::NavigateToURL( |
| browser(), GURL("http://does.not.resolve/echoheader?Chrome-Proxy")); |
| EXPECT_THAT(GetBody(), kPrimaryResponse); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest, |
| NoProxyUsedWhenBlockOnceHeaderSent) { |
| base::HistogramTester histogram_tester; |
| net::EmbeddedTestServer test_server; |
| test_server.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, kDummyBody)); |
| ASSERT_TRUE(test_server.Start()); |
| |
| // Request should not use a proxy. |
| SetHeader("block-once"); |
| ui_test_utils::NavigateToURL(browser(), |
| GetURLWithMockHost(test_server, "/echo")); |
| EXPECT_THAT(GetBody(), kDummyBody); |
| EXPECT_LE( |
| 1, histogram_tester.GetBucketCount("DataReductionProxy.BlockTypePrimary", |
| BYPASS_EVENT_TYPE_CURRENT)); |
| |
| // Proxy should no longer be blocked, and use first proxy. |
| SetHeader(""); |
| ui_test_utils::NavigateToURL(browser(), |
| GetURLWithMockHost(test_server, "/echo")); |
| EXPECT_EQ(GetBody(), kPrimaryResponse); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest, |
| FallbackProxyUsedWhenBlockHeaderSent) { |
| base::HistogramTester histogram_tester; |
| net::EmbeddedTestServer test_server; |
| test_server.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, kDummyBody)); |
| ASSERT_TRUE(test_server.Start()); |
| |
| // Request should not use a proxy. |
| SetHeader("block=100"); |
| ui_test_utils::NavigateToURL(browser(), |
| GetURLWithMockHost(test_server, "/echo")); |
| EXPECT_THAT(GetBody(), kDummyBody); |
| histogram_tester.ExpectUniqueSample("DataReductionProxy.BlockTypePrimary", |
| BYPASS_EVENT_TYPE_MEDIUM, 1); |
| |
| // Request should still not use proxy. |
| SetHeader(""); |
| ui_test_utils::NavigateToURL(browser(), |
| GetURLWithMockHost(test_server, "/echo")); |
| EXPECT_THAT(GetBody(), kDummyBody); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest, |
| FallbackProxyUsedWhenBlockZeroHeaderSent) { |
| base::HistogramTester histogram_tester; |
| net::EmbeddedTestServer test_server; |
| test_server.RegisterRequestHandler( |
| base::BindRepeating(&BasicResponse, kDummyBody)); |
| ASSERT_TRUE(test_server.Start()); |
| |
| // Request should not use a proxy. Sending 0 for the block param will block |
| // requests for a random duration between 1 and 5 minutes. |
| SetHeader("block=0"); |
| ui_test_utils::NavigateToURL(browser(), |
| GetURLWithMockHost(test_server, "/echo")); |
| EXPECT_THAT(GetBody(), kDummyBody); |
| histogram_tester.ExpectUniqueSample("DataReductionProxy.BlockTypePrimary", |
| BYPASS_EVENT_TYPE_MEDIUM, 1); |
| |
| // Request should still not use proxy. |
| SetHeader(""); |
| ui_test_utils::NavigateToURL(browser(), |
| GetURLWithMockHost(test_server, "/echo")); |
| EXPECT_THAT(GetBody(), kDummyBody); |
| } |
| |
| class DataReductionProxyResourceTypeBrowsertest |
| : public DataReductionProxyBrowsertest { |
| public: |
| void SetUpOnMainThread() override { |
| // Two proxies are set up here, one with type CORE and one UNSPECIFIED_TYPE. |
| // The CORE proxy is the secondary, and should be used for requests from the |
| // <video> tag. |
| unspecified_server_.RegisterRequestHandler(base::BindRepeating( |
| &IncrementRequestCount, "/video", &unspecified_request_count_)); |
| ASSERT_TRUE(unspecified_server_.Start()); |
| |
| core_server_.RegisterRequestHandler(base::BindRepeating( |
| &IncrementRequestCount, "/video", &core_request_count_)); |
| ASSERT_TRUE(core_server_.Start()); |
| |
| net::HostPortPair unspecified_host_port_pair = |
| unspecified_server_.host_port_pair(); |
| net::HostPortPair core_host_port_pair = core_server_.host_port_pair(); |
| SetConfig(CreateConfig( |
| kSessionKey, 1000, 0, ProxyServer_ProxyScheme_HTTP, |
| unspecified_host_port_pair.host(), unspecified_host_port_pair.port(), |
| ProxyServer::UNSPECIFIED_TYPE, ProxyServer_ProxyScheme_HTTP, |
| core_host_port_pair.host(), core_host_port_pair.port(), |
| ProxyServer::CORE, 0.5f, false)); |
| |
| DataReductionProxyBrowsertest::SetUpOnMainThread(); |
| } |
| |
| int unspecified_request_count_ = 0; |
| int core_request_count_ = 0; |
| |
| private: |
| net::EmbeddedTestServer unspecified_server_; |
| net::EmbeddedTestServer core_server_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyResourceTypeBrowsertest, |
| CoreProxyUsedForMedia) { |
| ui_test_utils::NavigateToURL( |
| browser(), GetURLWithMockHost(*embedded_test_server(), "/echo")); |
| |
| std::string script = R"((url => { |
| var video = document.createElement('video'); |
| // Use onerror since the response is not a valid video. |
| video.onerror = () => domAutomationController.send('done'); |
| video.src = url; |
| video.load(); |
| document.body.appendChild(video); |
| }))"; |
| std::string result; |
| ASSERT_TRUE(ExecuteScriptAndExtractString( |
| browser()->tab_strip_model()->GetActiveWebContents(), |
| script + "('" + |
| GetURLWithMockHost(*embedded_test_server(), "/video").spec() + "')", |
| &result)); |
| EXPECT_EQ(result, "done"); |
| |
| EXPECT_EQ(unspecified_request_count_, 0); |
| EXPECT_EQ(core_request_count_, 1); |
| } |
| |
| class DataReductionProxyWarmupURLBrowsertest |
| : public ::testing::WithParamInterface< |
| std::tuple<ProxyServer_ProxyScheme, bool>>, |
| public DataReductionProxyBrowsertestBase { |
| public: |
| DataReductionProxyWarmupURLBrowsertest() |
| : via_header_(std::get<1>(GetParam()) ? "1.1 Chrome-Compression-Proxy" |
| : "bad"), |
| primary_server_(GetTestServerType()), |
| secondary_server_(GetTestServerType()) {} |
| |
| void SetUpOnMainThread() override { |
| primary_server_loop_ = std::make_unique<base::RunLoop>(); |
| primary_server_.RegisterRequestHandler(base::BindRepeating( |
| &DataReductionProxyWarmupURLBrowsertest::WaitForWarmupRequest, |
| base::Unretained(this), primary_server_loop_.get())); |
| ASSERT_TRUE(primary_server_.Start()); |
| |
| secondary_server_loop_ = std::make_unique<base::RunLoop>(); |
| secondary_server_.RegisterRequestHandler(base::BindRepeating( |
| &DataReductionProxyWarmupURLBrowsertest::WaitForWarmupRequest, |
| base::Unretained(this), secondary_server_loop_.get())); |
| ASSERT_TRUE(secondary_server_.Start()); |
| |
| net::HostPortPair primary_host_port_pair = primary_server_.host_port_pair(); |
| net::HostPortPair secondary_host_port_pair = |
| secondary_server_.host_port_pair(); |
| SetConfig(CreateConfig( |
| kSessionKey, 1000, 0, std::get<0>(GetParam()), |
| primary_host_port_pair.host(), primary_host_port_pair.port(), |
| ProxyServer::UNSPECIFIED_TYPE, std::get<0>(GetParam()), |
| secondary_host_port_pair.host(), secondary_host_port_pair.port(), |
| ProxyServer::CORE, 0.5f, false)); |
| |
| DataReductionProxyBrowsertestBase::SetUpOnMainThread(); |
| } |
| |
| // Retries fetching |histogram_name| until it contains at least |count| |
| // samples. |
| void RetryForHistogramUntilCountReached( |
| base::HistogramTester* histogram_tester, |
| const std::string& histogram_name, |
| size_t count) { |
| base::RunLoop().RunUntilIdle(); |
| for (size_t attempt = 0; attempt < 3; ++attempt) { |
| const std::vector<base::Bucket> buckets = |
| histogram_tester->GetAllSamples(histogram_name); |
| size_t total_count = 0; |
| for (const auto& bucket : buckets) |
| total_count += bucket.count; |
| if (total_count >= count) |
| return; |
| content::FetchHistogramsFromChildProcesses(); |
| SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| } |
| |
| std::string GetHistogramName(ProxyServer::ProxyType type) { |
| return base::StrCat( |
| {"DataReductionProxy.WarmupURLFetcherCallback.SuccessfulFetch.", |
| std::get<0>(GetParam()) == ProxyServer_ProxyScheme_HTTP ? "Insecure" |
| : "Secure", |
| "Proxy.", type == ProxyServer::CORE ? "Core" : "NonCore"}); |
| } |
| |
| std::unique_ptr<base::RunLoop> primary_server_loop_; |
| std::unique_ptr<base::RunLoop> secondary_server_loop_; |
| base::HistogramTester histogram_tester_; |
| |
| private: |
| net::EmbeddedTestServer::Type GetTestServerType() { |
| if (std::get<0>(GetParam()) == ProxyServer_ProxyScheme_HTTP) |
| return net::EmbeddedTestServer::TYPE_HTTP; |
| return net::EmbeddedTestServer::TYPE_HTTPS; |
| } |
| |
| std::unique_ptr<net::test_server::HttpResponse> WaitForWarmupRequest( |
| base::RunLoop* run_loop, |
| const net::test_server::HttpRequest& request) { |
| if (base::StartsWith(request.relative_url, "/e2e_probe", |
| base::CompareCase::SENSITIVE)) { |
| run_loop->Quit(); |
| } |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_content("content"); |
| response->AddCustomHeader("via", via_header_); |
| return response; |
| } |
| |
| const std::string via_header_; |
| net::EmbeddedTestServer primary_server_; |
| net::EmbeddedTestServer secondary_server_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(DataReductionProxyWarmupURLBrowsertest, |
| WarmupURLsFetchedForEachProxy) { |
| primary_server_loop_->Run(); |
| secondary_server_loop_->Run(); |
| |
| SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| RetryForHistogramUntilCountReached( |
| &histogram_tester_, GetHistogramName(ProxyServer::UNSPECIFIED_TYPE), 1); |
| RetryForHistogramUntilCountReached(&histogram_tester_, |
| GetHistogramName(ProxyServer::CORE), 1); |
| |
| histogram_tester_.ExpectUniqueSample( |
| GetHistogramName(ProxyServer::UNSPECIFIED_TYPE), std::get<1>(GetParam()), |
| 1); |
| histogram_tester_.ExpectUniqueSample(GetHistogramName(ProxyServer::CORE), |
| std::get<1>(GetParam()), 1); |
| } |
| |
| // First parameter indicate proxy scheme for proxies that are being tested. |
| // Second parameter is true if the test proxy server should set via header |
| // correctly on the response headers. |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| DataReductionProxyWarmupURLBrowsertest, |
| ::testing::Combine(testing::Values(ProxyServer_ProxyScheme_HTTP, |
| ProxyServer_ProxyScheme_HTTPS), |
| ::testing::Bool())); |
| |
| // Threadsafe log for recording a sequence of events as newline separated text. |
| class EventLog { |
| public: |
| void Add(const std::string& event) { |
| base::AutoLock lock(lock_); |
| log_ += event + "\n"; |
| } |
| |
| std::string GetAndReset() { |
| base::AutoLock lock(lock_); |
| return std::move(log_); |
| } |
| |
| private: |
| base::Lock lock_; |
| std::string log_; |
| }; |
| |
| // Responds to requests for |path| with a 502 and "Chrome-Proxy: block-once", |
| // and logs the request into |event_log|. |
| std::unique_ptr<net::test_server::HttpResponse> DrpBlockOnceHandler( |
| const std::string& server_name, |
| EventLog* event_log, |
| const net::test_server::HttpRequest& request) { |
| if (request.relative_url == "/favicon.ico") |
| return nullptr; |
| |
| event_log->Add(server_name + " responded 502 for " + request.relative_url); |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_content_type("text/plain"); |
| response->set_code(net::HTTP_BAD_GATEWAY); |
| response->AddCustomHeader(chrome_proxy_header(), "block-once"); |
| return response; |
| } |
| |
| // Responds to requests with the path as response body, and logs the request |
| // into |event_log|. |
| std::unique_ptr<net::test_server::HttpResponse> RespondWithRequestPathHandler( |
| const std::string& server_name, |
| EventLog* event_log, |
| const net::test_server::HttpRequest& request) { |
| if (request.relative_url == "/favicon.ico") |
| return nullptr; |
| |
| event_log->Add(server_name + " responded 200 for " + request.relative_url); |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_content_type("text/plain"); |
| response->set_code(net::HTTP_OK); |
| response->set_content(request.relative_url); |
| return response; |
| } |
| |
| // Tests that Chrome-Proxy response headers are respected after the |
| // configuration is updated. |
| // |
| // When run under NetworkService, the DataReductionProxyURLLoaderThrottle |
| // decides whether to block-once based on the configured DRP servers. This |
| // config is in turn synchronized through the DataReductionProxyThrottleManager. |
| // |
| // The goal of this test is to ensure that this throttle sees the correct |
| // configuration when processing response headers (the UpdateConfig() test |
| // already checks that the network service sees the updated config). |
| IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertest, |
| BlockOnceWorksAfterUpdateConfig) { |
| EventLog event_log; |
| |
| // Setup a DRP server that will reply with "Chrome-Proxy: block-once". |
| net::EmbeddedTestServer drp_server1; |
| drp_server1.RegisterRequestHandler(base::BindRepeating( |
| &DrpBlockOnceHandler, "drp_server1", base::Unretained(&event_log))); |
| ASSERT_TRUE(drp_server1.Start()); |
| |
| // Setup a DRP server that will reply with "Chrome-Proxy: block-once". |
| net::EmbeddedTestServer drp_server2; |
| drp_server2.RegisterRequestHandler(base::BindRepeating( |
| &DrpBlockOnceHandler, "drp_server2", base::Unretained(&event_log))); |
| ASSERT_TRUE(drp_server2.Start()); |
| |
| // Regular server that will respond with the request path as body. |
| net::EmbeddedTestServer direct_server; |
| direct_server.RegisterRequestHandler(base::BindRepeating( |
| &RespondWithRequestPathHandler, "direct_server", &event_log)); |
| ASSERT_TRUE(direct_server.Start()); |
| |
| // Change the DRP configuration so that |drp_server1| is the current DRP. |
| SetConfig(CreateConfigForServer(drp_server1)); |
| SimulateNetworkChange(network::mojom::ConnectionType::CONNECTION_3G); |
| WaitForConfig(); |
| |
| // When issuing request /x1, it should first go to |drp_server1|, and then get |
| // restarted on |direct_server|. |
| const char kExpectedLog1[] = |
| "drp_server1 responded 502 for /x1\n" |
| "direct_server responded 200 for /x1\n"; |
| |
| GURL x1_url = GetURLWithMockHost(direct_server, "/x1"); |
| |
| // Test a browser-initiated request. |
| ui_test_utils::NavigateToURL(browser(), x1_url); |
| EXPECT_EQ("/x1", GetBody()); |
| EXPECT_EQ(kExpectedLog1, event_log.GetAndReset()); |
| |
| // Test a renderer initiated request. |
| EXPECT_EQ("/x1", ReadSubresourceFromRenderer(browser(), x1_url)); |
| EXPECT_EQ(kExpectedLog1, event_log.GetAndReset()); |
| |
| // Change the DRP configuration so that |drp_server2| is the current DRP. |
| SetConfig(CreateConfigForServer(drp_server2)); |
| SimulateNetworkChange(network::mojom::ConnectionType::CONNECTION_3G); |
| WaitForConfig(); |
| |
| // When issuing request /x2, it should first go to |drp_server2|, and then get |
| // restarted on |direct_server|. |
| const char kExpectedLog2[] = |
| "drp_server2 responded 502 for /x2\n" |
| "direct_server responded 200 for /x2\n"; |
| |
| // Test a browser-initiated request. |
| GURL x2_url = GetURLWithMockHost(direct_server, "/x2"); |
| ui_test_utils::NavigateToURL(browser(), x2_url); |
| EXPECT_EQ("/x2", GetBody()); |
| EXPECT_EQ(kExpectedLog2, event_log.GetAndReset()); |
| |
| // Test a renderer initiated request. |
| EXPECT_EQ("/x2", ReadSubresourceFromRenderer(browser(), x2_url)); |
| EXPECT_EQ(kExpectedLog2, event_log.GetAndReset()); |
| } |
| |
| } // namespace data_reduction_proxy |