diff options
author | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-04 22:43:12 +0000 |
---|---|---|
committer | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-04 22:43:12 +0000 |
commit | 620f57148c311c4dc2cf680a4a5861fbdcd29993 (patch) | |
tree | 93408ed35ebbaccd8f45734c7267392c81d71ca9 /net | |
parent | 289fdf862c649d17ddb2e08295304efb98f641f6 (diff) | |
download | chromium_src-620f57148c311c4dc2cf680a4a5861fbdcd29993.zip chromium_src-620f57148c311c4dc2cf680a4a5861fbdcd29993.tar.gz chromium_src-620f57148c311c4dc2cf680a4a5861fbdcd29993.tar.bz2 |
Better match IE's proxy settings.
* When BOTH autodetect and custom PAC script are given, try both.
* Use successful PAC parsing as the heuristic for determining when a script is valid (rather than first-request).
* Only apply the proxy bypass list when using non-PAC.
The high level explanation on how this works:
http://sites.google.com/a/chromium.org/dev/developers/design-documents/proxy-settings-fallback
BUG= http://crbug.com/18271, http://crbug.com/9985
TEST=unit tests.
Review URL: http://codereview.chromium.org/160510
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@22430 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/net.gyp | 3 | ||||
-rw-r--r-- | net/proxy/init_proxy_resolver.cc | 189 | ||||
-rw-r--r-- | net/proxy/init_proxy_resolver.h | 104 | ||||
-rw-r--r-- | net/proxy/init_proxy_resolver_unittest.cc | 325 | ||||
-rw-r--r-- | net/proxy/proxy_resolver.h | 30 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_mac.h | 6 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_perftest.cc | 6 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_v8.cc | 47 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_v8.h | 7 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_v8_unittest.cc | 95 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_winhttp.cc | 10 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_winhttp.h | 5 | ||||
-rw-r--r-- | net/proxy/proxy_script_fetcher.cc | 11 | ||||
-rw-r--r-- | net/proxy/proxy_script_fetcher.h | 6 | ||||
-rw-r--r-- | net/proxy/proxy_script_fetcher_unittest.cc | 3 | ||||
-rw-r--r-- | net/proxy/proxy_service.cc | 145 | ||||
-rw-r--r-- | net/proxy/proxy_service.h | 50 | ||||
-rw-r--r-- | net/proxy/proxy_service_unittest.cc | 445 | ||||
-rw-r--r-- | net/proxy/single_threaded_proxy_resolver.cc | 119 | ||||
-rw-r--r-- | net/proxy/single_threaded_proxy_resolver.h | 17 | ||||
-rw-r--r-- | net/proxy/single_threaded_proxy_resolver_unittest.cc | 75 |
21 files changed, 1408 insertions, 290 deletions
diff --git a/net/net.gyp b/net/net.gyp index ab5972c..3bba793 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -251,6 +251,8 @@ 'http/partial_data.h', 'ocsp/nss_ocsp.cc', 'ocsp/nss_ocsp.h', + 'proxy/init_proxy_resolver.cc', + 'proxy/init_proxy_resolver.h', 'proxy/proxy_config.cc', 'proxy/proxy_config.h', 'proxy/proxy_config_service.h', @@ -495,6 +497,7 @@ 'http/http_transaction_unittest.h', 'http/http_util_unittest.cc', 'http/http_vary_data_unittest.cc', + 'proxy/init_proxy_resolver_unittest.cc', 'proxy/proxy_config_service_linux_unittest.cc', 'proxy/proxy_config_service_win_unittest.cc', 'proxy/proxy_config_unittest.cc', diff --git a/net/proxy/init_proxy_resolver.cc b/net/proxy/init_proxy_resolver.cc new file mode 100644 index 0000000..7b98c4a --- /dev/null +++ b/net/proxy/init_proxy_resolver.cc @@ -0,0 +1,189 @@ +// Copyright (c) 2009 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 "net/proxy/init_proxy_resolver.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "net/base/net_errors.h" +#include "net/proxy/proxy_config.h" +#include "net/proxy/proxy_resolver.h" +#include "net/proxy/proxy_script_fetcher.h" + +namespace net { + +InitProxyResolver::InitProxyResolver(ProxyResolver* resolver, + ProxyScriptFetcher* proxy_script_fetcher) + : resolver_(resolver), + proxy_script_fetcher_(proxy_script_fetcher), + ALLOW_THIS_IN_INITIALIZER_LIST(io_callback_( + this, &InitProxyResolver::OnIOCompletion)), + user_callback_(NULL), + current_pac_url_index_(0u), + next_state_(STATE_NONE) { +} + +InitProxyResolver::~InitProxyResolver() { + switch (next_state_) { + case STATE_FETCH_PAC_SCRIPT_COMPLETE: + proxy_script_fetcher_->Cancel(); + break; + case STATE_SET_PAC_SCRIPT_COMPLETE: + resolver_->CancelSetPacScript(); + break; + default: + break; + } +} + +int InitProxyResolver::Init(const ProxyConfig& config, + CompletionCallback* callback) { + DCHECK_EQ(STATE_NONE, next_state_); + DCHECK(callback); + DCHECK(config.MayRequirePACResolver()); + + pac_urls_ = BuildPacUrlsFallbackList(config); + DCHECK(!pac_urls_.empty()); + + next_state_ = GetStartState(); + + int rv = DoLoop(OK); + if (rv == ERR_IO_PENDING) + user_callback_ = callback; + return rv; +} + +// Initialize the fallback rules. +// (1) WPAD +// (2) Custom PAC URL. +InitProxyResolver::UrlList InitProxyResolver::BuildPacUrlsFallbackList( + const ProxyConfig& config) const { + UrlList pac_urls; + if (config.auto_detect) { + GURL pac_url = resolver_->expects_pac_bytes() ? + GURL("http://wpad/wpad.dat") : GURL(); + pac_urls.push_back(pac_url); + } + if (config.pac_url.is_valid()) + pac_urls.push_back(config.pac_url); + return pac_urls; +} + +void InitProxyResolver::OnIOCompletion(int result) { + DCHECK_NE(STATE_NONE, next_state_); + int rv = DoLoop(result); + if (rv != ERR_IO_PENDING) + DoCallback(rv); +} + +int InitProxyResolver::DoLoop(int result) { + DCHECK_NE(next_state_, STATE_NONE); + int rv = result; + do { + State state = next_state_; + next_state_ = STATE_NONE; + switch (state) { + case STATE_FETCH_PAC_SCRIPT: + DCHECK_EQ(OK, rv); + rv = DoFetchPacScript(); + break; + case STATE_FETCH_PAC_SCRIPT_COMPLETE: + rv = DoFetchPacScriptComplete(rv); + break; + case STATE_SET_PAC_SCRIPT: + DCHECK_EQ(OK, rv); + rv = DoSetPacScript(); + break; + case STATE_SET_PAC_SCRIPT_COMPLETE: + rv = DoSetPacScriptComplete(rv); + break; + default: + NOTREACHED() << "bad state"; + rv = ERR_UNEXPECTED; + break; + } + } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); + return rv; +} + +void InitProxyResolver::DoCallback(int result) { + DCHECK_NE(ERR_IO_PENDING, result); + DCHECK(user_callback_); + user_callback_->Run(result); +} + +int InitProxyResolver::DoFetchPacScript() { + DCHECK(resolver_->expects_pac_bytes()); + + next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE; + + const GURL& pac_url = current_pac_url(); + + LOG(INFO) << "Starting fetch of PAC script " << pac_url; + + return proxy_script_fetcher_->Fetch(pac_url, &pac_bytes_, &io_callback_); +} + +int InitProxyResolver::DoFetchPacScriptComplete(int result) { + DCHECK(resolver_->expects_pac_bytes()); + + LOG(INFO) << "Completed PAC script fetch of " << current_pac_url() + << " with result " << ErrorToString(result) + << ". Fetched a total of " << pac_bytes_.size() << " bytes"; + + if (result != OK) + return TryToFallbackPacUrl(result); + + next_state_ = STATE_SET_PAC_SCRIPT; + return result; +} + +int InitProxyResolver::DoSetPacScript() { + const GURL& pac_url = current_pac_url(); + + next_state_ = STATE_SET_PAC_SCRIPT_COMPLETE; + + return resolver_->expects_pac_bytes() ? + resolver_->SetPacScriptByData(pac_bytes_, &io_callback_) : + resolver_->SetPacScriptByUrl(pac_url, &io_callback_); +} + +int InitProxyResolver::DoSetPacScriptComplete(int result) { + if (result != OK) { + LOG(INFO) << "Failed configuring PAC using " << current_pac_url() + << " with error " << ErrorToString(result); + return TryToFallbackPacUrl(result); + } + return result; +} + +int InitProxyResolver::TryToFallbackPacUrl(int error) { + DCHECK_LT(error, 0); + + if (current_pac_url_index_ + 1 >= pac_urls_.size()) { + // Nothing left to fall back to. + return error; + } + + // Advance to next URL in our list. + ++current_pac_url_index_; + + LOG(INFO) << "Falling back to next PAC URL..."; + + next_state_ = GetStartState(); + + return OK; +} + +InitProxyResolver::State InitProxyResolver::GetStartState() const { + return resolver_->expects_pac_bytes() ? + STATE_FETCH_PAC_SCRIPT : STATE_SET_PAC_SCRIPT; +} + +const GURL& InitProxyResolver::current_pac_url() const { + DCHECK_LT(current_pac_url_index_, pac_urls_.size()); + return pac_urls_[current_pac_url_index_]; +} + +} // namespace net diff --git a/net/proxy/init_proxy_resolver.h b/net/proxy/init_proxy_resolver.h new file mode 100644 index 0000000..4da28f2 --- /dev/null +++ b/net/proxy/init_proxy_resolver.h @@ -0,0 +1,104 @@ +// Copyright (c) 2009 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 NET_PROXY_INIT_PROXY_RESOLVER_H_ +#define NET_PROXY_INIT_PROXY_RESOLVER_H_ + +#include <string> +#include <vector> + +#include "googleurl/src/gurl.h" +#include "net/base/completion_callback.h" + +namespace net { + +class ProxyConfig; +class ProxyResolver; +class ProxyScriptFetcher; + +// InitProxyResolver is a helper class used by ProxyService to +// initialize a ProxyResolver with the PAC script data specified +// by a particular ProxyConfig. +// +// This involves trying to use PAC scripts in this order: +// +// (1) WPAD if auto-detect is on. +// (2) Custom PAC script if a URL was given. +// +// If no PAC script was successfully downloaded + parsed, then it fails with +// a network error. Otherwise the proxy resolver is left initialized with +// the PAC script. +// +// Deleting InitProxyResolver while Init() is in progress, will +// cancel the request. +// +class InitProxyResolver { + public: + // |resolver| and |proxy_script_fetcher| must remain valid for + // the lifespan of InitProxyResolver. + InitProxyResolver(ProxyResolver* resolver, + ProxyScriptFetcher* proxy_script_fetcher); + + // Aborts any in-progress request. + ~InitProxyResolver(); + + // Apply the PAC settings of |config| to |resolver_|. + int Init(const ProxyConfig& config, CompletionCallback* callback); + + private: + enum State { + STATE_NONE, + STATE_FETCH_PAC_SCRIPT, + STATE_FETCH_PAC_SCRIPT_COMPLETE, + STATE_SET_PAC_SCRIPT, + STATE_SET_PAC_SCRIPT_COMPLETE, + }; + typedef std::vector<GURL> UrlList; + + // Returns ordered list of PAC urls to try for |config|. + UrlList BuildPacUrlsFallbackList(const ProxyConfig& config) const; + + void OnIOCompletion(int result); + int DoLoop(int result); + void DoCallback(int result); + + int DoFetchPacScript(); + int DoFetchPacScriptComplete(int result); + + int DoSetPacScript(); + int DoSetPacScriptComplete(int result); + + // Tries restarting using the next fallback PAC URL: + // |pac_urls_[++current_pac_url_index]|. + // Returns OK and rewinds the state machine when there + // is something to try, otherwise returns |error|. + int TryToFallbackPacUrl(int error); + + // Gets the initial state (we skip fetching when the + // ProxyResolver doesn't |expect_pac_bytes()|. + State GetStartState() const; + + // Returns the current PAC URL we are fetching/testing. + const GURL& current_pac_url() const; + + ProxyResolver* resolver_; + ProxyScriptFetcher* proxy_script_fetcher_; + + CompletionCallbackImpl<InitProxyResolver> io_callback_; + CompletionCallback* user_callback_; + + size_t current_pac_url_index_; + + // Filled when the PAC script fetch completes. + std::string pac_bytes_; + + UrlList pac_urls_; + State next_state_; + + DISALLOW_COPY_AND_ASSIGN(InitProxyResolver); +}; + +} // namespace net + +#endif // NET_PROXY_INIT_PROXY_RESOLVER_H_ diff --git a/net/proxy/init_proxy_resolver_unittest.cc b/net/proxy/init_proxy_resolver_unittest.cc new file mode 100644 index 0000000..1f4417d --- /dev/null +++ b/net/proxy/init_proxy_resolver_unittest.cc @@ -0,0 +1,325 @@ +// Copyright (c) 2009 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 <vector> + +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/proxy/init_proxy_resolver.h" +#include "net/proxy/proxy_config.h" +#include "net/proxy/proxy_resolver.h" +#include "net/proxy/proxy_script_fetcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace { + +enum Error { + kFailedDownloading = -100, + kFailedParsing = -200, +}; + +class Rules { + public: + struct Rule { + Rule(const GURL& url, + int fetch_error, + int set_pac_error) + : url(url), + fetch_error(fetch_error), + set_pac_error(set_pac_error) { + } + + std::string bytes() const { + if (set_pac_error == OK) + return url.spec() + "!valid-script"; + if (fetch_error == OK) + return url.spec() + "!invalid-script"; + return std::string(); + } + + GURL url; + int fetch_error; + int set_pac_error; + }; + + Rule AddSuccessRule(const char* url) { + Rule rule(GURL(url), OK /*fetch_error*/, OK /*set_pac_error*/); + rules_.push_back(rule); + return rule; + } + + void AddFailDownloadRule(const char* url) { + rules_.push_back(Rule(GURL(url), kFailedDownloading /*fetch_error*/, + ERR_UNEXPECTED /*set_pac_error*/)); + } + + void AddFailParsingRule(const char* url) { + rules_.push_back(Rule(GURL(url), OK /*fetch_error*/, + kFailedParsing /*set_pac_error*/)); + } + + const Rule& GetRuleByUrl(const GURL& url) const { + for (RuleList::const_iterator it = rules_.begin(); it != rules_.end(); + ++it) { + if (it->url == url) + return *it; + } + CHECK(false) << "Rule not found for " << url; + return rules_[0]; + } + + const Rule& GetRuleByBytes(const std::string& bytes) const { + for (RuleList::const_iterator it = rules_.begin(); it != rules_.end(); + ++it) { + if (it->bytes() == bytes) + return *it; + } + CHECK(false) << "Rule not found for " << bytes; + return rules_[0]; + } + + private: + typedef std::vector<Rule> RuleList; + RuleList rules_; +}; + +class RuleBasedProxyScriptFetcher : public ProxyScriptFetcher { + public: + explicit RuleBasedProxyScriptFetcher(const Rules* rules) : rules_(rules) {} + + // ProxyScriptFetcher implementation. + virtual int Fetch(const GURL& url, + std::string* bytes, + CompletionCallback* callback) { + const Rules::Rule& rule = rules_->GetRuleByUrl(url); + int rv = rule.fetch_error; + EXPECT_NE(ERR_UNEXPECTED, rv); + if (rv == OK) + *bytes = rule.bytes(); + return rv; + } + + virtual void Cancel() {} + + private: + const Rules* rules_; +}; + +class RuleBasedProxyResolver : public ProxyResolver { + public: + RuleBasedProxyResolver(const Rules* rules, bool expects_pac_bytes) + : ProxyResolver(expects_pac_bytes), rules_(rules) {} + + // ProxyResolver implementation: + virtual int GetProxyForURL(const GURL& /*url*/, + ProxyInfo* /*results*/, + CompletionCallback* /*callback*/, + RequestHandle* /*request_handle*/) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + + virtual void CancelRequest(RequestHandle request_handle) { + NOTREACHED(); + } + + virtual int SetPacScript(const GURL& pac_url, + const std::string& pac_bytes, + CompletionCallback* callback) { + const Rules::Rule& rule = expects_pac_bytes() ? + rules_->GetRuleByBytes(pac_bytes) : + rules_->GetRuleByUrl(pac_url); + + int rv = rule.set_pac_error; + EXPECT_NE(ERR_UNEXPECTED, rv); + + if (expects_pac_bytes()) + EXPECT_EQ(rule.bytes(), pac_bytes); + else + EXPECT_EQ(rule.url, pac_url); + + if (rv == OK) { + pac_bytes_ = pac_bytes; + pac_url_ = pac_url; + } + return rv; + } + + const std::string& pac_bytes() const { return pac_bytes_; } + const GURL& pac_url() const { return pac_url_; } + + private: + const Rules* rules_; + std::string pac_bytes_; + GURL pac_url_; +}; + +// Succeed using custom PAC script. +TEST(InitProxyResolverTest, CustomPacSucceeds) { + Rules rules; + RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); + RuleBasedProxyScriptFetcher fetcher(&rules); + + ProxyConfig config; + config.pac_url = GURL("http://custom/proxy.pac"); + + Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + InitProxyResolver init(&resolver, &fetcher); + EXPECT_EQ(OK, init.Init(config, &callback)); + EXPECT_EQ(rule.bytes(), resolver.pac_bytes()); +} + +// Fail downloading the custom PAC script. +TEST(InitProxyResolverTest, CustomPacFails1) { + Rules rules; + RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); + RuleBasedProxyScriptFetcher fetcher(&rules); + + ProxyConfig config; + config.pac_url = GURL("http://custom/proxy.pac"); + + rules.AddFailDownloadRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + InitProxyResolver init(&resolver, &fetcher); + EXPECT_EQ(kFailedDownloading, init.Init(config, &callback)); + EXPECT_EQ("", resolver.pac_bytes()); +} + +// Fail parsing the custom PAC script. +TEST(InitProxyResolverTest, CustomPacFails2) { + Rules rules; + RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); + RuleBasedProxyScriptFetcher fetcher(&rules); + + ProxyConfig config; + config.pac_url = GURL("http://custom/proxy.pac"); + + rules.AddFailParsingRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + InitProxyResolver init(&resolver, &fetcher); + EXPECT_EQ(kFailedParsing, init.Init(config, &callback)); + EXPECT_EQ("", resolver.pac_bytes()); +} + +// Succeeds in choosing autodetect (wpad). +TEST(InitProxyResolverTest, AutodetectSuccess) { + Rules rules; + RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); + RuleBasedProxyScriptFetcher fetcher(&rules); + + ProxyConfig config; + config.auto_detect = true; + + Rules::Rule rule = rules.AddSuccessRule("http://wpad/wpad.dat"); + + TestCompletionCallback callback; + InitProxyResolver init(&resolver, &fetcher); + EXPECT_EQ(OK, init.Init(config, &callback)); + EXPECT_EQ(rule.bytes(), resolver.pac_bytes()); +} + +// Fails at WPAD (downloading), but succeeds in choosing the custom PAC. +TEST(InitProxyResolverTest, AutodetectFailCustomSuccess1) { + Rules rules; + RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); + RuleBasedProxyScriptFetcher fetcher(&rules); + + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://custom/proxy.pac"); + + rules.AddFailDownloadRule("http://wpad/wpad.dat"); + Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + InitProxyResolver init(&resolver, &fetcher); + EXPECT_EQ(OK, init.Init(config, &callback)); + EXPECT_EQ(rule.bytes(), resolver.pac_bytes()); +} + +// Fails at WPAD (parsing), but succeeds in choosing the custom PAC. +TEST(InitProxyResolverTest, AutodetectFailCustomSuccess2) { + Rules rules; + RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); + RuleBasedProxyScriptFetcher fetcher(&rules); + + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://custom/proxy.pac"); + + rules.AddFailParsingRule("http://wpad/wpad.dat"); + Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + InitProxyResolver init(&resolver, &fetcher); + EXPECT_EQ(OK, init.Init(config, &callback)); + EXPECT_EQ(rule.bytes(), resolver.pac_bytes()); +} + +// Fails at WPAD (downloading), and fails at custom PAC (downloading). +TEST(InitProxyResolverTest, AutodetectFailCustomFails1) { + Rules rules; + RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); + RuleBasedProxyScriptFetcher fetcher(&rules); + + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://custom/proxy.pac"); + + rules.AddFailDownloadRule("http://wpad/wpad.dat"); + rules.AddFailDownloadRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + InitProxyResolver init(&resolver, &fetcher); + EXPECT_EQ(kFailedDownloading, init.Init(config, &callback)); + EXPECT_EQ("", resolver.pac_bytes()); +} + +// Fails at WPAD (downloading), and fails at custom PAC (parsing). +TEST(InitProxyResolverTest, AutodetectFailCustomFails2) { + Rules rules; + RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); + RuleBasedProxyScriptFetcher fetcher(&rules); + + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://custom/proxy.pac"); + + rules.AddFailDownloadRule("http://wpad/wpad.dat"); + rules.AddFailParsingRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + InitProxyResolver init(&resolver, &fetcher); + EXPECT_EQ(kFailedParsing, init.Init(config, &callback)); + EXPECT_EQ("", resolver.pac_bytes()); +} + +// Fails at WPAD (parsing), but succeeds in choosing the custom PAC. +// This is the same as AutodetectFailCustomSuccess2, but using a ProxyResolver +// that doesn't |expects_pac_bytes|. +TEST(InitProxyResolverTest, AutodetectFailCustomSuccess2_NoFetch) { + Rules rules; + RuleBasedProxyResolver resolver(&rules, false /*expects_pac_bytes*/); + RuleBasedProxyScriptFetcher fetcher(&rules); + + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://custom/proxy.pac"); + + rules.AddFailParsingRule(""); // Autodetect. + Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + InitProxyResolver init(&resolver, &fetcher); + EXPECT_EQ(OK, init.Init(config, &callback)); + EXPECT_EQ(rule.url, resolver.pac_url()); +} + +} // namespace +} // namespace net diff --git a/net/proxy/proxy_resolver.h b/net/proxy/proxy_resolver.h index ce0e81d..89fffb1 100644 --- a/net/proxy/proxy_resolver.h +++ b/net/proxy/proxy_resolver.h @@ -8,10 +8,9 @@ #include <string> #include "base/logging.h" +#include "googleurl/src/gurl.h" #include "net/base/completion_callback.h" -class GURL; - namespace net { class ProxyInfo; @@ -52,29 +51,32 @@ class ProxyResolver { bool expects_pac_bytes() const { return expects_pac_bytes_; } // Sets the PAC script backend to use for this proxy resolver (by URL). - void SetPacScriptByUrl(const GURL& pac_url) { + int SetPacScriptByUrl(const GURL& url, CompletionCallback* callback) { DCHECK(!expects_pac_bytes()); - SetPacScriptByUrlInternal(pac_url); + return SetPacScript(url, std::string(), callback); } // Sets the PAC script backend to use for this proxy resolver (by contents). - void SetPacScriptByData(const std::string& bytes) { + int SetPacScriptByData(const std::string& bytes, + CompletionCallback* callback) { DCHECK(expects_pac_bytes()); - SetPacScriptByDataInternal(bytes); + return SetPacScript(GURL(), bytes, callback); } - private: - // Called to set the PAC script backend to use. If |pac_url| is invalid, - // this is a request to use WPAD (auto detect). - virtual void SetPacScriptByUrlInternal(const GURL& pac_url) { + // TODO(eroman): Make this =0. + virtual void CancelSetPacScript() { NOTREACHED(); } - // Called to set the PAC script backend to use. |bytes| may be empty if the + private: + // Called to set the PAC script backend to use. If |pac_url| is invalid, + // this is a request to use WPAD (auto detect). |bytes| may be empty if the // fetch failed, or if the fetch returned no content. - virtual void SetPacScriptByDataInternal(const std::string& bytes) { - NOTREACHED(); - } + // Returns ERR_IO_PENDING in the case of asynchronous completion, and notifies + // the result through |callback|. + virtual int SetPacScript(const GURL& pac_url, + const std::string& bytes, + CompletionCallback* callback) = 0; const bool expects_pac_bytes_; diff --git a/net/proxy/proxy_resolver_mac.h b/net/proxy/proxy_resolver_mac.h index 58f6096..6bad1d2 100644 --- a/net/proxy/proxy_resolver_mac.h +++ b/net/proxy/proxy_resolver_mac.h @@ -8,6 +8,7 @@ #include <string> #include "googleurl/src/gurl.h" +#include "net/base/net_errors.h" #include "net/proxy/proxy_config_service.h" #include "net/proxy/proxy_resolver.h" @@ -30,8 +31,11 @@ class ProxyResolverMac : public ProxyResolver { } private: - virtual void SetPacScriptByUrlInternal(const GURL& pac_url) { + virtual int SetPacScript(const GURL& pac_url, + const std::string& /*pac_bytes*/, + CompletionCallback* /*callback*/) { pac_url_ = pac_url; + return OK; } GURL pac_url_; diff --git a/net/proxy/proxy_resolver_perftest.cc b/net/proxy/proxy_resolver_perftest.cc index cb20ff9..efaf437 100644 --- a/net/proxy/proxy_resolver_perftest.cc +++ b/net/proxy/proxy_resolver_perftest.cc @@ -97,7 +97,8 @@ class PacPerfSuiteRunner { InitHttpServer(); GURL pac_url = server_->TestServerPage(std::string("files/") + script_name); - resolver_->SetPacScriptByUrl(pac_url); + int rv = resolver_->SetPacScriptByUrl(pac_url, NULL); + EXPECT_EQ(net::OK, rv); } else { LoadPacScriptIntoResolver(script_name); } @@ -163,7 +164,8 @@ class PacPerfSuiteRunner { ASSERT_TRUE(ok); // Load the PAC script into the ProxyResolver. - resolver_->SetPacScriptByData(file_contents); + int rv = resolver_->SetPacScriptByData(file_contents, NULL); + EXPECT_EQ(net::OK, rv); } net::ProxyResolver* resolver_; diff --git a/net/proxy/proxy_resolver_v8.cc b/net/proxy/proxy_resolver_v8.cc index 0b77145..8c40ecf 100644 --- a/net/proxy/proxy_resolver_v8.cc +++ b/net/proxy/proxy_resolver_v8.cc @@ -53,10 +53,9 @@ bool V8ObjectToString(v8::Handle<v8::Value> object, std::string* result) { class ProxyResolverV8::Context { public: - Context(ProxyResolverJSBindings* js_bindings, const std::string& pac_data) + explicit Context(ProxyResolverJSBindings* js_bindings) : js_bindings_(js_bindings) { DCHECK(js_bindings != NULL); - InitV8(pac_data); } ~Context() { @@ -72,9 +71,8 @@ class ProxyResolverV8::Context { v8::Context::Scope function_scope(v8_context_); - v8::Local<v8::Value> function = - v8_context_->Global()->Get(v8::String::New("FindProxyForURL")); - if (!function->IsFunction()) { + v8::Local<v8::Value> function; + if (!GetFindProxyForURL(&function)) { js_bindings_->OnError(-1, "FindProxyForURL() is undefined."); return ERR_PAC_SCRIPT_FAILED; } @@ -105,8 +103,7 @@ class ProxyResolverV8::Context { return OK; } - private: - void InitV8(const std::string& pac_data) { + int InitV8(const std::string& pac_data) { v8::Locker locked; v8::HandleScope scope; @@ -145,8 +142,24 @@ class ProxyResolverV8::Context { if (!code.IsEmpty()) code->Run(); - if (try_catch.HasCaught()) + if (try_catch.HasCaught()) { HandleError(try_catch.Message()); + return ERR_PAC_SCRIPT_FAILED; + } + + // At a minimum, the FindProxyForURL() function must be defined for this + // to be a legitimiate PAC script. + v8::Local<v8::Value> function; + if (!GetFindProxyForURL(&function)) + return ERR_PAC_SCRIPT_FAILED; + + return OK; + } + + private: + bool GetFindProxyForURL(v8::Local<v8::Value>* function) { + *function = v8_context_->Global()->Get(v8::String::New("FindProxyForURL")); + return (*function)->IsFunction(); } // Handle an exception thrown by V8. @@ -233,8 +246,7 @@ int ProxyResolverV8::GetProxyForURL(const GURL& query_url, CompletionCallback* /*callback*/, RequestHandle* /*request*/) { // If the V8 instance has not been initialized (either because - // SetPacScriptByData() wasn't called yet, or because it was called with - // empty string). + // SetPacScript() wasn't called yet, or because it failed. if (!context_.get()) return ERR_FAILED; @@ -247,10 +259,19 @@ void ProxyResolverV8::CancelRequest(RequestHandle request) { NOTREACHED(); } -void ProxyResolverV8::SetPacScriptByDataInternal(const std::string& data) { +int ProxyResolverV8::SetPacScript(const GURL& /*url*/, + const std::string& bytes, + CompletionCallback* /*callback*/) { context_.reset(); - if (!data.empty()) - context_.reset(new Context(js_bindings_.get(), data)); + if (bytes.empty()) + return ERR_PAC_SCRIPT_FAILED; + + // Try parsing the PAC script. + scoped_ptr<Context> context(new Context(js_bindings_.get())); + int rv = context->InitV8(bytes); + if (rv == OK) + context_.reset(context.release()); + return rv; } } // namespace net diff --git a/net/proxy/proxy_resolver_v8.h b/net/proxy/proxy_resolver_v8.h index 6689191..f746ecb 100644 --- a/net/proxy/proxy_resolver_v8.h +++ b/net/proxy/proxy_resolver_v8.h @@ -56,12 +56,13 @@ class ProxyResolverV8 : public ProxyResolver { private: // Context holds the Javascript state for the most recently loaded PAC // script. It corresponds with the data from the last call to - // SetPacScriptByDataInternal(). + // SetPacScript(). class Context; // ProxyResolver implementation: - virtual void SetPacScriptByDataInternal(const std::string& bytes); - + virtual int SetPacScript(const GURL& /*pac_url*/, + const std::string& bytes, + CompletionCallback* /*callback*/); scoped_ptr<Context> context_; scoped_ptr<ProxyResolverJSBindings> js_bindings_; diff --git a/net/proxy/proxy_resolver_v8_unittest.cc b/net/proxy/proxy_resolver_v8_unittest.cc index 20d878b..1aecb12 100644 --- a/net/proxy/proxy_resolver_v8_unittest.cc +++ b/net/proxy/proxy_resolver_v8_unittest.cc @@ -68,7 +68,7 @@ class ProxyResolverV8WithMockBindings : public net::ProxyResolverV8 { } // Initialize with the PAC script data at |filename|. - void SetPacScriptFromDisk(const char* filename) { + int SetPacScriptFromDisk(const char* filename) { FilePath path; PathService::Get(base::DIR_SOURCE_ROOT, &path); path = path.AppendASCII("net"); @@ -81,11 +81,13 @@ class ProxyResolverV8WithMockBindings : public net::ProxyResolverV8 { bool ok = file_util::ReadFileToString(path, &file_contents); // If we can't load the file from disk, something is misconfigured. - LOG_IF(ERROR, !ok) << "Failed to read file: " << path.value(); - ASSERT_TRUE(ok); + if (!ok) { + LOG(ERROR) << "Failed to read file: " << path.value(); + return net::ERR_UNEXPECTED; + } // Load the PAC script into the ProxyResolver. - SetPacScriptByData(file_contents); + return SetPacScriptByData(file_contents, NULL); } }; @@ -97,10 +99,11 @@ const GURL kPacUrl; TEST(ProxyResolverV8Test, Direct) { ProxyResolverV8WithMockBindings resolver; - resolver.SetPacScriptFromDisk("direct.js"); + int result = resolver.SetPacScriptFromDisk("direct.js"); + EXPECT_EQ(net::OK, result); net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); + result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); EXPECT_EQ(net::OK, result); EXPECT_TRUE(proxy_info.is_direct()); @@ -111,10 +114,11 @@ TEST(ProxyResolverV8Test, Direct) { TEST(ProxyResolverV8Test, ReturnEmptyString) { ProxyResolverV8WithMockBindings resolver; - resolver.SetPacScriptFromDisk("return_empty_string.js"); + int result = resolver.SetPacScriptFromDisk("return_empty_string.js"); + EXPECT_EQ(net::OK, result); net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); + result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); EXPECT_EQ(net::OK, result); EXPECT_TRUE(proxy_info.is_direct()); @@ -125,14 +129,15 @@ TEST(ProxyResolverV8Test, ReturnEmptyString) { TEST(ProxyResolverV8Test, Basic) { ProxyResolverV8WithMockBindings resolver; - resolver.SetPacScriptFromDisk("passthrough.js"); + int result = resolver.SetPacScriptFromDisk("passthrough.js"); + EXPECT_EQ(net::OK, result); // The "FindProxyForURL" of this PAC script simply concatenates all of the // arguments into a pseudo-host. The purpose of this test is to verify that // the correct arguments are being passed to FindProxyForURL(). { net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(GURL("http://query.com/path"), + result = resolver.GetProxyForURL(GURL("http://query.com/path"), &proxy_info, NULL, NULL); EXPECT_EQ(net::OK, result); EXPECT_EQ("http.query.com.path.query.com:80", @@ -168,10 +173,11 @@ TEST(ProxyResolverV8Test, BadReturnType) { for (size_t i = 0; i < arraysize(filenames); ++i) { ProxyResolverV8WithMockBindings resolver; - resolver.SetPacScriptFromDisk(filenames[i]); + int result = resolver.SetPacScriptFromDisk(filenames[i]); + EXPECT_EQ(net::OK, result); net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); + result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result); @@ -187,54 +193,46 @@ TEST(ProxyResolverV8Test, BadReturnType) { // Try using a PAC script which defines no "FindProxyForURL" function. TEST(ProxyResolverV8Test, NoEntryPoint) { ProxyResolverV8WithMockBindings resolver; - resolver.SetPacScriptFromDisk("no_entrypoint.js"); + int result = resolver.SetPacScriptFromDisk("no_entrypoint.js"); + EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result); net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); - - EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result); + result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); - MockJSBindings* bindings = resolver.mock_js_bindings(); - EXPECT_EQ(0U, bindings->alerts.size()); - ASSERT_EQ(1U, bindings->errors.size()); - EXPECT_EQ("FindProxyForURL() is undefined.", bindings->errors[0]); - EXPECT_EQ(-1, bindings->errors_line_number[0]); + EXPECT_EQ(net::ERR_FAILED, result); } // Try loading a malformed PAC script. TEST(ProxyResolverV8Test, ParseError) { ProxyResolverV8WithMockBindings resolver; - resolver.SetPacScriptFromDisk("missing_close_brace.js"); + int result = resolver.SetPacScriptFromDisk("missing_close_brace.js"); + EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result); net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); + result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); - EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result); + EXPECT_EQ(net::ERR_FAILED, result); MockJSBindings* bindings = resolver.mock_js_bindings(); EXPECT_EQ(0U, bindings->alerts.size()); - // We get two errors -- one during compilation, and then later when - // trying to run FindProxyForURL(). - ASSERT_EQ(2U, bindings->errors.size()); + // We get one error during compilation. + ASSERT_EQ(1U, bindings->errors.size()); EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input", bindings->errors[0]); EXPECT_EQ(-1, bindings->errors_line_number[0]); - - EXPECT_EQ("FindProxyForURL() is undefined.", bindings->errors[1]); - EXPECT_EQ(-1, bindings->errors_line_number[1]); } // Run a PAC script several times, which has side-effects. TEST(ProxyResolverV8Test, SideEffects) { ProxyResolverV8WithMockBindings resolver; - resolver.SetPacScriptFromDisk("side_effects.js"); + int result = resolver.SetPacScriptFromDisk("side_effects.js"); // The PAC script increments a counter each time we invoke it. for (int i = 0; i < 3; ++i) { net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); + result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); EXPECT_EQ(net::OK, result); EXPECT_EQ(StringPrintf("sideffect_%d:80", i), proxy_info.proxy_server().ToURI()); @@ -242,11 +240,12 @@ TEST(ProxyResolverV8Test, SideEffects) { // Reload the script -- the javascript environment should be reset, hence // the counter starts over. - resolver.SetPacScriptFromDisk("side_effects.js"); + result = resolver.SetPacScriptFromDisk("side_effects.js"); + EXPECT_EQ(net::OK, result); for (int i = 0; i < 3; ++i) { net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); + result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); EXPECT_EQ(net::OK, result); EXPECT_EQ(StringPrintf("sideffect_%d:80", i), proxy_info.proxy_server().ToURI()); @@ -256,10 +255,11 @@ TEST(ProxyResolverV8Test, SideEffects) { // Execute a PAC script which throws an exception in FindProxyForURL. TEST(ProxyResolverV8Test, UnhandledException) { ProxyResolverV8WithMockBindings resolver; - resolver.SetPacScriptFromDisk("unhandled_exception.js"); + int result = resolver.SetPacScriptFromDisk("unhandled_exception.js"); + EXPECT_EQ(net::OK, result); net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); + result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result); @@ -275,10 +275,11 @@ TEST(ProxyResolverV8Test, UnhandledException) { // host/port doesn't check for non-ascii characters. TEST(ProxyResolverV8Test, DISABLED_ReturnUnicode) { ProxyResolverV8WithMockBindings resolver; - resolver.SetPacScriptFromDisk("return_unicode.js"); + int result = resolver.SetPacScriptFromDisk("return_unicode.js"); + EXPECT_EQ(net::OK, result); net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); + result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); // The result from this resolve was unparseable, because it // wasn't ascii. @@ -288,10 +289,11 @@ TEST(ProxyResolverV8Test, DISABLED_ReturnUnicode) { // Test the PAC library functions that we expose in the JS environmnet. TEST(ProxyResolverV8Test, JavascriptLibrary) { ProxyResolverV8WithMockBindings resolver; - resolver.SetPacScriptFromDisk("pac_library_unittest.js"); + int result = resolver.SetPacScriptFromDisk("pac_library_unittest.js"); + EXPECT_EQ(net::OK, result); net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); + result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); // If the javascript side of this unit-test fails, it will throw a javascript // exception. Otherwise it will return "PROXY success:80". @@ -313,21 +315,23 @@ TEST(ProxyResolverV8Test, NoSetPacScript) { EXPECT_EQ(net::ERR_FAILED, result); // Initialize it. - resolver.SetPacScriptFromDisk("direct.js"); + result = resolver.SetPacScriptFromDisk("direct.js"); + EXPECT_EQ(net::OK, result); // Resolve should now succeed. result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); EXPECT_EQ(net::OK, result); // Clear it, by initializing with an empty string. - resolver.SetPacScriptByData(std::string()); + resolver.SetPacScriptByData(std::string(), NULL); // Resolve should fail again now. result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); EXPECT_EQ(net::ERR_FAILED, result); // Load a good script once more. - resolver.SetPacScriptFromDisk("direct.js"); + result = resolver.SetPacScriptFromDisk("direct.js"); + EXPECT_EQ(net::OK, result); result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); EXPECT_EQ(net::OK, result); @@ -338,10 +342,11 @@ TEST(ProxyResolverV8Test, NoSetPacScript) { // Test marshalling/un-marshalling of values between C++/V8. TEST(ProxyResolverV8Test, V8Bindings) { ProxyResolverV8WithMockBindings resolver; - resolver.SetPacScriptFromDisk("bindings.js"); + int result = resolver.SetPacScriptFromDisk("bindings.js"); + EXPECT_EQ(net::OK, result); net::ProxyInfo proxy_info; - int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); + result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL); EXPECT_EQ(net::OK, result); EXPECT_TRUE(proxy_info.is_direct()); diff --git a/net/proxy/proxy_resolver_winhttp.cc b/net/proxy/proxy_resolver_winhttp.cc index d4bca9c5..4c0848d 100644 --- a/net/proxy/proxy_resolver_winhttp.cc +++ b/net/proxy/proxy_resolver_winhttp.cc @@ -70,8 +70,7 @@ int ProxyResolverWinHttp::GetProxyForURL(const GURL& query_url, options.fAutoLogonIfChallenged = FALSE; options.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; std::wstring pac_url_wide = ASCIIToWide(pac_url_.spec()); - options.lpszAutoConfigUrl = - pac_url_wide.empty() ? L"http://wpad/wpad.dat" : pac_url_wide.c_str(); + options.lpszAutoConfigUrl = pac_url_wide.c_str(); WINHTTP_PROXY_INFO info = {0}; DCHECK(session_handle_); @@ -140,8 +139,11 @@ void ProxyResolverWinHttp::CancelRequest(RequestHandle request) { NOTREACHED(); } -void ProxyResolverWinHttp::SetPacScriptByUrlInternal(const GURL& pac_url) { - pac_url_ = pac_url; +int ProxyResolverWinHttp::SetPacScript(const GURL& pac_url, + const std::string& /*pac_bytes*/, + CompletionCallback* /*callback*/) { + pac_url_ = pac_url.is_valid() ? pac_url : GURL("http://wpad/wpad.dat"); + return OK; } bool ProxyResolverWinHttp::OpenWinHttpSession() { diff --git a/net/proxy/proxy_resolver_winhttp.h b/net/proxy/proxy_resolver_winhttp.h index 4932ba6..1d9a7e8 100644 --- a/net/proxy/proxy_resolver_winhttp.h +++ b/net/proxy/proxy_resolver_winhttp.h @@ -30,8 +30,9 @@ class ProxyResolverWinHttp : public ProxyResolver { private: // ProxyResolver implementation: - virtual void SetPacScriptByUrlInternal(const GURL& pac_url); - + virtual int SetPacScript(const GURL& pac_url, + const std::string& /*pac_bytes*/, + CompletionCallback* /*callback*/); bool OpenWinHttpSession(); void CloseWinHttpSession(); diff --git a/net/proxy/proxy_script_fetcher.cc b/net/proxy/proxy_script_fetcher.cc index eecff19..d93c5c2 100644 --- a/net/proxy/proxy_script_fetcher.cc +++ b/net/proxy/proxy_script_fetcher.cc @@ -57,8 +57,8 @@ class ProxyScriptFetcherImpl : public ProxyScriptFetcher, // ProxyScriptFetcher methods: - virtual void Fetch(const GURL& url, std::string* bytes, - CompletionCallback* callback); + virtual int Fetch(const GURL& url, std::string* bytes, + CompletionCallback* callback); virtual void Cancel(); // URLRequest::Delegate methods: @@ -137,9 +137,9 @@ ProxyScriptFetcherImpl::~ProxyScriptFetcherImpl() { // ensure that the delegate (this) is not called again. } -void ProxyScriptFetcherImpl::Fetch(const GURL& url, - std::string* bytes, - CompletionCallback* callback) { +int ProxyScriptFetcherImpl::Fetch(const GURL& url, + std::string* bytes, + CompletionCallback* callback) { // It is invalid to call Fetch() while a request is already in progress. DCHECK(!cur_request_.get()); @@ -171,6 +171,7 @@ void ProxyScriptFetcherImpl::Fetch(const GURL& url, // Start the request. cur_request_->Start(); + return ERR_IO_PENDING; } void ProxyScriptFetcherImpl::Cancel() { diff --git a/net/proxy/proxy_script_fetcher.h b/net/proxy/proxy_script_fetcher.h index fddbd7b..e88ab6b 100644 --- a/net/proxy/proxy_script_fetcher.h +++ b/net/proxy/proxy_script_fetcher.h @@ -9,6 +9,8 @@ #ifndef NET_PROXY_PROXY_SCRIPT_FETCHER_H_ #define NET_PROXY_PROXY_SCRIPT_FETCHER_H_ +#include <string> + #include "net/base/completion_callback.h" #include "testing/gtest/include/gtest/gtest_prod.h" @@ -37,8 +39,8 @@ class ProxyScriptFetcher { // deleting |this|), then no callback is invoked. // // Only one fetch is allowed to be outstanding at a time. - virtual void Fetch(const GURL& url, std::string* bytes, - CompletionCallback* callback) = 0; + virtual int Fetch(const GURL& url, std::string* bytes, + CompletionCallback* callback) = 0; // Aborts the in-progress fetch (if any). virtual void Cancel() = 0; diff --git a/net/proxy/proxy_script_fetcher_unittest.cc b/net/proxy/proxy_script_fetcher_unittest.cc index 8a8030b..6dd05dc 100644 --- a/net/proxy/proxy_script_fetcher_unittest.cc +++ b/net/proxy/proxy_script_fetcher_unittest.cc @@ -62,7 +62,8 @@ class SynchFetcherThreadHelper { // Starts fetching the script at |url|. Upon completion |event_| will be // signalled, and the bytes read will have been written to |fetch_result_|. void Start(const GURL& url) { - fetcher_->Fetch(url, &fetch_result_->bytes, &callback_); + int rv = fetcher_->Fetch(url, &fetch_result_->bytes, &callback_); + EXPECT_EQ(net::ERR_IO_PENDING, rv); } void OnFetchCompletion(int result) { diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc index ab18956..327e741 100644 --- a/net/proxy/proxy_service.cc +++ b/net/proxy/proxy_service.cc @@ -13,6 +13,7 @@ #include "googleurl/src/gurl.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" +#include "net/proxy/init_proxy_resolver.h" #include "net/proxy/proxy_config_service_fixed.h" #include "net/proxy/proxy_script_fetcher.h" #if defined(OS_WIN) @@ -61,7 +62,11 @@ class ProxyResolverNull : public ProxyResolver { } private: - virtual void SetPacScriptByUrlInternal(const GURL& pac_url) {} + virtual int SetPacScript(const GURL& /*pac_url*/, + const std::string& /*pac_bytes*/, + CompletionCallback* /*callback*/) { + return ERR_NOT_IMPLEMENTED; + } }; // ProxyService::PacRequest --------------------------------------------------- @@ -100,18 +105,25 @@ class ProxyService::PacRequest return !!resolve_job_; } - void StartAndComplete() { - int rv = Start(); + void StartAndCompleteCheckingForSynchronous() { + int rv = service_->TryToCompleteSynchronously(url_, results_); + if (rv == ERR_IO_PENDING) + rv = Start(); if (rv != ERR_IO_PENDING) QueryComplete(rv); } - void Cancel() { + void CancelResolveJob() { // The request may already be running in the resolver. if (is_started()) { resolver()->CancelRequest(resolve_job_); resolve_job_ = NULL; } + DCHECK(!is_started()); + } + + void Cancel() { + CancelResolveJob(); // Mark as cancelled, to prevent accessing this again later. service_ = NULL; @@ -177,11 +189,8 @@ ProxyService::ProxyService(ProxyConfigService* config_service, resolver_(resolver), next_config_id_(1), config_is_bad_(false), - ALLOW_THIS_IN_INITIALIZER_LIST(proxy_script_fetcher_callback_( - this, &ProxyService::OnScriptFetchCompletion)), - fetched_pac_config_id_(ProxyConfig::INVALID_ID), - fetched_pac_error_(OK), - in_progress_fetch_config_id_(ProxyConfig::INVALID_ID) { + ALLOW_THIS_IN_INITIALIZER_LIST(init_proxy_resolver_callback_( + this, &ProxyService::OnInitProxyResolverComplete)) { } // static @@ -254,7 +263,7 @@ int ProxyService::ResolveProxy(const GURL& raw_url, ProxyInfo* result, scoped_refptr<PacRequest> req = new PacRequest(this, url, result, callback); - bool resolver_is_ready = PrepareResolverForRequests(); + bool resolver_is_ready = !IsInitializingProxyResolver(); if (resolver_is_ready) { // Start the resolve request. @@ -290,22 +299,15 @@ int ProxyService::TryToCompleteSynchronously(const GURL& url, // Remember that we are trying to use the current proxy configuration. result->config_was_tried_ = true; + if (config_.MayRequirePACResolver()) { + // Need to go through ProxyResolver for this. + return ERR_IO_PENDING; + } + if (!config_.proxy_rules.empty()) { ApplyProxyRules(url, config_.proxy_rules, result); return OK; } - - if (config_.pac_url.is_valid() || config_.auto_detect) { - // If we failed to download the PAC script, return the network error - // from the failed download. This is only going to happen for the first - // request after the failed download -- after that |config_is_bad_| will - // be set to true, so we short-cuircuit sooner. - if (fetched_pac_error_ != OK && !IsFetchingPacScript()) { - DidCompletePacRequest(fetched_pac_config_id_, fetched_pac_error_); - return fetched_pac_error_; - } - return ERR_IO_PENDING; - } } // otherwise, we have no proxy config @@ -354,7 +356,17 @@ ProxyService::~ProxyService() { } } +void ProxyService::SuspendAllPendingRequests() { + for (PendingRequests::iterator it = pending_requests_.begin(); + it != pending_requests_.end(); + ++it) { + it->get()->CancelResolveJob(); + } +} + void ProxyService::ResumeAllPendingRequests() { + DCHECK(!IsInitializingProxyResolver()); + // Make a copy in case |this| is deleted during the synchronous completion // of one of the requests. If |this| is deleted then all of the PacRequest // instances will be Cancel()-ed. @@ -364,57 +376,27 @@ void ProxyService::ResumeAllPendingRequests() { it != pending_copy.end(); ++it) { PacRequest* req = it->get(); - if (!req->is_started() && !req->was_cancelled()) - req->StartAndComplete(); + if (!req->is_started() && !req->was_cancelled()) { + // Note that we re-check for synchronous completion, in case we are + // no longer using a ProxyResolver (can happen if we fell-back to manual). + req->StartAndCompleteCheckingForSynchronous(); + } } } -bool ProxyService::PrepareResolverForRequests() { - // While the PAC script is being downloaded, block requests. - if (IsFetchingPacScript()) - return false; - - // Check if a new PAC script needs to be downloaded. - DCHECK(config_.id() != ProxyConfig::INVALID_ID); - if (resolver_->expects_pac_bytes() && - config_.id() != fetched_pac_config_id_) { - // For auto-detect we use the well known WPAD url. - GURL pac_url = config_.auto_detect ? - GURL("http://wpad/wpad.dat") : config_.pac_url; - - in_progress_fetch_config_id_ = config_.id(); - - LOG(INFO) << "Starting fetch of PAC script " << pac_url - << " for config_id=" << in_progress_fetch_config_id_; - - proxy_script_fetcher_->Fetch( - pac_url, &in_progress_fetch_bytes_, &proxy_script_fetcher_callback_); - return false; +void ProxyService::OnInitProxyResolverComplete(int result) { + DCHECK(init_proxy_resolver_.get()); + DCHECK(config_.MayRequirePACResolver()); + init_proxy_resolver_.reset(); + + if (result != OK) { + LOG(INFO) << "Failed configuring with PAC script, falling-back to manual " + "proxy servers."; + config_.auto_detect = false; + config_.pac_url = GURL(); + DCHECK(!config_.MayRequirePACResolver()); } - // We are good to go. - return true; -} - -void ProxyService::OnScriptFetchCompletion(int result) { - DCHECK(IsFetchingPacScript()); - DCHECK(resolver_->expects_pac_bytes()); - - LOG(INFO) << "Completed PAC script fetch for config_id=" - << in_progress_fetch_config_id_ - << " with error " << ErrorToString(result) - << ". Fetched a total of " << in_progress_fetch_bytes_.size() - << " bytes"; - - // Notify the ProxyResolver of the new script data (will be empty string if - // result != OK). - resolver_->SetPacScriptByData(in_progress_fetch_bytes_); - - fetched_pac_config_id_ = in_progress_fetch_config_id_; - fetched_pac_error_ = result; - in_progress_fetch_config_id_ = ProxyConfig::INVALID_ID; - in_progress_fetch_bytes_.clear(); - // Resume any requests which we had to defer until the PAC script was // downloaded. ResumeAllPendingRequests(); @@ -455,6 +437,8 @@ int ProxyService::ReconsiderProxyAfterError(const GURL& url, if (!was_direct && result->Fallback(&proxy_retry_info_)) return OK; + // TODO(eroman): Hmm, this doesn't seem right. For starters just because + // auto_detect is true doesn't mean we are actually using it. if (!config_.auto_detect && !config_.proxy_rules.empty()) { // If auto detect is on, then we should try a DIRECT connection // as the attempt to reach the proxy failed. @@ -587,13 +571,24 @@ void ProxyService::SetConfig(const ProxyConfig& config) { config_is_bad_ = false; proxy_retry_info_.clear(); - // Tell the resolver to use the new PAC URL (for resolvers that fetch the - // PAC script internally). - // TODO(eroman): this isn't quite right. See http://crbug.com/9985 - if ((config_.pac_url.is_valid() || config_.auto_detect) && - !resolver_->expects_pac_bytes()) { - const GURL pac_url = config_.auto_detect ? GURL() : config_.pac_url; - resolver_->SetPacScriptByUrl(pac_url); + // Cancel any PAC fetching / ProxyResolver::SetPacScript() which was + // in progress for the previous configuration. + init_proxy_resolver_.reset(); + + // Start downloading + testing the PAC scripts for this new configuration. + if (config_.MayRequirePACResolver()) { + // Since InitProxyResolver will be playing around with the proxy resolver + // as it tests the parsing of various PAC scripts, make sure there is + // nothing in-flight in |resolver_|. These paused requests are resumed by + // OnInitProxyResolverComplete(). + SuspendAllPendingRequests(); + + init_proxy_resolver_.reset( + new InitProxyResolver(resolver_.get(), proxy_script_fetcher_.get())); + int rv = init_proxy_resolver_->Init( + config_, &init_proxy_resolver_callback_); + if (rv != ERR_IO_PENDING) + OnInitProxyResolverComplete(rv); } } diff --git a/net/proxy/proxy_service.h b/net/proxy/proxy_service.h index f74b203..a7546f1 100644 --- a/net/proxy/proxy_service.h +++ b/net/proxy/proxy_service.h @@ -24,9 +24,10 @@ class URLRequestContext; namespace net { -class ProxyScriptFetcher; +class InitProxyResolver; class ProxyConfigService; class ProxyResolver; +class ProxyScriptFetcher; // This class can be used to resolve the proxy server to use when loading a // HTTP(S) URL. It uses the given ProxyResolver to handle the actual proxy @@ -165,15 +166,16 @@ class ProxyService { // Tries to update the configuration if it hasn't been checked in a while. void UpdateConfigIfOld(); - // Returns true if this ProxyService is downloading a PAC script on behalf - // of ProxyResolverWithoutFetch. Resolve requests will be frozen until - // the fetch has completed. - bool IsFetchingPacScript() const { - return in_progress_fetch_config_id_ != ProxyConfig::INVALID_ID; + // Returns true if the proxy resolver is being initialized for PAC + // (downloading PAC script(s) + testing). + // Resolve requests will be frozen until the initialization has completed. + bool IsInitializingProxyResolver() const { + return init_proxy_resolver_.get() != NULL; } - // Callback for when the PAC script has finished downloading. - void OnScriptFetchCompletion(int result); + // Callback for when the proxy resolver has been initialized with a + // PAC script. + void OnInitProxyResolverComplete(int result); // Returns ERR_IO_PENDING if the request cannot be completed synchronously. // Otherwise it fills |result| with the proxy information for |url|. @@ -185,6 +187,10 @@ class ProxyService { const ProxyConfig::ProxyRules& rules, ProxyInfo* result); + // Cancel all of the requests sent to the ProxyResolver. These will be + // restarted when calling ResumeAllPendingRequests(). + void SuspendAllPendingRequests(); + // Sends all the unstarted pending requests off to the resolver. void ResumeAllPendingRequests(); @@ -194,12 +200,6 @@ class ProxyService { // Removes |req| from the list of pending requests. void RemovePendingRequest(PacRequest* req); - // Returns true if the resolver is all set-up and ready to accept requests. - // Returns false if requests are blocked (because the PAC script is being - // downloaded). May have the side-effect of starting the PAC script - // download. - bool PrepareResolverForRequests(); - // Called to indicate that a PacRequest completed. The |config_id| parameter // indicates the proxy configuration that was queried. |result_code| is OK // if the PAC file could be downloaded and executed. Otherwise, it is an @@ -215,6 +215,9 @@ class ProxyService { // heuristic). static bool IsLocalName(const GURL& url); + // Helper to download the PAC script (wpad + custom) and apply fallback rules. + scoped_ptr<InitProxyResolver> init_proxy_resolver_; + scoped_ptr<ProxyConfigService> config_service_; scoped_ptr<ProxyResolver> resolver_; @@ -242,23 +245,8 @@ class ProxyService { // external PAC script fetching. scoped_ptr<ProxyScriptFetcher> proxy_script_fetcher_; - // Callback for when |proxy_script_fetcher_| is done. - CompletionCallbackImpl<ProxyService> proxy_script_fetcher_callback_; - - // The ID of the configuration for which we last downloaded a PAC script. - // If no PAC script has been fetched yet, will be ProxyConfig::INVALID_ID. - ProxyConfig::ID fetched_pac_config_id_; - - // The error corresponding with |fetched_pac_config_id_|, or OK. - int fetched_pac_error_; - - // The ID of the configuration for which we are currently downloading the - // PAC script. If no fetch is in progress, will be ProxyConfig::INVALID_ID. - ProxyConfig::ID in_progress_fetch_config_id_; - - // The results of the current in progress fetch, or empty string. - std::string in_progress_fetch_bytes_; - + // Callback for when |init_proxy_resolver_| is done. + CompletionCallbackImpl<ProxyService> init_proxy_resolver_callback_; DISALLOW_COPY_AND_ASSIGN(ProxyService); }; diff --git a/net/proxy/proxy_service_unittest.cc b/net/proxy/proxy_service_unittest.cc index b6b62b6..0dac40b 100644 --- a/net/proxy/proxy_service_unittest.cc +++ b/net/proxy/proxy_service_unittest.cc @@ -15,6 +15,8 @@ #include "net/proxy/proxy_service.h" #include "testing/gtest/include/gtest/gtest.h" +// TODO(eroman): Write a test which exercises +// ProxyService::SuspendAllPendingRequests(). namespace net { namespace { @@ -72,6 +74,39 @@ class MockAsyncProxyResolverBase : public ProxyResolver { MessageLoop* origin_loop_; }; + class SetPacScriptRequest { + public: + SetPacScriptRequest(MockAsyncProxyResolverBase* resolver, + const GURL& pac_url, + const std::string& pac_bytes, + CompletionCallback* callback) + : resolver_(resolver), + pac_url_(pac_url), + pac_bytes_(pac_bytes), + callback_(callback), + origin_loop_(MessageLoop::current()) { + } + + const GURL& pac_url() const { return pac_url_; } + const std::string& pac_bytes() const { return pac_bytes_; } + + void CompleteNow(int rv) { + CompletionCallback* callback = callback_; + + // Will delete |this|. + resolver_->RemovePendingSetPacScriptRequest(this); + + callback->Run(rv); + } + + private: + MockAsyncProxyResolverBase* resolver_; + const GURL pac_url_; + const std::string pac_bytes_; + CompletionCallback* callback_; + MessageLoop* origin_loop_; + }; + typedef std::vector<scoped_refptr<Request> > RequestsList; // ProxyResolver implementation: @@ -95,12 +130,14 @@ class MockAsyncProxyResolverBase : public ProxyResolver { RemovePendingRequest(request); } - virtual void SetPacScriptByUrlInternal(const GURL& pac_url) { - pac_url_ = pac_url; - } - - virtual void SetPacScriptByDataInternal(const std::string& bytes) { - pac_bytes_ = bytes; + virtual int SetPacScript(const GURL& pac_url, + const std::string& pac_bytes, + CompletionCallback* callback) { + EXPECT_EQ(NULL, pending_set_pac_script_request_.get()); + pending_set_pac_script_request_.reset( + new SetPacScriptRequest(this, pac_url, pac_bytes, callback)); + // Finished when user calls SetPacScriptRequest::CompleteNow(). + return ERR_IO_PENDING; } const RequestsList& pending_requests() const { @@ -111,12 +148,8 @@ class MockAsyncProxyResolverBase : public ProxyResolver { return cancelled_requests_; } - const GURL& pac_url() const { - return pac_url_; - } - - const std::string& pac_bytes() const { - return pac_bytes_; + SetPacScriptRequest* pending_set_pac_script_request() const { + return pending_set_pac_script_request_.get(); } void RemovePendingRequest(Request* request) { @@ -126,6 +159,11 @@ class MockAsyncProxyResolverBase : public ProxyResolver { pending_requests_.erase(it); } + void RemovePendingSetPacScriptRequest(SetPacScriptRequest* request) { + EXPECT_EQ(request, pending_set_pac_script_request()); + pending_set_pac_script_request_.reset(); + } + protected: explicit MockAsyncProxyResolverBase(bool expects_pac_bytes) : ProxyResolver(expects_pac_bytes) {} @@ -133,8 +171,7 @@ class MockAsyncProxyResolverBase : public ProxyResolver { private: RequestsList pending_requests_; RequestsList cancelled_requests_; - GURL pac_url_; - std::string pac_bytes_; + scoped_ptr<SetPacScriptRequest> pending_set_pac_script_request_; }; class MockAsyncProxyResolver : public MockAsyncProxyResolverBase { @@ -160,21 +197,24 @@ class MockProxyScriptFetcher : public ProxyScriptFetcher { } // ProxyScriptFetcher implementation. - virtual void Fetch(const GURL& url, - std::string* bytes, - CompletionCallback* callback) { + virtual int Fetch(const GURL& url, + std::string* bytes, + CompletionCallback* callback) { DCHECK(!has_pending_request()); // Save the caller's information, and have them wait. pending_request_url_ = url; pending_request_callback_ = callback; pending_request_bytes_ = bytes; + return ERR_IO_PENDING; } void NotifyFetchCompletion(int result, const std::string& bytes) { DCHECK(has_pending_request()); *pending_request_bytes_ = bytes; - pending_request_callback_->Run(result); + CompletionCallback* callback = pending_request_callback_; + pending_request_callback_ = NULL; + callback->Run(result); } virtual void Cancel() {} @@ -223,7 +263,10 @@ TEST(ProxyServiceTest, PAC) { int rv = service.ResolveProxy(url, &info, &callback, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(GURL("http://foopy/proxy.pac"), resolver->pac_url()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(1u, resolver->pending_requests().size()); EXPECT_EQ(url, resolver->pending_requests()[0]->url()); @@ -253,7 +296,10 @@ TEST(ProxyServiceTest, PAC_NoIdentityOrHash) { int rv = service.ResolveProxy(url, &info, &callback, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(GURL("http://foopy/proxy.pac"), resolver->pac_url()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(1u, resolver->pending_requests().size()); // The URL should have been simplified, stripping the username/password/hash. EXPECT_EQ(GURL("http://www.google.com/?ref"), @@ -277,7 +323,10 @@ TEST(ProxyServiceTest, PAC_FailoverToDirect) { int rv = service.ResolveProxy(url, &info, &callback1, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(GURL("http://foopy/proxy.pac"), resolver->pac_url()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(1u, resolver->pending_requests().size()); EXPECT_EQ(url, resolver->pending_requests()[0]->url()); @@ -300,6 +349,7 @@ TEST(ProxyServiceTest, ProxyResolverFails) { // Test what happens when the ProxyResolver fails (this could represent // a failure to download the PAC script in the case of ProxyResolvers which // do the fetch internally.) + // TODO(eroman): change this comment. MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); @@ -315,7 +365,10 @@ TEST(ProxyServiceTest, ProxyResolverFails) { int rv = service.ResolveProxy(url, &info, &callback1, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(GURL("http://foopy/proxy.pac"), resolver->pac_url()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(1u, resolver->pending_requests().size()); EXPECT_EQ(url, resolver->pending_requests()[0]->url()); @@ -369,7 +422,10 @@ TEST(ProxyServiceTest, ProxyFallback) { int rv = service.ResolveProxy(url, &info, &callback1, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(GURL("http://foopy/proxy.pac"), resolver->pac_url()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(1u, resolver->pending_requests().size()); EXPECT_EQ(url, resolver->pending_requests()[0]->url()); @@ -446,7 +502,10 @@ TEST(ProxyServiceTest, ProxyFallback_NewSettings) { int rv = service.ResolveProxy(url, &info, &callback1, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(GURL("http://foopy/proxy.pac"), resolver->pac_url()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(1u, resolver->pending_requests().size()); EXPECT_EQ(url, resolver->pending_requests()[0]->url()); @@ -468,7 +527,10 @@ TEST(ProxyServiceTest, ProxyFallback_NewSettings) { rv = service.ReconsiderProxyAfterError(url, &info, &callback2, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(GURL("http://foopy-new/proxy.pac"), resolver->pac_url()); + EXPECT_EQ(GURL("http://foopy-new/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(1u, resolver->pending_requests().size()); EXPECT_EQ(url, resolver->pending_requests()[0]->url()); @@ -495,7 +557,10 @@ TEST(ProxyServiceTest, ProxyFallback_NewSettings) { rv = service.ReconsiderProxyAfterError(url, &info, &callback4, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(GURL("http://foopy-new2/proxy.pac"), resolver->pac_url()); + EXPECT_EQ(GURL("http://foopy-new2/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(1u, resolver->pending_requests().size()); EXPECT_EQ(url, resolver->pending_requests()[0]->url()); @@ -525,7 +590,9 @@ TEST(ProxyServiceTest, ProxyFallback_BadConfig) { int rv = service.ResolveProxy(url, &info, &callback1, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(GURL("http://foopy/proxy.pac"), resolver->pac_url()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); ASSERT_EQ(1u, resolver->pending_requests().size()); EXPECT_EQ(url, resolver->pending_requests()[0]->url()); @@ -702,7 +769,7 @@ TEST(ProxyServiceTest, ProxyBypassListWithPorts) { config.proxy_bypass.push_back("*.example.com:99"); { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); { GURL test_url("http://www.example.com:99"); TestCompletionCallback callback; @@ -730,7 +797,7 @@ TEST(ProxyServiceTest, ProxyBypassListWithPorts) { config.proxy_bypass.push_back("*.example.com:80"); { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); GURL test_url("http://www.example.com"); TestCompletionCallback callback; int rv = service.ResolveProxy(test_url, &info, &callback, NULL); @@ -742,7 +809,7 @@ TEST(ProxyServiceTest, ProxyBypassListWithPorts) { config.proxy_bypass.push_back("*.example.com"); { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); GURL test_url("http://www.example.com:99"); TestCompletionCallback callback; int rv = service.ResolveProxy(test_url, &info, &callback, NULL); @@ -755,7 +822,7 @@ TEST(ProxyServiceTest, ProxyBypassListWithPorts) { config.proxy_bypass.push_back("[3ffe:2a00:100:7031::1]:99"); { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); { GURL test_url("http://[3ffe:2a00:100:7031::1]:99/"); TestCompletionCallback callback; @@ -779,7 +846,7 @@ TEST(ProxyServiceTest, ProxyBypassListWithPorts) { config.proxy_bypass.push_back("[3ffe:2a00:100:7031::1]"); { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); { GURL test_url("http://[3ffe:2a00:100:7031::1]:99/"); TestCompletionCallback callback; @@ -803,7 +870,7 @@ TEST(ProxyServiceTest, PerProtocolProxyTests) { config.auto_detect = false; { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); GURL test_url("http://www.msn.com"); ProxyInfo info; TestCompletionCallback callback; @@ -814,7 +881,7 @@ TEST(ProxyServiceTest, PerProtocolProxyTests) { } { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); GURL test_url("ftp://ftp.google.com"); ProxyInfo info; TestCompletionCallback callback; @@ -825,7 +892,7 @@ TEST(ProxyServiceTest, PerProtocolProxyTests) { } { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); GURL test_url("https://webbranch.techcu.com"); ProxyInfo info; TestCompletionCallback callback; @@ -837,7 +904,7 @@ TEST(ProxyServiceTest, PerProtocolProxyTests) { { config.proxy_rules.ParseFromString("foopy1:8080"); ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); GURL test_url("http://www.microsoft.com"); ProxyInfo info; TestCompletionCallback callback; @@ -859,7 +926,7 @@ TEST(ProxyServiceTest, DefaultProxyFallbackToSOCKS) { { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); GURL test_url("http://www.msn.com"); ProxyInfo info; TestCompletionCallback callback; @@ -870,7 +937,7 @@ TEST(ProxyServiceTest, DefaultProxyFallbackToSOCKS) { } { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); GURL test_url("ftp://ftp.google.com"); ProxyInfo info; TestCompletionCallback callback; @@ -881,7 +948,7 @@ TEST(ProxyServiceTest, DefaultProxyFallbackToSOCKS) { } { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); GURL test_url("https://webbranch.techcu.com"); ProxyInfo info; TestCompletionCallback callback; @@ -892,7 +959,7 @@ TEST(ProxyServiceTest, DefaultProxyFallbackToSOCKS) { } { ProxyService service(new MockProxyConfigService(config), - new MockAsyncProxyResolver); + new MockAsyncProxyResolver); GURL test_url("unknown://www.microsoft.com"); ProxyInfo info; TestCompletionCallback callback; @@ -919,6 +986,16 @@ TEST(ProxyServiceTest, CancelInProgressRequest) { int rv = service.ResolveProxy( GURL("http://request1"), &info1, &callback1, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); + + // Nothing has been sent to the proxy resolver yet, since the proxy + // resolver has not been configured yet. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // Successfully initialize the PAC script. + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(1u, resolver->pending_requests().size()); EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); @@ -1011,9 +1088,11 @@ TEST(ProxyServiceTest, InitialPACScriptDownload) { // PAC script download completion. fetcher->NotifyFetchCompletion(OK, "pac-v1"); - // Now that the PAC script is downloaded, everything should have been sent - // over to the proxy resolver. - EXPECT_EQ("pac-v1", resolver->pac_bytes()); + // Now that the PAC script is downloaded, it will have been sent to the proxy + // resolver. + EXPECT_EQ("pac-v1", resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(3u, resolver->pending_requests().size()); EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url()); @@ -1092,9 +1171,11 @@ TEST(ProxyServiceTest, CancelWhilePACFetching) { // PAC script download completion. fetcher->NotifyFetchCompletion(OK, "pac-v1"); - // Now that the PAC script is downloaded, everything should have been sent - // over to the proxy resolver. - EXPECT_EQ("pac-v1", resolver->pac_bytes()); + // Now that the PAC script is downloaded, it will have been sent to the + // proxy resolver. + EXPECT_EQ("pac-v1", resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(1u, resolver->pending_requests().size()); EXPECT_EQ(GURL("http://request3"), resolver->pending_requests()[0]->url()); @@ -1111,12 +1192,280 @@ TEST(ProxyServiceTest, CancelWhilePACFetching) { EXPECT_FALSE(callback2.have_result()); // Cancelled. } +// Test that if auto-detect fails, we fall-back to the custom pac. +TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac) { + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://foopy/proxy.pac"); + config.proxy_rules.ParseFromString("http=foopy:80"); // Won't be used. + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + ProxyService service(config_service, resolver); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetcher(fetcher); + + // Start 2 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyService::PacRequest* request2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, &callback2, &request2); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Check that nothing has been sent to the proxy resolver yet. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // It should be trying to auto-detect first -- FAIL the autodetect during + // the script download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(ERR_FAILED, ""); + + // Next it should be trying the custom PAC url. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, "custom-pac-script"); + + EXPECT_EQ("custom-pac-script", + resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + // Now finally, the pending requests should have been sent to the resolver + // (which was initialized with custom PAC script). + + ASSERT_EQ(2u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); + EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url()); + + // Complete the pending requests. + resolver->pending_requests()[1]->results()->UseNamedProxy("request2:80"); + resolver->pending_requests()[1]->CompleteNow(OK); + resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Verify that requests ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// This is the same test as FallbackFromAutodetectToCustomPac, except +// the auto-detect script fails parsing rather than downloading. +TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac2) { + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://foopy/proxy.pac"); + config.proxy_rules.ParseFromString("http=foopy:80"); // Won't be used. + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + ProxyService service(config_service, resolver); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetcher(fetcher); + + // Start 2 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyService::PacRequest* request2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, &callback2, &request2); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Check that nothing has been sent to the proxy resolver yet. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // It should be trying to auto-detect first -- succeed the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, "invalid-script-contents"); + + // Simulate a parse error. + EXPECT_EQ("invalid-script-contents", + resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow( + ERR_PAC_SCRIPT_FAILED); + + // Next it should be trying the custom PAC url. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, "custom-pac-script"); + + EXPECT_EQ("custom-pac-script", + resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + // Now finally, the pending requests should have been sent to the resolver + // (which was initialized with custom PAC script). + + ASSERT_EQ(2u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); + EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url()); + + // Complete the pending requests. + resolver->pending_requests()[1]->results()->UseNamedProxy("request2:80"); + resolver->pending_requests()[1]->CompleteNow(OK); + resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Verify that requests ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// Test that if all of auto-detect, a custom PAC script, and manual settings +// are given, then we will try them in that order. +TEST(ProxyServiceTest, FallbackFromAutodetectToCustomToManual) { + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://foopy/proxy.pac"); + config.proxy_rules.ParseFromString("http=foopy:80"); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + ProxyService service(config_service, resolver); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetcher(fetcher); + + // Start 2 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyService::PacRequest* request2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, &callback2, &request2); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Check that nothing has been sent to the proxy resolver yet. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // It should be trying to auto-detect first -- fail the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(ERR_FAILED, ""); + + // Next it should be trying the custom PAC url -- fail the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(ERR_FAILED, ""); + + // Since we never managed to initialize a ProxyResolver, nothing should have + // been sent to it. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // Verify that requests ran as expected -- they should have fallen back to + // the manual proxy configuration for HTTP urls. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("foopy:80", info1.proxy_server().ToURI()); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("foopy:80", info2.proxy_server().ToURI()); +} + +// Test that the bypass rules are NOT applied when using autodetect. +TEST(ProxyServiceTest, BypassDoesntApplyToPac) { + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://foopy/proxy.pac"); + config.proxy_rules.ParseFromString("http=foopy:80"); // Not used. + config.proxy_bypass.push_back("www.google.com"); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + ProxyService service(config_service, resolver); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetcher(fetcher); + + // Start 1 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://www.google.com"), &info1, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Check that nothing has been sent to the proxy resolver yet. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // It should be trying to auto-detect first -- succeed the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, "auto-detect"); + + EXPECT_EQ("auto-detect", + resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://www.google.com"), + resolver->pending_requests()[0]->url()); + + // Complete the pending request. + resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Verify that request ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // Start another request, it should pickup the bypass item. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy( + GURL("http://www.google.com"), &info2, &callback2, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://www.google.com"), + resolver->pending_requests()[0]->url()); + + // Complete the pending request. + resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + TEST(ProxyServiceTest, ResetProxyConfigService) { ProxyConfig config1; config1.proxy_rules.ParseFromString("foopy1:8080"); config1.auto_detect = false; ProxyService service(new MockProxyConfigService(config1), - new MockAsyncProxyResolverExpectsBytes); + new MockAsyncProxyResolverExpectsBytes); ProxyInfo info; TestCompletionCallback callback1; @@ -1136,7 +1485,7 @@ TEST(ProxyServiceTest, ResetProxyConfigService) { } TEST(ProxyServiceTest, IsLocalName) { - const struct { + const struct { const char* url; bool expected_is_local; } tests[] = { diff --git a/net/proxy/single_threaded_proxy_resolver.cc b/net/proxy/single_threaded_proxy_resolver.cc index e1e621de..02658a6 100644 --- a/net/proxy/single_threaded_proxy_resolver.cc +++ b/net/proxy/single_threaded_proxy_resolver.cc @@ -10,28 +10,77 @@ namespace net { -// Runs on the worker thread to call ProxyResolver::SetPacScriptByUrl or -// ProxyResolver::SetPacScriptByData. -// TODO(eroman): the lifetime of this task is ill-defined; |resolver_| must -// be valid when the task eventually is run. Make this task cancellable. -class SetPacScriptTask : public Task { +// SingleThreadedProxyResolver::SetPacScriptTask ------------------------------ + +// Runs on the worker thread to call ProxyResolver::SetPacScript. +class SingleThreadedProxyResolver::SetPacScriptTask + : public base::RefCountedThreadSafe< + SingleThreadedProxyResolver::SetPacScriptTask> { public: - SetPacScriptTask(ProxyResolver* resolver, + SetPacScriptTask(SingleThreadedProxyResolver* coordinator, const GURL& pac_url, - const std::string& bytes) - : resolver_(resolver), pac_url_(pac_url), bytes_(bytes) {} - - virtual void Run() { - if (resolver_->expects_pac_bytes()) - resolver_->SetPacScriptByData(bytes_); - else - resolver_->SetPacScriptByUrl(pac_url_); + const std::string& pac_bytes, + CompletionCallback* callback) + : coordinator_(coordinator), + callback_(callback), + pac_bytes_(pac_bytes), + pac_url_(pac_url), + origin_loop_(MessageLoop::current()) { + DCHECK(callback); + } + + // Start the SetPacScript request on the worker thread. + void Start() { + // TODO(eroman): Are these manual AddRef / Release necessary? + AddRef(); // balanced in RequestComplete + + coordinator_->thread()->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &SetPacScriptTask::DoRequest, + coordinator_->resolver_.get())); } + void Cancel() { + // Clear these to inform RequestComplete that it should not try to + // access them. + coordinator_ = NULL; + callback_ = NULL; + } + + // Returns true if Cancel() has been called. + bool was_cancelled() const { return callback_ == NULL; } + private: - ProxyResolver* resolver_; + // Runs on the worker thread. + void DoRequest(ProxyResolver* resolver) { + int rv = resolver->expects_pac_bytes() ? + resolver->SetPacScriptByData(pac_bytes_, NULL) : + resolver->SetPacScriptByUrl(pac_url_, NULL); + + DCHECK_NE(rv, ERR_IO_PENDING); + origin_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &SetPacScriptTask::RequestComplete, rv)); + } + + // Runs the completion callback on the origin thread. + void RequestComplete(int result_code) { + // The task may have been cancelled after it was started. + if (!was_cancelled()) { + CompletionCallback* callback = callback_; + coordinator_->RemoveOutstandingSetPacScriptTask(this); + callback->Run(result_code); + } + + Release(); // Balances the AddRef in Start. + } + + // Must only be used on the "origin" thread. + SingleThreadedProxyResolver* coordinator_; + CompletionCallback* callback_; + std::string pac_bytes_; GURL pac_url_; - std::string bytes_; + + // Usable from within DoQuery on the worker thread. + MessageLoop* origin_loop_; }; // SingleThreadedProxyResolver::Job ------------------------------------------- @@ -102,7 +151,7 @@ class SingleThreadedProxyResolver::Job coordinator_->RemoveFrontOfJobsQueueAndStartNext(this); } - Release(); // balances the AddRef in Query. we may get deleted after + Release(); // Balances the AddRef in Start. We may get deleted after // we return. } @@ -134,6 +183,9 @@ SingleThreadedProxyResolver::~SingleThreadedProxyResolver() { (*it)->Cancel(); } + if (outstanding_set_pac_script_task_) + outstanding_set_pac_script_task_->Cancel(); + // Note that |thread_| is destroyed before |resolver_|. This is important // since |resolver_| could be running on |thread_|. } @@ -182,21 +234,24 @@ void SingleThreadedProxyResolver::CancelRequest(RequestHandle req) { pending_jobs_.erase(it); } -void SingleThreadedProxyResolver::SetPacScriptByUrlInternal( - const GURL& pac_url) { - SetPacScriptHelper(pac_url, std::string()); +void SingleThreadedProxyResolver::CancelSetPacScript() { + DCHECK(outstanding_set_pac_script_task_); + outstanding_set_pac_script_task_->Cancel(); + outstanding_set_pac_script_task_ = NULL; } -void SingleThreadedProxyResolver::SetPacScriptByDataInternal( - const std::string& bytes) { - SetPacScriptHelper(GURL(), bytes); -} - -void SingleThreadedProxyResolver::SetPacScriptHelper(const GURL& pac_url, - const std::string& bytes) { +int SingleThreadedProxyResolver::SetPacScript( + const GURL& pac_url, + const std::string& pac_bytes, + CompletionCallback* callback) { EnsureThreadStarted(); - thread()->message_loop()->PostTask( - FROM_HERE, new SetPacScriptTask(resolver(), pac_url, bytes)); + DCHECK(!outstanding_set_pac_script_task_); + + SetPacScriptTask* task = new SetPacScriptTask( + this, pac_url, pac_bytes, callback); + outstanding_set_pac_script_task_ = task; + task->Start(); + return ERR_IO_PENDING; } void SingleThreadedProxyResolver::EnsureThreadStarted() { @@ -228,4 +283,10 @@ void SingleThreadedProxyResolver::RemoveFrontOfJobsQueueAndStartNext( ProcessPendingJobs(); } +void SingleThreadedProxyResolver::RemoveOutstandingSetPacScriptTask( + SetPacScriptTask* task) { + DCHECK_EQ(outstanding_set_pac_script_task_.get(), task); + outstanding_set_pac_script_task_ = NULL; +} + } // namespace net diff --git a/net/proxy/single_threaded_proxy_resolver.h b/net/proxy/single_threaded_proxy_resolver.h index 010e526..fff23b8 100644 --- a/net/proxy/single_threaded_proxy_resolver.h +++ b/net/proxy/single_threaded_proxy_resolver.h @@ -37,6 +37,7 @@ class SingleThreadedProxyResolver : public ProxyResolver { CompletionCallback* callback, RequestHandle* request); virtual void CancelRequest(RequestHandle request); + virtual void CancelSetPacScript(); protected: // The wrapped (synchronous) ProxyResolver. @@ -47,18 +48,17 @@ class SingleThreadedProxyResolver : public ProxyResolver { // thread. class Job; friend class Job; + class SetPacScriptTask; + friend class SetPacScriptTask; // FIFO queue that contains the in-progress job, and any pending jobs. typedef std::deque<scoped_refptr<Job> > PendingJobsQueue; base::Thread* thread() { return thread_.get(); } // ProxyResolver implementation: - virtual void SetPacScriptByUrlInternal(const GURL& pac_url); - virtual void SetPacScriptByDataInternal(const std::string& bytes); - - // Helper for shared code between SetPacScriptByUrlInternal() and - // SetPacScriptByDataInternal() -- the unused parameter is set to empty. - void SetPacScriptHelper(const GURL& pac_url, const std::string& bytes); + virtual int SetPacScript(const GURL& pac_url, + const std::string& pac_bytes, + CompletionCallback* callback); // Starts the worker thread if it isn't already running. void EnsureThreadStarted(); @@ -71,6 +71,10 @@ class SingleThreadedProxyResolver : public ProxyResolver { // DCHECK for verification purposes. void RemoveFrontOfJobsQueueAndStartNext(Job* expected_job); + // Clears |outstanding_set_pac_script_task_|. + // Called when |task| has just finished. + void RemoveOutstandingSetPacScriptTask(SetPacScriptTask* task); + // The synchronous resolver implementation. scoped_ptr<ProxyResolver> resolver_; @@ -81,6 +85,7 @@ class SingleThreadedProxyResolver : public ProxyResolver { scoped_ptr<base::Thread> thread_; PendingJobsQueue pending_jobs_; + scoped_refptr<SetPacScriptTask> outstanding_set_pac_script_task_; }; } // namespace net diff --git a/net/proxy/single_threaded_proxy_resolver_unittest.cc b/net/proxy/single_threaded_proxy_resolver_unittest.cc index 6fe7995..da21ff1 100644 --- a/net/proxy/single_threaded_proxy_resolver_unittest.cc +++ b/net/proxy/single_threaded_proxy_resolver_unittest.cc @@ -47,9 +47,12 @@ class MockProxyResolver : public ProxyResolver { NOTREACHED(); } - virtual void SetPacScriptByDataInternal(const std::string& bytes) { + virtual int SetPacScript(const GURL& pac_url, + const std::string& bytes, + CompletionCallback* callback) { CheckIsOnWorkerThread(); last_pac_bytes_ = bytes; + return OK; } const std::string& last_pac_bytes() const { return last_pac_bytes_; } @@ -127,9 +130,13 @@ TEST(SingleThreadedProxyResolverTest, Basic) { EXPECT_TRUE(resolver->expects_pac_bytes()); - // Call SetPacScriptByData() -- we will make sure it reaches the sync resolver - // later on. - resolver->SetPacScriptByData("pac script bytes"); + // Call SetPacScriptByData() -- verify that it reaches the synchronous + // resolver. + TestCompletionCallback set_script_callback; + rv = resolver->SetPacScriptByData("pac script bytes", &set_script_callback); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, set_script_callback.WaitForResult()); + EXPECT_EQ("pac script bytes", mock->last_pac_bytes()); // Start request 0. TestCompletionCallback callback0; @@ -143,11 +150,6 @@ TEST(SingleThreadedProxyResolverTest, Basic) { EXPECT_EQ(0, rv); EXPECT_EQ("PROXY request0:80", results0.ToPacString()); - // Verify that the data from SetPacScriptByData() reached the resolver. - // (Since we waited for the first request to complete, we are guaranteed - // that the earlier post completed). - EXPECT_EQ("pac script bytes", mock->last_pac_bytes()); - // Start 3 more requests (request1 to request3). TestCompletionCallback callback1; @@ -309,5 +311,60 @@ TEST(SingleThreadedProxyResolverTest, CancelRequestByDeleting) { EXPECT_FALSE(callback2.have_result()); } +// Cancel an outstanding call to SetPacScriptByData(). +TEST(SingleThreadedProxyResolverTest, CancelSetPacScript) { + BlockableProxyResolver* mock = new BlockableProxyResolver; + scoped_ptr<SingleThreadedProxyResolver> resolver( + new SingleThreadedProxyResolver(mock)); + + int rv; + + // Block the proxy resolver, so no request can complete. + mock->Block(); + + // Start request 0. + ProxyResolver::RequestHandle request0; + TestCompletionCallback callback0; + ProxyInfo results0; + rv = resolver->GetProxyForURL( + GURL("http://request0"), &results0, &callback0, &request0); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Wait until requests 0 reaches the worker thread. + mock->WaitUntilBlocked(); + + TestCompletionCallback set_pac_script_callback; + rv = resolver->SetPacScriptByData("data", &set_pac_script_callback); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Cancel the SetPacScriptByData request (it can't have finished yet, + // since the single-thread is currently blocked). + resolver->CancelSetPacScript(); + + // Start 1 more request. + + TestCompletionCallback callback1; + ProxyInfo results1; + rv = resolver->GetProxyForURL( + GURL("http://request1"), &results1, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Unblock the worker thread so the requests can continue running. + mock->Unblock(); + + // Wait for requests 0 and 1 to finish. + + rv = callback0.WaitForResult(); + EXPECT_EQ(0, rv); + EXPECT_EQ("PROXY request0:80", results0.ToPacString()); + + rv = callback1.WaitForResult(); + EXPECT_EQ(1, rv); + EXPECT_EQ("PROXY request1:80", results1.ToPacString()); + + // The SetPacScript callback should never have been completed. + EXPECT_FALSE(set_pac_script_callback.have_result()); +} + } // namespace } // namespace net |