diff options
author | bengr@chromium.org <bengr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-13 13:17:15 +0000 |
---|---|---|
committer | bengr@chromium.org <bengr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-13 13:17:15 +0000 |
commit | d8fc4720c06d9e57d5fd98d6d6b723fffddbe159 (patch) | |
tree | 665d9568104f3bfe1330e7d7b567cbe8c5d003d3 /components | |
parent | a2d6ed8e40f2747490558f311c438fa0245d262a (diff) | |
download | chromium_src-d8fc4720c06d9e57d5fd98d6d6b723fffddbe159.zip chromium_src-d8fc4720c06d9e57d5fd98d6d6b723fffddbe159.tar.gz chromium_src-d8fc4720c06d9e57d5fd98d6d6b723fffddbe159.tar.bz2 |
Moved data reduction proxy bypass logic to a NetworkDelegate
BUG=367221
Review URL: https://codereview.chromium.org/286903018
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@277009 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
10 files changed, 1094 insertions, 41 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 8e5cc53..7129e06 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -76,6 +76,7 @@ 'data_reduction_proxy/browser/data_reduction_proxy_config_service_unittest.cc', 'data_reduction_proxy/browser/data_reduction_proxy_metrics_unittest.cc', 'data_reduction_proxy/browser/data_reduction_proxy_params_unittest.cc', + 'data_reduction_proxy/browser/data_reduction_proxy_protocol_unittest.cc', 'data_reduction_proxy/browser/data_reduction_proxy_settings_unittest.cc', 'data_reduction_proxy/browser/http_auth_handler_data_reduction_proxy_unittest.cc', 'dom_distiller/core/article_entry_unittest.cc', diff --git a/components/data_reduction_proxy.gypi b/components/data_reduction_proxy.gypi index bfe50a2..89eced8 100644 --- a/components/data_reduction_proxy.gypi +++ b/components/data_reduction_proxy.gypi @@ -30,6 +30,8 @@ 'data_reduction_proxy/browser/data_reduction_proxy_params.h', 'data_reduction_proxy/browser/data_reduction_proxy_prefs.cc', 'data_reduction_proxy/browser/data_reduction_proxy_prefs.h', + 'data_reduction_proxy/browser/data_reduction_proxy_protocol.cc', + 'data_reduction_proxy/browser/data_reduction_proxy_protocol.h', 'data_reduction_proxy/browser/data_reduction_proxy_settings.cc', 'data_reduction_proxy/browser/data_reduction_proxy_settings.h', 'data_reduction_proxy/browser/http_auth_handler_data_reduction_proxy.cc', diff --git a/components/data_reduction_proxy/browser/data_reduction_proxy_params.cc b/components/data_reduction_proxy/browser/data_reduction_proxy_params.cc index f1c0ab5..16e0d20 100644 --- a/components/data_reduction_proxy/browser/data_reduction_proxy_params.cc +++ b/components/data_reduction_proxy/browser/data_reduction_proxy_params.cc @@ -7,6 +7,7 @@ #include "base/command_line.h" #include "base/metrics/field_trial.h" #include "components/data_reduction_proxy/common/data_reduction_proxy_switches.h" +#include "net/url_request/url_request.h" using base::FieldTrialList; @@ -25,7 +26,7 @@ bool DataReductionProxyParams::IsIncludedInFieldTrial() { // static bool DataReductionProxyParams::IsIncludedInAlternativeFieldTrial() { return base::FieldTrialList::FindFullName( - "DataCompressionProxyAlternativeConfiguration") == kEnabled; + "DataCompressionProxyAlternativeConfiguration") == kEnabled; } // static @@ -225,6 +226,59 @@ void DataReductionProxyParams::InitWithoutChecks() { } +bool DataReductionProxyParams::WasDataReductionProxyUsed( + const net::URLRequest* request, + std::pair<GURL, GURL>* proxy_servers) const { + DCHECK(request); + return IsDataReductionProxy(request->proxy_server(), proxy_servers); +} + +bool DataReductionProxyParams::IsDataReductionProxy( + const net::HostPortPair& host_port_pair, + std::pair<GURL, GURL>* proxy_servers) const { + if (net::HostPortPair::FromURL(origin()).Equals(host_port_pair)) { + if (proxy_servers) { + (*proxy_servers).first = origin(); + if (fallback_allowed()) + (*proxy_servers).second = fallback_origin(); + } + return true; + } + if (fallback_allowed() && + net::HostPortPair::FromURL(fallback_origin()).Equals(host_port_pair)) { + if (proxy_servers) { + (*proxy_servers).first = fallback_origin(); + (*proxy_servers).second = GURL(); + } + return true; + } + if (net::HostPortPair::FromURL(alt_origin()).Equals(host_port_pair)) { + if (proxy_servers) { + (*proxy_servers).first = alt_origin(); + if (fallback_allowed()) + (*proxy_servers).second = alt_fallback_origin(); + } + return true; + } + if (fallback_allowed() && + net::HostPortPair::FromURL(alt_fallback_origin()).Equals( + host_port_pair)) { + if (proxy_servers) { + (*proxy_servers).first = alt_fallback_origin(); + (*proxy_servers).second = GURL(); + } + return true; + } + if (net::HostPortPair::FromURL(ssl_origin()).Equals(host_port_pair)) { + if (proxy_servers) { + (*proxy_servers).first = ssl_origin(); + (*proxy_servers).second = GURL(); + } + return true; + } + return false; +} + std::string DataReductionProxyParams::GetDefaultKey() const { #if defined(SPDY_PROXY_AUTH_VALUE) return SPDY_PROXY_AUTH_VALUE; diff --git a/components/data_reduction_proxy/browser/data_reduction_proxy_params.h b/components/data_reduction_proxy/browser/data_reduction_proxy_params.h index 7eaf4a4..0285001 100644 --- a/components/data_reduction_proxy/browser/data_reduction_proxy_params.h +++ b/components/data_reduction_proxy/browser/data_reduction_proxy_params.h @@ -5,19 +5,27 @@ #ifndef COMPONENTS_DATA_REDUCTION_PROXY_BROWSER_DATA_REDUCTION_PROXY_PARAMS_H_ #define COMPONENTS_DATA_REDUCTION_PROXY_BROWSER_DATA_REDUCTION_PROXY_PARAMS_H_ +#include <string> +#include <utility> #include <vector> #include "base/macros.h" +#include "net/base/host_port_pair.h" #include "url/gurl.h" -namespace data_reduction_proxy { +namespace net { +class URLRequest; +} +namespace data_reduction_proxy { // Provides initialization parameters. Proxy origins, the probe url, and the // authentication key are taken from flags if available and from preprocessor -// constants otherwise. Only the key may be changed after construction. +// constants otherwise. Only the key may be changed after construction. The +// DataReductionProxySettings class and others use this class to determine +// the necessary DNS names and keys to configure use of the data reduction +// proxy. class DataReductionProxyParams { public: - static const unsigned int kAllowed = (1 << 0); static const unsigned int kFallbackAllowed = (1 << 1); static const unsigned int kAlternativeAllowed = (1 << 2); @@ -61,6 +69,25 @@ class DataReductionProxyParams { virtual ~DataReductionProxyParams(); + // Returns true if a data reduction proxy was used for the given |request|. + // If true, |proxy_servers.first| will contain the name of the proxy that was + // used. |proxy_servers.second| will contain the name of the data reduction + // proxy server that would be used if |proxy_server.first| is bypassed, if one + // exists. |proxy_servers| can be NULL if the caller isn't interested in its + // values. + virtual bool WasDataReductionProxyUsed( + const net::URLRequest* request, + std::pair<GURL, GURL>* proxy_servers) const; + + // Returns true if the specified |host_port_pair| matches a data reduction + // proxy. If true, |proxy_servers.first| will contain the name of the proxy + // that matches. |proxy_servers.second| will contain the name of the + // data reduction proxy server that would be used if |proxy_server.first| is + // bypassed, if one exists. |proxy_servers| can be NULL if the caller isn't + // interested in its values. + bool IsDataReductionProxy(const net::HostPortPair& host_port_pair, + std::pair<GURL, GURL>* proxy_servers) const; + // Returns the data reduction proxy primary origin. const GURL& origin() const { return origin_; diff --git a/components/data_reduction_proxy/browser/data_reduction_proxy_params_unittest.cc b/components/data_reduction_proxy/browser/data_reduction_proxy_params_unittest.cc index 37cb3bb..725acf1 100644 --- a/components/data_reduction_proxy/browser/data_reduction_proxy_params_unittest.cc +++ b/components/data_reduction_proxy/browser/data_reduction_proxy_params_unittest.cc @@ -42,6 +42,7 @@ static const unsigned int HAS_EVERYTHING = 0xff; } // namespace namespace data_reduction_proxy { +namespace { class TestDataReductionProxyParams : public DataReductionProxyParams { public: @@ -101,6 +102,7 @@ class TestDataReductionProxyParams : public DataReductionProxyParams { unsigned int has_definitions_; bool init_result_; }; +} // namespace class DataReductionProxyParamsTest : public testing::Test { public: @@ -292,4 +294,85 @@ TEST_F(DataReductionProxyParamsTest, InvalidConfigurations) { } } +TEST_F(DataReductionProxyParamsTest, IsDataReductionProxy) { + const struct { + net::HostPortPair host_port_pair; + bool fallback_allowed; + bool expected_result; + net::HostPortPair expected_first; + net::HostPortPair expected_second; + } tests[] = { + { net::HostPortPair::FromURL(GURL(kDefaultOrigin)), + true, + true, + net::HostPortPair::FromURL(GURL(kDefaultOrigin)), + net::HostPortPair::FromURL(GURL(kDefaultFallbackOrigin)) + }, + { net::HostPortPair::FromURL(GURL(kDefaultOrigin)), + false, + true, + net::HostPortPair::FromURL(GURL(kDefaultOrigin)), + net::HostPortPair::FromURL(GURL()) + }, + { net::HostPortPair::FromURL(GURL(kDefaultFallbackOrigin)), + true, + true, + net::HostPortPair::FromURL(GURL(kDefaultFallbackOrigin)), + net::HostPortPair::FromURL(GURL()) + }, + { net::HostPortPair::FromURL(GURL(kDefaultFallbackOrigin)), + false, + false, + net::HostPortPair::FromURL(GURL()), + net::HostPortPair::FromURL(GURL()) + }, + { net::HostPortPair::FromURL(GURL(kDefaultAltOrigin)), + true, + true, + net::HostPortPair::FromURL(GURL(kDefaultAltOrigin)), + net::HostPortPair::FromURL(GURL(kDefaultAltFallbackOrigin)) + }, + { net::HostPortPair::FromURL(GURL(kDefaultAltOrigin)), + false, + true, + net::HostPortPair::FromURL(GURL(kDefaultAltOrigin)), + net::HostPortPair::FromURL(GURL()) + }, + { net::HostPortPair::FromURL(GURL(kDefaultAltFallbackOrigin)), + true, + true, + net::HostPortPair::FromURL(GURL(kDefaultAltFallbackOrigin)), + net::HostPortPair::FromURL(GURL()) + }, + { net::HostPortPair::FromURL(GURL(kDefaultAltFallbackOrigin)), + false, + false, + net::HostPortPair::FromURL(GURL()), + net::HostPortPair::FromURL(GURL()) + }, + { net::HostPortPair::FromURL(GURL(kDefaultSSLOrigin)), + true, + true, + net::HostPortPair::FromURL(GURL(kDefaultSSLOrigin)), + net::HostPortPair::FromURL(GURL()) + }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + int flags = DataReductionProxyParams::kAllowed | + DataReductionProxyParams::kAlternativeAllowed; + if (tests[i].fallback_allowed) + flags |= DataReductionProxyParams::kFallbackAllowed; + TestDataReductionProxyParams params(flags, + HAS_EVERYTHING & ~HAS_DEV_ORIGIN); + std::pair<GURL, GURL> proxy_servers; + EXPECT_EQ(tests[i].expected_result, + params.IsDataReductionProxy( + tests[i].host_port_pair, &proxy_servers)); + EXPECT_TRUE(tests[i].expected_first.Equals( + net::HostPortPair::FromURL(proxy_servers.first))); + EXPECT_TRUE(tests[i].expected_second.Equals( + net::HostPortPair::FromURL(proxy_servers.second))); + } +} + } // namespace data_reduction_proxy diff --git a/components/data_reduction_proxy/browser/data_reduction_proxy_protocol.cc b/components/data_reduction_proxy/browser/data_reduction_proxy_protocol.cc new file mode 100644 index 0000000..c050e40 --- /dev/null +++ b/components/data_reduction_proxy/browser/data_reduction_proxy_protocol.cc @@ -0,0 +1,155 @@ +// 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/data_reduction_proxy/browser/data_reduction_proxy_protocol.h" + +#include "base/memory/ref_counted.h" +#include "base/time/time.h" +#include "components/data_reduction_proxy/browser/data_reduction_proxy_params.h" +#include "net/base/load_flags.h" +#include "net/http/http_response_headers.h" +#include "net/proxy/proxy_info.h" +#include "net/proxy/proxy_list.h" +#include "net/proxy/proxy_server.h" +#include "net/proxy/proxy_service.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "url/gurl.h" + +namespace { +bool SetProxyServerFromGURL(const GURL& gurl, + net::ProxyServer* proxy_server) { + DCHECK(proxy_server); + if (!gurl.SchemeIsHTTPOrHTTPS()) + return false; + *proxy_server = net::ProxyServer(gurl.SchemeIs("http") ? + net::ProxyServer::SCHEME_HTTP : + net::ProxyServer::SCHEME_HTTPS, + net::HostPortPair::FromURL(gurl)); + return true; +} +} // namespace + +namespace data_reduction_proxy { + +bool MaybeBypassProxyAndPrepareToRetry( + const DataReductionProxyParams* data_reduction_proxy_params, + net::URLRequest* request, + const net::HttpResponseHeaders* original_response_headers, + scoped_refptr<net::HttpResponseHeaders>* override_response_headers) { + if (!data_reduction_proxy_params) + return false; + std::pair<GURL, GURL> data_reduction_proxies; + if (!data_reduction_proxy_params->WasDataReductionProxyUsed( + request, &data_reduction_proxies)) { + return false; + } + + // Empty implies either that the request was served from cache or that + // request was served directly from the origin. + if (request->proxy_server().IsEmpty()) + return false; + + if (data_reduction_proxies.first.is_empty()) + return false; + + net::HttpResponseHeaders::DataReductionProxyInfo data_reduction_proxy_info; + net::ProxyService::DataReductionProxyBypassEventType bypass_type = + original_response_headers->GetDataReductionProxyBypassEventType( + &data_reduction_proxy_info); + + if (bypass_type == net::ProxyService::BYPASS_EVENT_TYPE_MAX) { + return false; + } + + DCHECK(request->context()); + DCHECK(request->context()->proxy_service()); + net::ProxyServer proxy_server; + SetProxyServerFromGURL(data_reduction_proxies.first, &proxy_server); + request->context()->proxy_service()->RecordDataReductionProxyBypassInfo( + !data_reduction_proxies.second.is_empty(), proxy_server, bypass_type); + + MarkProxiesAsBadUntil(request, + data_reduction_proxy_info.bypass_duration, + data_reduction_proxy_info.bypass_all, + data_reduction_proxies); + + // Only retry idempotent methods. + if (!IsRequestIdempotent(request)) + return false; + + OverrideResponseAsRedirect(request, + original_response_headers, + override_response_headers); + return true; +} + + + +bool IsRequestIdempotent(const net::URLRequest* request) { + DCHECK(request); + if (request->method() == "GET" || + request->method() == "OPTIONS" || + request->method() == "HEAD" || + request->method() == "PUT" || + request->method() == "DELETE" || + request->method() == "TRACE") + return true; + return false; +} + +void OverrideResponseAsRedirect( + net::URLRequest* request, + const net::HttpResponseHeaders* original_response_headers, + scoped_refptr<net::HttpResponseHeaders>* override_response_headers) { + DCHECK(request); + DCHECK(original_response_headers); + DCHECK(override_response_headers->get() == NULL); + + request->SetLoadFlags(request->load_flags() | + net::LOAD_DISABLE_CACHE | + net::LOAD_BYPASS_PROXY); + *override_response_headers = new net::HttpResponseHeaders( + original_response_headers->raw_headers()); + (*override_response_headers)->ReplaceStatusLine("HTTP/1.1 302 Found"); + (*override_response_headers)->RemoveHeader("Location"); + (*override_response_headers)->AddHeader("Location: " + + request->url().spec()); + // TODO(bengr): Should we pop_back the request->url_chain? +} + +void MarkProxiesAsBadUntil( + net::URLRequest* request, + base::TimeDelta& bypass_duration, + bool bypass_all, + const std::pair<GURL, GURL>& data_reduction_proxies) { + DCHECK(!data_reduction_proxies.first.is_empty()); + // Synthesize a suitable |ProxyInfo| to add the proxies to the + // |ProxyRetryInfoMap| of the proxy service. + net::ProxyList proxy_list; + net::ProxyServer primary; + SetProxyServerFromGURL(data_reduction_proxies.first, &primary); + if (primary.is_valid()) + proxy_list.AddProxyServer(primary); + net::ProxyServer fallback; + if (bypass_all) { + if (!data_reduction_proxies.second.is_empty()) + SetProxyServerFromGURL(data_reduction_proxies.second, &fallback); + if (fallback.is_valid()) + proxy_list.AddProxyServer(fallback); + proxy_list.AddProxyServer(net::ProxyServer::Direct()); + } + net::ProxyInfo proxy_info; + proxy_info.UseProxyList(proxy_list); + DCHECK(request->context()); + net::ProxyService* proxy_service = request->context()->proxy_service(); + DCHECK(proxy_service); + + proxy_service->MarkProxiesAsBadUntil(proxy_info, + bypass_duration, + fallback, + request->net_log()); +} + +} // namespace data_reduction_proxy diff --git a/components/data_reduction_proxy/browser/data_reduction_proxy_protocol.h b/components/data_reduction_proxy/browser/data_reduction_proxy_protocol.h new file mode 100644 index 0000000..4c28498 --- /dev/null +++ b/components/data_reduction_proxy/browser/data_reduction_proxy_protocol.h @@ -0,0 +1,59 @@ +// 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. + +#ifndef COMPONENTS_DATA_REDUCTION_PROXY_BROWSER_DATA_REDUCTION_PROXY_PROTOCOL_H_ +#define COMPONENTS_DATA_REDUCTION_PROXY_BROWSER_DATA_REDUCTION_PROXY_PROTOCOL_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" + +namespace base { +class TimeDelta; +} + +namespace net { +class HttpResponseHeaders; +class ProxyServer; +class URLRequest; +} + +class GURL; + +namespace data_reduction_proxy { + +class DataReductionProxyParams; + +// Decides whether to mark the data reduction proxy as temporarily bad and +// put it on the proxy retry list. Returns true if the request should be +// retried. Sets |override_response_headers| to redirect if so. +bool MaybeBypassProxyAndPrepareToRetry( + const DataReductionProxyParams* params, + net::URLRequest* request, + const net::HttpResponseHeaders* original_response_headers, + scoped_refptr<net::HttpResponseHeaders>* override_response_headers); + +// Returns true if the request method is idempotent. Only idempotent requests +// are retried on a bypass. Visible as part of the public API for testing. +bool IsRequestIdempotent(const net::URLRequest* request); + +// Sets the override headers to contain a status line that indicates a +// redirect (a 302), a "Location:" header that points to the request url, +// and sets load flags to bypass proxies. Visible as part of the public API for +// testing. +void OverrideResponseAsRedirect( + net::URLRequest* request, + const net::HttpResponseHeaders* original_response_headers, + scoped_refptr<net::HttpResponseHeaders>* override_response_headers); + +// Adds non-empty entries in |data_reduction_proxies| to the retry map +// maintained by the proxy service of the request. Adds +// |data_reduction_proxies.second| to the retry list only if |bypass_all| is +// true. Visible as part of the public API for testing. +void MarkProxiesAsBadUntil(net::URLRequest* request, + base::TimeDelta& bypass_duration, + bool bypass_all, + const std::pair<GURL, GURL>& data_reduction_proxies); + +} // namespace data_reduction_proxy +#endif // COMPONENTS_DATA_REDUCTION_PROXY_BROWSER_DATA_REDUCTION_PROXY_PROTOCOL_H_ diff --git a/components/data_reduction_proxy/browser/data_reduction_proxy_protocol_unittest.cc b/components/data_reduction_proxy/browser/data_reduction_proxy_protocol_unittest.cc new file mode 100644 index 0000000..fb732f8 --- /dev/null +++ b/components/data_reduction_proxy/browser/data_reduction_proxy_protocol_unittest.cc @@ -0,0 +1,670 @@ +// 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/data_reduction_proxy/browser/data_reduction_proxy_protocol.h" + +#include <utility> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "components/data_reduction_proxy/browser/data_reduction_proxy_params.h" +#include "net/base/completion_callback.h" +#include "net/base/host_port_pair.h" +#include "net/base/network_delegate.h" +#include "net/cert/cert_verifier.h" +#include "net/cert/mock_cert_verifier.h" +#include "net/dns/mock_host_resolver.h" +#include "net/http/http_network_layer.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_server_properties_impl.h" +#include "net/http/http_transaction_test_util.h" +#include "net/socket/socket_test_util.h" +#include "net/ssl/ssl_config_service.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +using net::HttpNetworkLayer; +using net::HttpNetworkSession; +using net::HttpResponseHeaders; +using net::HostPortPair; +using net::MockRead; +using net::MockWrite; +using net::ProxyRetryInfoMap; +using net::ProxyService; +using net::StaticSocketDataProvider; +using net::TestDelegate; +using net::TransportSecurityState; +using net::URLRequest; +using net::URLRequestContext; + + +namespace { +// Transform "normal"-looking headers (\n-separated) to the appropriate +// input format for ParseRawHeaders (\0-separated). +void HeadersToRaw(std::string* headers) { + std::replace(headers->begin(), headers->end(), '\n', '\0'); + if (!headers->empty()) + *headers += '\0'; +} + +std::string GetDataReductionProxy() { + return "https://proxy1.com:443/"; +} + +std::string GetDataReductionProxyFallback() { + return "http://proxy2.com:80/"; +} +} // namespace + + +namespace data_reduction_proxy { +namespace { +class TestDataReductionProxyParams : public DataReductionProxyParams { + public: + TestDataReductionProxyParams() : DataReductionProxyParams(0, false) {} + + virtual bool WasDataReductionProxyUsed( + const net::URLRequest* request, + std::pair<GURL, GURL>* proxy_servers) const OVERRIDE; +}; + +bool TestDataReductionProxyParams::WasDataReductionProxyUsed( + const net::URLRequest* request, + std::pair<GURL, GURL>* proxy_servers) const { + if (net::HostPortPair::FromURL(GURL(GetDataReductionProxy())).Equals( + request->proxy_server())) { + proxy_servers->first = GURL(GetDataReductionProxy()); + proxy_servers->second = GURL(GetDataReductionProxyFallback()); + return true; + } + if (net::HostPortPair::FromURL( + GURL(GetDataReductionProxyFallback())).Equals( + request->proxy_server())) { + proxy_servers->first = GURL(GetDataReductionProxyFallback()); + proxy_servers->second = GURL(); + return true; + } + return false; +} +} // namespace + +// A test network delegate that exercises the bypass logic of the data +// reduction proxy. +class TestDataReductionProxyNetworkDelegate : public net::NetworkDelegate { + public: + TestDataReductionProxyNetworkDelegate( + TestDataReductionProxyParams* test_params) + : net::NetworkDelegate(), test_data_reduction_proxy_params_(test_params) { + } + + virtual int OnHeadersReceived( + URLRequest* request, + const net::CompletionCallback& callback, + const HttpResponseHeaders* original_response_headers, + scoped_refptr<HttpResponseHeaders>* override_response_headers, + GURL* allowed_unsafe_redirect_url) OVERRIDE { + data_reduction_proxy::MaybeBypassProxyAndPrepareToRetry( + test_data_reduction_proxy_params_, + request, + original_response_headers, + override_response_headers); + return net::OK; + } + + TestDataReductionProxyParams* test_data_reduction_proxy_params_; +}; + +// Constructs a |URLRequestContext| that uses a |MockSocketFactory| to simulate +// requests and responses. +class DataReductionProxyProtocolTest : public testing::Test { + public: + DataReductionProxyProtocolTest() + : ssl_config_service_(new net::SSLConfigServiceDefaults) {} + + // Sets up the |URLRequestContext| with the provided |ProxyService|. + void ConfigureTestDependencies(ProxyService* proxy_service) { + cert_verifier_.reset(new net::MockCertVerifier); + transport_security_state_.reset(new TransportSecurityState); + proxy_service_.reset(proxy_service); + HttpNetworkSession::Params session_params; + session_params.client_socket_factory = &mock_socket_factory_; + session_params.host_resolver = &host_resolver_; + session_params.cert_verifier = cert_verifier_.get(); + session_params.transport_security_state = transport_security_state_.get(); + session_params.proxy_service = proxy_service_.get(); + session_params.ssl_config_service = ssl_config_service_.get(); + session_params.http_server_properties = + http_server_properties_.GetWeakPtr(); + network_session_ = new HttpNetworkSession(session_params); + factory_.reset(new HttpNetworkLayer(network_session_.get())); + + proxy_params_.reset(new TestDataReductionProxyParams()); + network_delegate_.reset(new TestDataReductionProxyNetworkDelegate( + proxy_params_.get())); + + context_.reset(new URLRequestContext()); + context_->set_host_resolver(&host_resolver_); + context_->set_cert_verifier(cert_verifier_.get()); + context_->set_proxy_service(proxy_service_.get()); + context_->set_ssl_config_service(ssl_config_service_.get()); + context_->set_http_transaction_factory(factory_.get()); + context_->set_http_server_properties(http_server_properties_.GetWeakPtr()); + context_->set_transport_security_state(transport_security_state_.get()); + context_->set_network_delegate(network_delegate_.get()); + } + + // Simulates a request to a data reduction proxy that may result in bypassing + // the proxy and retrying the the request. + // Runs a test with the given request |method| that expects the first response + // from the server to be |first_response|. If |expected_retry|, the test + // will expect a retry of the request. A response body will be expected + // if |expect_response_body|. + void TestProxyFallback(const char* method, + const char* first_response, + bool expected_retry, + bool expect_response_body) { + std::string payload1 = + (expected_retry ? "Bypass message" : "content"); + MockRead data_reads[] = { + MockRead(first_response), + MockRead(payload1.c_str()), + MockRead(net::SYNCHRONOUS, net::OK), + }; + std::string m(method); + std::string trailer = + (m == "HEAD" || m == "PUT" || m == "POST") ? + "Content-Length: 0\r\n" : ""; + + std::string request1 = + base::StringPrintf("%s http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n%s" + "User-Agent:\r\n" + "Accept-Encoding: gzip,deflate\r\n\r\n", + method, trailer.c_str()); + MockWrite data_writes[] = { + MockWrite(request1.c_str()), + }; + StaticSocketDataProvider data1(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + mock_socket_factory_.AddSocketDataProvider(&data1); + + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n" + "Server: not-proxy\r\n\r\n"), + MockRead("content"), + MockRead(net::SYNCHRONOUS, net::OK), + }; + std::string request2 = + base::StringPrintf("%s / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n%s" + "User-Agent:\r\n" + "Accept-Encoding: gzip,deflate\r\n\r\n", + method, trailer.c_str()); + MockWrite data_writes2[] = { + MockWrite(request2.c_str()), + }; + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + if (expected_retry) { + mock_socket_factory_.AddSocketDataProvider(&data2); + } + + // Expect that we get "content" and not "Bypass message", and that there's + // a "not-proxy" "Server:" header in the final response. + ExecuteRequestExpectingContentAndHeader( + method, + (expect_response_body ? "content" : ""), + "server", + (expected_retry == 0 ? "proxy" : "not-proxy"), + expected_retry); + } + + // Starts a request with the given |method| and checks that the response + // contains |content| and the the header |header|: |value|, if |header| is + // non-empty. Verifies that the request's URL chain is the right length + // depending on whether or not a retry was expected (|expected_retry|). + void ExecuteRequestExpectingContentAndHeader(const std::string& method, + const std::string& content, + const std::string& header, + const std::string& value, + bool expected_retry) { + TestDelegate d; + URLRequest r(GURL("http://www.google.com/"), + net::DEFAULT_PRIORITY, + &d, + context_.get()); + r.set_method(method); + r.SetLoadFlags(net::LOAD_NORMAL); + + r.Start(); + base::RunLoop().Run(); + + EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status()); + EXPECT_EQ(net::OK, r.status().error()); + if (expected_retry) + EXPECT_EQ(2U, r.url_chain().size()); + else + EXPECT_EQ(1U, r.url_chain().size()); + + if (!header.empty()) { + // We also have a server header here that isn't set by the proxy. + EXPECT_TRUE(r.response_headers()->HasHeaderValue(header, value)); + } + + EXPECT_EQ(content, d.data_received()); + } + + // Returns the key to the |ProxyRetryInfoMap|. + std::string GetProxyKey(std::string proxy) { + GURL gurl(proxy); + std::string host_port = HostPortPair::FromURL(GURL(proxy)).ToString(); + if (gurl.SchemeIs("https")) + return "https://" + host_port; + return host_port; + } + + // Checks that |expected_num_bad_proxies| proxies are on the proxy retry list. + // If the list has one proxy, it should match |bad_proxy|. If it has two + // proxies, it should match |bad_proxy| and |bad_proxy2|. Checks also that + // the current delay associated with each bad proxy is |duration_seconds|. + void TestBadProxies(unsigned int expected_num_bad_proxies, + int duration_seconds, + const std::string& bad_proxy, + const std::string& bad_proxy2) { + const ProxyRetryInfoMap& retry_info = proxy_service_->proxy_retry_info(); + ASSERT_EQ(expected_num_bad_proxies, retry_info.size()); + + base::TimeDelta expected_min_duration; + base::TimeDelta expected_max_duration; + if (duration_seconds == 0) { + expected_min_duration = base::TimeDelta::FromMinutes(1); + expected_max_duration = base::TimeDelta::FromMinutes(5); + } + else { + expected_min_duration = base::TimeDelta::FromSeconds(duration_seconds); + expected_max_duration = base::TimeDelta::FromSeconds(duration_seconds); + } + + if (expected_num_bad_proxies >= 1u) { + ProxyRetryInfoMap::const_iterator i = + retry_info.find(GetProxyKey(bad_proxy)); + ASSERT_TRUE(i != retry_info.end()); + EXPECT_TRUE(expected_min_duration <= (*i).second.current_delay); + EXPECT_TRUE((*i).second.current_delay <= expected_max_duration); + } + if (expected_num_bad_proxies == 2u) { + ProxyRetryInfoMap::const_iterator i = + retry_info.find(GetProxyKey(bad_proxy2)); + ASSERT_TRUE(i != retry_info.end()); + EXPECT_TRUE(expected_min_duration <= (*i).second.current_delay); + EXPECT_TRUE((*i).second.current_delay <= expected_max_duration); + } + } + + protected: + base::MessageLoopForIO loop_; + + net::MockClientSocketFactory mock_socket_factory_; + net::MockHostResolver host_resolver_; + scoped_ptr<net::CertVerifier> cert_verifier_; + scoped_ptr<TransportSecurityState> transport_security_state_; + scoped_ptr<ProxyService> proxy_service_; + const scoped_refptr<net::SSLConfigService> ssl_config_service_; + scoped_refptr<HttpNetworkSession> network_session_; + scoped_ptr<HttpNetworkLayer> factory_; + net::HttpServerPropertiesImpl http_server_properties_; + scoped_ptr<TestDataReductionProxyParams> proxy_params_; + scoped_ptr<TestDataReductionProxyNetworkDelegate> network_delegate_; + + scoped_ptr<URLRequestContext> context_; +}; + +// Tests that request are deemed idempotent or not according to the method used. +TEST_F(DataReductionProxyProtocolTest, TestIdempotency) { + net::TestURLRequestContext context; + const struct { + const char* method; + bool expected_result; + } tests[] = { + { "GET", true }, + { "OPTIONS", true }, + { "HEAD", true }, + { "PUT", true }, + { "DELETE", true }, + { "TRACE", true }, + { "POST", false }, + { "CONNECT", false }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + net::TestURLRequest request(GURL("http://www.google.com/"), + net::DEFAULT_PRIORITY, + NULL, + &context); + request.set_method(tests[i].method); + EXPECT_EQ(tests[i].expected_result, IsRequestIdempotent(&request)); + } +} + +// Tests that the response is correctly overwritten as a redirect. +TEST_F(DataReductionProxyProtocolTest, OverrideResponseAsRedirect) { + net::TestURLRequestContext context; + const struct { + const char* headers; + const char* expected_headers; + } tests[] = { + { "HTTP/1.1 200 0K\n" + "Chrome-Proxy: block=1\n" + "Via: 1.1 Chrome-Compression-Proxy\n", + + "HTTP/1.1 302 Found\n" + "Chrome-Proxy: block=1\n" + "Via: 1.1 Chrome-Compression-Proxy\n" + "Location: http://www.google.com/\n" + }, + { "HTTP/1.1 302 Found\n" + "Location: http://foo.com/\n", + + "HTTP/1.1 302 Found\n" + "Location: http://www.google.com/\n" + }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + std::string headers(tests[i].headers); + HeadersToRaw(&headers); + scoped_refptr<HttpResponseHeaders> original_response_headers( + new HttpResponseHeaders(headers)); + scoped_refptr<HttpResponseHeaders> override_response_headers; + TestDelegate test_delegate; + net::TestURLRequest request(GURL("http://www.google.com/"), + net::DEFAULT_PRIORITY, + NULL, + &context); + OverrideResponseAsRedirect(&request, + original_response_headers, + &override_response_headers); + int expected_flags = net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_PROXY; + EXPECT_EQ(expected_flags, request.load_flags()); + std::string override_headers; + override_response_headers->GetNormalizedHeaders(&override_headers); + EXPECT_EQ(std::string(tests[i].expected_headers), override_headers); + } +} + + +// After each test, the proxy retry info will contain zero, one, or two of the +// data reduction proxies depending on whether no bypass was indicated by the +// initial response, a single proxy bypass was indicated, or a double bypass +// was indicated. In both the single and double bypass cases, if the request +// was idempotent, it will be retried over a direct connection. +TEST_F(DataReductionProxyProtocolTest, BypassLogic) { + std::string primary = GetDataReductionProxy(); + std::string fallback = GetDataReductionProxyFallback(); + const struct { + const char* method; + const char* first_response; + bool expected_retry; + size_t expected_bad_proxy_count; + bool expect_response_body; + int expected_duration; + } tests[] = { + // Valid data reduction proxy response with no bypass message. + { "GET", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + false, + 0u, + true, + -1 + }, + // Valid data reduction proxy response with older, but still valid via + // header. + { "GET", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Via: 1.1 Chrome Compression Proxy\r\n\r\n", + false, + 0u, + true, + -1 + }, + // Valid data reduction proxy response with chained via header, + // no bypass message. + { "GET", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Via: 1.1 Chrome-Compression-Proxy, 1.0 some-other-proxy\r\n\r\n", + false, + 0u, + true, + -1 + }, + // Valid data reduction proxy response with a bypass message. + { "GET", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Chrome-Proxy: bypass=0\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + true, + 1u, + true, + 0 + }, + // Valid data reduction proxy response with a bypass message. + { "GET", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Chrome-Proxy: bypass=1\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + true, + 1u, + true, + 1 + }, + // Same as above with the OPTIONS method, which is idempotent. + { "OPTIONS", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Chrome-Proxy: bypass=0\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + true, + 1u, + true, + 0 + }, + // Same as above with the HEAD method, which is idempotent. + { "HEAD", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Chrome-Proxy: bypass=0\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + true, + 1u, + false, + 0 + }, + // Same as above with the PUT method, which is idempotent. + { "PUT", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Chrome-Proxy: bypass=0\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + true, + 1u, + true, + 0 + }, + // Same as above with the DELETE method, which is idempotent. + { "DELETE", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Chrome-Proxy: bypass=0\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + true, + 1u, + true, + 0 + }, + // Same as above with the TRACE method, which is idempotent. + { "TRACE", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Chrome-Proxy: bypass=0\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + true, + 1u, + true, + 0 + }, + // 500 responses should be bypassed. + { "GET", + "HTTP/1.1 500 Internal Server Error\r\n" + "Server: proxy\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + true, + 1u, + true, + 0 + }, + // 502 responses should be bypassed. + { "GET", + "HTTP/1.1 502 Internal Server Error\r\n" + "Server: proxy\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + true, + 1u, + true, + 0 + }, + // 503 responses should be bypassed. + { "GET", + "HTTP/1.1 503 Internal Server Error\r\n" + "Server: proxy\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + true, + 1u, + true, + 0 + }, + // Invalid data reduction proxy response. Missing Via header. + { "GET", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n\r\n", + true, + 1u, + true, + 0 + }, + // Invalid data reduction proxy response. Wrong Via header. + { "GET", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Via: 1.0 some-other-proxy\r\n\r\n", + true, + 1u, + true, + 0 + }, + // Valid data reduction proxy response. 304 missing Via header. + { "GET", + "HTTP/1.1 304 Not Modified\r\n" + "Server: proxy\r\n\r\n", + false, + 0u, + false, + 0 + }, + // Valid data reduction proxy response with a bypass message. It will + // not be retried because the request is non-idempotent. + { "POST", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Chrome-Proxy: bypass=0\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + false, + 1u, + true, + 0 + }, + // Valid data reduction proxy response with block message. Both proxies + // should be on the retry list when it completes. + { "GET", + "HTTP/1.1 200 OK\r\n" + "Server: proxy\r\n" + "Chrome-Proxy: block=1\r\n" + "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n", + true, + 2u, + true, + 1 + } + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + ConfigureTestDependencies( + ProxyService::CreateFixedFromPacResult("PROXY " + + HostPortPair::FromURL(GURL(primary)).ToString() + "; PROXY " + + HostPortPair::FromURL(GURL(fallback)).ToString() + "; DIRECT")); + TestProxyFallback(tests[i].method, + tests[i].first_response, + tests[i].expected_retry, + tests[i].expect_response_body); + + // We should also observe the bad proxy in the retry list. + TestBadProxies(tests[i].expected_bad_proxy_count, + tests[i].expected_duration, + primary, fallback); + } +} + +TEST_F(DataReductionProxyProtocolTest, + ProxyBypassIgnoredOnDirectConnection) { + // Verify that a Chrome-Proxy header is ignored when returned from a directly + // connected origin server. + ConfigureTestDependencies(ProxyService::CreateFixedFromPacResult("DIRECT")); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n" + "Chrome-Proxy: bypass=0\r\n\r\n"), + MockRead("Bypass message"), + MockRead(net::SYNCHRONOUS, net::OK), + }; + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "User-Agent:\r\n" + "Accept-Encoding: gzip,deflate\r\n\r\n"), + }; + StaticSocketDataProvider data1(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + mock_socket_factory_.AddSocketDataProvider(&data1); + + TestDelegate d; + URLRequest r(GURL("http://www.google.com/"), + net::DEFAULT_PRIORITY, + &d, + context_.get()); + r.set_method("GET"); + r.SetLoadFlags(net::LOAD_NORMAL); + + r.Start(); + base::RunLoop().Run(); + + EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status()); + EXPECT_EQ(net::OK, r.status().error()); + + EXPECT_EQ("Bypass message", d.data_received()); + + // We should have no entries in our bad proxy list. + TestBadProxies(0, -1, "", ""); +} + +} // namespace data_reduction_proxy diff --git a/components/data_reduction_proxy/browser/data_reduction_proxy_settings.cc b/components/data_reduction_proxy/browser/data_reduction_proxy_settings.cc index 1f7cd2f..5fc9213e 100644 --- a/components/data_reduction_proxy/browser/data_reduction_proxy_settings.cc +++ b/components/data_reduction_proxy/browser/data_reduction_proxy_settings.cc @@ -247,7 +247,8 @@ bool DataReductionProxySettings::IsDataReductionProxyEnabled() { DataReductionProxyParams::IsKeySetOnCommandLine(); } -bool DataReductionProxySettings::IsDataReductionProxyAlternativeEnabled() { +bool +DataReductionProxySettings::IsDataReductionProxyAlternativeEnabled() const { return data_reduction_proxy_alternative_enabled_.GetValue(); } @@ -590,6 +591,7 @@ net::URLFetcher* DataReductionProxySettings::GetURLFetcher() { // Configure max retries to be at most kMaxRetries times for 5xx errors. static const int kMaxRetries = 5; fetcher->SetMaxRetriesOn5xx(kMaxRetries); + fetcher->SetAutomaticallyRetryOnNetworkChanges(kMaxRetries); return fetcher; } diff --git a/components/data_reduction_proxy/browser/data_reduction_proxy_settings.h b/components/data_reduction_proxy/browser/data_reduction_proxy_settings.h index cf0abe1..c7fb154 100644 --- a/components/data_reduction_proxy/browser/data_reduction_proxy_settings.h +++ b/components/data_reduction_proxy/browser/data_reduction_proxy_settings.h @@ -41,41 +41,41 @@ const unsigned int kNumDaysInHistorySummary = 30; COMPILE_ASSERT(kNumDaysInHistorySummary <= kNumDaysInHistory, DataReductionProxySettings_summary_too_long); - // Values of the UMA DataReductionProxy.StartupState histogram. - // This enum must remain synchronized with DataReductionProxyStartupState - // in metrics/histograms/histograms.xml. - enum ProxyStartupState { - PROXY_NOT_AVAILABLE = 0, - PROXY_DISABLED, - PROXY_ENABLED, - PROXY_STARTUP_STATE_COUNT, - }; - - // Values of the UMA DataReductionProxy.ProbeURL histogram. - // This enum must remain synchronized with - // DataReductionProxyProbeURLFetchResult in metrics/histograms/histograms.xml. - // TODO(marq): Rename these histogram buckets with s/DISABLED/RESTRICTED/, so - // their names match the behavior they track. - enum ProbeURLFetchResult { - // The probe failed because the Internet was disconnected. - INTERNET_DISCONNECTED = 0, - - // The probe failed for any other reason, and as a result, the proxy was - // disabled. - FAILED_PROXY_DISABLED, - - // The probe failed, but the proxy was already restricted. - FAILED_PROXY_ALREADY_DISABLED, - - // THe probe succeeded, and as a result the proxy was restricted. - SUCCEEDED_PROXY_ENABLED, - - // The probe succeeded, but the proxy was already restricted. - SUCCEEDED_PROXY_ALREADY_ENABLED, - - // This must always be last. - PROBE_URL_FETCH_RESULT_COUNT - }; +// Values of the UMA DataReductionProxy.StartupState histogram. +// This enum must remain synchronized with DataReductionProxyStartupState +// in metrics/histograms/histograms.xml. +enum ProxyStartupState { + PROXY_NOT_AVAILABLE = 0, + PROXY_DISABLED, + PROXY_ENABLED, + PROXY_STARTUP_STATE_COUNT, +}; + +// Values of the UMA DataReductionProxy.ProbeURL histogram. +// This enum must remain synchronized with +// DataReductionProxyProbeURLFetchResult in metrics/histograms/histograms.xml. +// TODO(marq): Rename these histogram buckets with s/DISABLED/RESTRICTED/, so +// their names match the behavior they track. +enum ProbeURLFetchResult { + // The probe failed because the Internet was disconnected. + INTERNET_DISCONNECTED = 0, + + // The probe failed for any other reason, and as a result, the proxy was + // disabled. + FAILED_PROXY_DISABLED, + + // The probe failed, but the proxy was already restricted. + FAILED_PROXY_ALREADY_DISABLED, + + // The probe succeeded, and as a result the proxy was restricted. + SUCCEEDED_PROXY_ENABLED, + + // The probe succeeded, but the proxy was already restricted. + SUCCEEDED_PROXY_ALREADY_ENABLED, + + // This must always be last. + PROBE_URL_FETCH_RESULT_COUNT +}; // Central point for configuring the data reduction proxy. // This object lives on the UI thread and all of its methods are expected to @@ -141,7 +141,7 @@ class DataReductionProxySettings bool IsDataReductionProxyEnabled(); // Returns true if the alternative proxy is enabled. - bool IsDataReductionProxyAlternativeEnabled(); + bool IsDataReductionProxyAlternativeEnabled() const; // Returns true if the proxy is managed by an adminstrator's policy. bool IsDataReductionProxyManaged(); |