diff options
author | darin@google.com <darin@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-08-11 15:40:23 +0000 |
---|---|---|
committer | darin@google.com <darin@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-08-11 15:40:23 +0000 |
commit | 928fb58bd99536f16df3739329cbade0853b3309 (patch) | |
tree | 7a6433d25d9c5303614f05b6b39cb49e60468d12 /net/proxy | |
parent | 6723f835ff0247e5bffad9e92d8d42a8c3ae1b3b (diff) | |
download | chromium_src-928fb58bd99536f16df3739329cbade0853b3309.zip chromium_src-928fb58bd99536f16df3739329cbade0853b3309.tar.gz chromium_src-928fb58bd99536f16df3739329cbade0853b3309.tar.bz2 |
Rename HttpProxy* classes to Proxy*. Move them into a net/proxy/ subdirectory.
I'm making this change because proxy resolution is really not specific to the HTTP protocol. We need to use the proxy service in our FTP implementation, for example. I made a separate directory instead of just putting these in base, because I anticipate more files once we have our own PAC implementation.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@651 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/proxy')
-rw-r--r-- | net/proxy/proxy_resolver_fixed.cc | 48 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_fixed.h | 54 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_winhttp.cc | 185 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_winhttp.h | 64 | ||||
-rw-r--r-- | net/proxy/proxy_service.cc | 496 | ||||
-rw-r--r-- | net/proxy/proxy_service.h | 303 | ||||
-rw-r--r-- | net/proxy/proxy_service_unittest.cc | 408 |
7 files changed, 1558 insertions, 0 deletions
diff --git a/net/proxy/proxy_resolver_fixed.cc b/net/proxy/proxy_resolver_fixed.cc new file mode 100644 index 0000000..412cd2d --- /dev/null +++ b/net/proxy/proxy_resolver_fixed.cc @@ -0,0 +1,48 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "net/proxy/proxy_resolver_fixed.h" + +#include "net/base/net_errors.h" + +namespace net { + +int ProxyResolverFixed::GetProxyConfig(ProxyConfig* config) { + config->proxy_server = pi_.proxy_server(); + return OK; +} + +int ProxyResolverFixed::GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + ProxyInfo* results) { + NOTREACHED() << "Should not be asked to do proxy auto config"; + return ERR_FAILED; +} + +} // namespace net diff --git a/net/proxy/proxy_resolver_fixed.h b/net/proxy/proxy_resolver_fixed.h new file mode 100644 index 0000000..81283e7 --- /dev/null +++ b/net/proxy/proxy_resolver_fixed.h @@ -0,0 +1,54 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef NET_PROXY_PROXY_RESOLVER_FIXED_H_ +#define NET_PROXY_PROXY_RESOLVER_FIXED_H_ + +#include "net/proxy/proxy_service.h" + +namespace net { + +// Implementation of ProxyResolver that returns a fixed result. +class ProxyResolverFixed : public ProxyResolver { + public: + ProxyResolverFixed(const ProxyInfo& pi) { pi_.Use(pi); } + + // ProxyResolver methods: + virtual int GetProxyConfig(ProxyConfig* config); + virtual int GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + ProxyInfo* results); + + private: + ProxyInfo pi_; +}; + +} // namespace net + +#endif // NET_PROXY_PROXY_RESOLVER_FIXED_H_ diff --git a/net/proxy/proxy_resolver_winhttp.cc b/net/proxy/proxy_resolver_winhttp.cc new file mode 100644 index 0000000..aa1a296 --- /dev/null +++ b/net/proxy/proxy_resolver_winhttp.cc @@ -0,0 +1,185 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "net/proxy/proxy_resolver_winhttp.h" + +#include <windows.h> +#include <winhttp.h> + +#include "base/histogram.h" +#include "net/base/net_errors.h" + +#pragma comment(lib, "winhttp.lib") + +namespace net { + +// A small wrapper for histogramming purposes ;-) +static BOOL CallWinHttpGetProxyForUrl(HINTERNET session, LPCWSTR url, + WINHTTP_AUTOPROXY_OPTIONS* options, + WINHTTP_PROXY_INFO* results) { + TimeTicks time_start = TimeTicks::Now(); + BOOL rv = WinHttpGetProxyForUrl(session, url, options, results); + TimeDelta time_delta = TimeTicks::Now() - time_start; + // Record separately success and failure times since they will have very + // different characteristics. + if (rv) { + UMA_HISTOGRAM_LONG_TIMES(L"Net.GetProxyForUrl_OK", time_delta); + } else { + UMA_HISTOGRAM_LONG_TIMES(L"Net.GetProxyForUrl_FAIL", time_delta); + } + return rv; +} + +static void FreeConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* config) { + if (config->lpszAutoConfigUrl) + GlobalFree(config->lpszAutoConfigUrl); + if (config->lpszProxy) + GlobalFree(config->lpszProxy); + if (config->lpszProxyBypass) + GlobalFree(config->lpszProxyBypass); +} + +static void FreeInfo(WINHTTP_PROXY_INFO* info) { + if (info->lpszProxy) + GlobalFree(info->lpszProxy); + if (info->lpszProxyBypass) + GlobalFree(info->lpszProxyBypass); +} + +ProxyResolverWinHttp::ProxyResolverWinHttp() + : session_handle_(NULL) { +} + +ProxyResolverWinHttp::~ProxyResolverWinHttp() { + CloseWinHttpSession(); +} + +int ProxyResolverWinHttp::GetProxyConfig(ProxyConfig* config) { + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_config = {0}; + if (!WinHttpGetIEProxyConfigForCurrentUser(&ie_config)) { + LOG(ERROR) << "WinHttpGetIEProxyConfigForCurrentUser failed: " << + GetLastError(); + return ERR_FAILED; // TODO(darin): Bug 1189288: translate error code. + } + + if (ie_config.fAutoDetect) + config->auto_detect = true; + if (ie_config.lpszProxy) + config->proxy_server = ie_config.lpszProxy; + if (ie_config.lpszProxyBypass) + config->proxy_bypass = ie_config.lpszProxyBypass; + if (ie_config.lpszAutoConfigUrl) + config->pac_url = ie_config.lpszAutoConfigUrl; + + FreeConfig(&ie_config); + return OK; +} + +int ProxyResolverWinHttp::GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + ProxyInfo* results) { + // If we don't have a WinHTTP session, then create a new one. + if (!session_handle_ && !OpenWinHttpSession()) + return ERR_FAILED; + + // If we have been given an empty PAC url, then use auto-detection. + // + // NOTE: We just use DNS-based auto-detection here like Firefox. We do this + // to avoid WinHTTP's auto-detection code, which while more featureful (it + // supports DHCP based auto-detection) also appears to have issues. + // + WINHTTP_AUTOPROXY_OPTIONS options = {0}; + options.fAutoLogonIfChallenged = TRUE; + options.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; + options.lpszAutoConfigUrl = + pac_url.empty() ? L"http://wpad/wpad.dat" : pac_url.c_str(); + + WINHTTP_PROXY_INFO info = {0}; + DCHECK(session_handle_); + if (!CallWinHttpGetProxyForUrl(session_handle_, query_url.c_str(), &options, + &info)) { + DWORD error = GetLastError(); + LOG(ERROR) << "WinHttpGetProxyForUrl failed: " << error; + + // If we got here because of RPC timeout during out of process PAC + // resolution, no further requests on this session are going to work. + if ((ERROR_WINHTTP_TIMEOUT == error) || + (ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR == error)) { + CloseWinHttpSession(); + } + + return ERR_FAILED; // TODO(darin): Bug 1189288: translate error code. + } + + int rv = OK; + + switch (info.dwAccessType) { + case WINHTTP_ACCESS_TYPE_NO_PROXY: + results->UseDirect(); + break; + case WINHTTP_ACCESS_TYPE_NAMED_PROXY: + results->UseNamedProxy(info.lpszProxy); + break; + default: + NOTREACHED(); + rv = ERR_FAILED; + } + + FreeInfo(&info); + return rv; +} + +bool ProxyResolverWinHttp::OpenWinHttpSession() { + DCHECK(!session_handle_); + session_handle_ = WinHttpOpen(NULL, + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + if (!session_handle_) + return false; + + // Since this session handle will never be used for WinHTTP connections, + // these timeouts don't really mean much individually. However, WinHTTP's + // out of process PAC resolution will use a combined (sum of all timeouts) + // value to wait for an RPC reply. + BOOL rv = WinHttpSetTimeouts(session_handle_, 10000, 10000, 5000, 5000); + DCHECK(rv); + + return true; +} + +void ProxyResolverWinHttp::CloseWinHttpSession() { + if (session_handle_) { + WinHttpCloseHandle(session_handle_); + session_handle_ = NULL; + } +} + +} // namespace net diff --git a/net/proxy/proxy_resolver_winhttp.h b/net/proxy/proxy_resolver_winhttp.h new file mode 100644 index 0000000..5d2a432 --- /dev/null +++ b/net/proxy/proxy_resolver_winhttp.h @@ -0,0 +1,64 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef NET_PROXY_PROXY_RESOLVER_WINHTTP_H_ +#define NET_PROXY_PROXY_RESOLVER_WINHTTP_H_ + +#include "net/proxy/proxy_service.h" + +typedef LPVOID HINTERNET; // From winhttp.h + +namespace net { + +// An implementation of ProxyResolver that uses WinHTTP and the system +// proxy settings. +class ProxyResolverWinHttp : public ProxyResolver { + public: + ProxyResolverWinHttp(); + ~ProxyResolverWinHttp(); + + // ProxyResolver implementation: + virtual int GetProxyConfig(ProxyConfig* config); + virtual int GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + ProxyInfo* results); + + private: + bool OpenWinHttpSession(); + void CloseWinHttpSession(); + + // Proxy configuration is cached on the session handle. + HINTERNET session_handle_; + + DISALLOW_COPY_AND_ASSIGN(ProxyResolverWinHttp); +}; + +} // namespace net + +#endif // NET_PROXY_PROXY_RESOLVER_WINHTTP_H_ diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc new file mode 100644 index 0000000..6007909 --- /dev/null +++ b/net/proxy/proxy_service.cc @@ -0,0 +1,496 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "net/proxy/proxy_service.h" + +#include <windows.h> +#include <winhttp.h> + +#include <algorithm> + +#include "base/message_loop.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "googleurl/src/gurl.h" +#include "net/base/net_errors.h" + +namespace net { + +// ProxyConfig ---------------------------------------------------------------- + +// static +ProxyConfig::ID ProxyConfig::last_id_ = ProxyConfig::INVALID_ID; + +ProxyConfig::ProxyConfig() + : auto_detect(false), + id_(++last_id_) { +} + +bool ProxyConfig::Equals(const ProxyConfig& other) const { + // The two configs can have different IDs. We are just interested in if they + // have the same settings. + return auto_detect == other.auto_detect && + pac_url == other.pac_url && + proxy_server == other.proxy_server && + proxy_bypass == other.proxy_bypass; +} + +// ProxyList ------------------------------------------------------------------ +void ProxyList::SetVector(const std::vector<std::wstring>& proxies) { + proxies_.clear(); + std::vector<std::wstring>::const_iterator iter = proxies.begin(); + for (; iter != proxies.end(); ++iter) { + std::wstring proxy_sever; + TrimWhitespace(*iter, TRIM_ALL, &proxy_sever); + proxies_.push_back(proxy_sever); + } +} + +void ProxyList::Set(const std::wstring& proxy_list) { + // Extract the different proxies from the list. + std::vector<std::wstring> proxies; + SplitString(proxy_list, L';', &proxies); + SetVector(proxies); +} + +void ProxyList::RemoveBadProxies(const ProxyRetryInfoMap& proxy_retry_info) { + std::vector<std::wstring> new_proxy_list; + std::vector<std::wstring>::const_iterator iter = proxies_.begin(); + for (; iter != proxies_.end(); ++iter) { + ProxyRetryInfoMap::const_iterator bad_proxy = + proxy_retry_info.find(*iter); + if (bad_proxy != proxy_retry_info.end()) { + // This proxy is bad. Check if it's time to retry. + if (bad_proxy->second.bad_until >= TimeTicks::Now()) { + // still invalid. + continue; + } + } + new_proxy_list.push_back(*iter); + } + + proxies_ = new_proxy_list; +} + +std::wstring ProxyList::Get() const { + if (!proxies_.empty()) + return proxies_[0]; + + return std::wstring(); +} + +std::wstring ProxyList::GetList() const { + std::wstring proxy_list; + std::vector<std::wstring>::const_iterator iter = proxies_.begin(); + for (; iter != proxies_.end(); ++iter) { + if (!proxy_list.empty()) + proxy_list += L';'; + + proxy_list += *iter; + } + + return proxy_list; +} + +bool ProxyList::Fallback(ProxyRetryInfoMap* proxy_retry_info) { + // Number of minutes to wait before retrying a bad proxy server. + const TimeDelta kProxyRetryDelay = TimeDelta::FromMinutes(5); + + if (proxies_.empty()) { + NOTREACHED(); + return false; + } + + // Mark this proxy as bad. + ProxyRetryInfoMap::iterator iter = proxy_retry_info->find(proxies_[0]); + if (iter != proxy_retry_info->end()) { + // TODO(nsylvain): This is not the first time we get this. We should + // double the retry time. Bug 997660. + iter->second.bad_until = TimeTicks::Now() + iter->second.current_delay; + } else { + ProxyRetryInfo retry_info; + retry_info.current_delay = kProxyRetryDelay; + retry_info.bad_until = TimeTicks().Now() + retry_info.current_delay; + (*proxy_retry_info)[proxies_[0]] = retry_info; + } + + // Remove this proxy from our list. + proxies_.erase(proxies_.begin()); + + return !proxies_.empty(); +} + +// ProxyInfo ------------------------------------------------------------------ + +ProxyInfo::ProxyInfo() + : config_id_(ProxyConfig::INVALID_ID), + config_was_tried_(false) { +} + +void ProxyInfo::Use(const ProxyInfo& other) { + proxy_list_ = other.proxy_list_; +} + +void ProxyInfo::UseDirect() { + proxy_list_.Set(std::wstring()); +} + +void ProxyInfo::UseNamedProxy(const std::wstring& proxy_server) { + proxy_list_.Set(proxy_server); +} + +void ProxyInfo::Apply(HINTERNET request_handle) { + WINHTTP_PROXY_INFO pi; + std::wstring proxy; // We need to declare this variable here because + // lpszProxy needs to be valid in WinHttpSetOption. + if (is_direct()) { + pi.dwAccessType = WINHTTP_ACCESS_TYPE_NO_PROXY; + pi.lpszProxy = WINHTTP_NO_PROXY_NAME; + pi.lpszProxyBypass = WINHTTP_NO_PROXY_BYPASS; + } else { + proxy = proxy_list_.Get(); + pi.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; + pi.lpszProxy = const_cast<LPWSTR>(proxy.c_str()); + // NOTE: Specifying a bypass list here would serve no purpose. + pi.lpszProxyBypass = WINHTTP_NO_PROXY_BYPASS; + } + WinHttpSetOption(request_handle, WINHTTP_OPTION_PROXY, &pi, sizeof(pi)); +} + +// ProxyService::PacRequest --------------------------------------------------- + +// We rely on the fact that the origin thread (and its message loop) will not +// be destroyed until after the PAC thread is destroyed. + +class ProxyService::PacRequest : + public base::RefCountedThreadSafe<ProxyService::PacRequest> { + public: + PacRequest(ProxyService* service, + const std::wstring& pac_url, + CompletionCallback* callback) + : service_(service), + callback_(callback), + results_(NULL), + config_id_(service->config_id()), + pac_url_(pac_url), + origin_loop_(NULL) { + // We need to remember original loop if only in case of asynchronous call + if (callback_) + origin_loop_ = MessageLoop::current(); + } + + void Query(const std::wstring& url, ProxyInfo* results) { + results_ = results; + // If we have a valid callback then execute Query asynchronously + if (callback_) { + AddRef(); // balanced in QueryComplete + service_->pac_thread()->message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(this, &ProxyService::PacRequest::DoQuery, + service_->resolver(), url, pac_url_)); + } else { + DoQuery(service_->resolver(), url, pac_url_); + } + } + + void Cancel() { + // Clear these to inform QueryComplete that it should not try to + // access them. + service_ = NULL; + callback_ = NULL; + results_ = NULL; + } + + private: + // Runs on the PAC thread if a valid callback is provided. + void DoQuery(ProxyResolver* resolver, + const std::wstring& query_url, + const std::wstring& pac_url) { + int rv = resolver->GetProxyForURL(query_url, pac_url, &results_buf_); + if (origin_loop_) { + origin_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &PacRequest::QueryComplete, rv)); + } else { + QueryComplete(rv); + } + } + + // If a valid callback is provided, this runs on the origin thread to + // indicate that the completion callback should be run. + void QueryComplete(int result_code) { + if (service_) + service_->DidCompletePacRequest(config_id_, result_code); + + if (result_code == OK && results_) { + results_->Use(results_buf_); + results_->RemoveBadProxies(service_->proxy_retry_info_); + } + + if (callback_) + callback_->Run(result_code); + + if (origin_loop_) { + Release(); // balances the AddRef in Query. we may get deleted after + // we return. + } + } + + // Must only be used on the "origin" thread. + ProxyService* service_; + CompletionCallback* callback_; + ProxyInfo* results_; + ProxyConfig::ID config_id_; + + // Usable from within DoQuery on the PAC thread. + ProxyInfo results_buf_; + std::wstring pac_url_; + MessageLoop* origin_loop_; +}; + +// ProxyService --------------------------------------------------------------- + +ProxyService::ProxyService(ProxyResolver* resolver) + : resolver_(resolver), + config_is_bad_(false) { + UpdateConfig(); +} + +int ProxyService::ResolveProxy(const GURL& url, ProxyInfo* result, + CompletionCallback* callback, + PacRequest** pac_request) { + // The overhead of calling WinHttpGetIEProxyConfigForCurrentUser is very low. + const TimeDelta kProxyConfigMaxAge = TimeDelta::FromSeconds(5); + + // Periodically check for a new config. + if ((TimeTicks::Now() - config_last_update_time_) > kProxyConfigMaxAge) + UpdateConfig(); + result->config_id_ = config_.id(); + + // Fallback to a "direct" (no proxy) connection if the current configuration + // is known to be bad. + if (config_is_bad_) { + // Reset this flag to false in case the ProxyInfo object is being + // re-used by the caller. + result->config_was_tried_ = false; + } else { + // Remember that we are trying to use the current proxy configuration. + result->config_was_tried_ = true; + + if (!config_.proxy_server.empty()) { + if (ShouldBypassProxyForURL(url)) { + result->UseDirect(); + } else { + // If proxies are specified on a per protocol basis, the proxy server + // field contains a list the format of which is as below:- + // "scheme1=url:port;scheme2=url:port", etc. + std::wstring url_scheme = ASCIIToWide(url.scheme()); + + WStringTokenizer proxy_server_list(config_.proxy_server, L";"); + while (proxy_server_list.GetNext()) { + WStringTokenizer proxy_server_for_scheme( + proxy_server_list.token_begin(), proxy_server_list.token_end(), + L"="); + + while (proxy_server_for_scheme.GetNext()) { + const std::wstring& proxy_server_scheme = + proxy_server_for_scheme.token(); + + // If we fail to get the proxy server here, it means that + // this is a regular proxy server configuration, i.e. proxies + // are not configured per protocol. + if (!proxy_server_for_scheme.GetNext()) { + result->UseNamedProxy(proxy_server_scheme); + return OK; + } + + if (proxy_server_scheme == url_scheme) { + result->UseNamedProxy(proxy_server_for_scheme.token()); + return OK; + } + } + } + // We failed to find a matching proxy server for the current URL + // scheme. Default to direct. + result->UseDirect(); + } + return OK; + } + + if (!config_.pac_url.empty() || config_.auto_detect) { + if (callback) { + // Create PAC thread for asynchronous mode. + if (!pac_thread_.get()) { + pac_thread_.reset(new Thread("pac-thread")); + pac_thread_->Start(); + } + } else { + // If this request is synchronous, then there's no point + // in returning PacRequest instance + DCHECK(!pac_request); + } + + scoped_refptr<PacRequest> req = + new PacRequest(this, config_.pac_url, callback); + req->Query(UTF8ToWide(url.spec()), result); + + if (callback) { + if (pac_request) + *pac_request = req; + return ERR_IO_PENDING; // Wait for callback. + } + return OK; + } + } + + // otherwise, we have no proxy config + result->UseDirect(); + return OK; +} + +int ProxyService::ReconsiderProxyAfterError(const GURL& url, + ProxyInfo* result, + CompletionCallback* callback, + PacRequest** pac_request) { + // Check to see if we have a new config since ResolveProxy was called. We + // want to re-run ResolveProxy in two cases: 1) we have a new config, or 2) a + // direct connection failed and we never tried the current config. + + bool re_resolve = result->config_id_ != config_.id(); + if (!re_resolve) { + UpdateConfig(); + if (result->config_id_ != config_.id()) { + // A new configuration! + re_resolve = true; + } else if (!result->config_was_tried_) { + // We never tried the proxy configuration since we thought it was bad, + // but because we failed to establish a connection, let's try the proxy + // configuration again to see if it will work now. + config_is_bad_ = false; + re_resolve = true; + } + } + if (re_resolve) { + // If we have a new config or the config was never tried, we delete the + // list of bad proxies and we try again. + proxy_retry_info_.clear(); + return ResolveProxy(url, result, callback, pac_request); + } + + // We don't have new proxy settings to try, fallback to the next proxy + // in the list. + bool was_direct = result->is_direct(); + if (!was_direct && result->Fallback(&proxy_retry_info_)) + return OK; + + if (!config_.auto_detect && !config_.proxy_server.empty()) { + // If auto detect is on, then we should try a DIRECT connection + // as the attempt to reach the proxy failed. + return ERR_FAILED; + } + + // If we already tried a direct connection, then just give up. + if (was_direct) + return ERR_FAILED; + + // Try going direct. + result->UseDirect(); + return OK; +} + +void ProxyService::CancelPacRequest(PacRequest* pac_request) { + pac_request->Cancel(); +} + +void ProxyService::DidCompletePacRequest(int config_id, int result_code) { + // If we get an error that indicates a bad PAC config, then we should + // remember that, and not try the PAC config again for a while. + + // Our config may have already changed. + if (result_code == OK || config_id != config_.id()) + return; + + // Remember that this configuration doesn't work. + config_is_bad_ = true; +} + +void ProxyService::UpdateConfig() { + ProxyConfig latest; + if (resolver_->GetProxyConfig(&latest) != OK) + return; + config_last_update_time_ = TimeTicks::Now(); + + if (latest.Equals(config_)) + return; + + config_ = latest; + config_is_bad_ = false; + + // We have a new config, we should clear the list of bad proxies. + proxy_retry_info_.clear(); +} + +bool ProxyService::ShouldBypassProxyForURL(const GURL& url) { + std::wstring url_domain = ASCIIToWide(url.scheme()); + if (!url_domain.empty()) + url_domain += L"://"; + + url_domain += ASCIIToWide(url.host()); + StringToLowerASCII(url_domain); + + WStringTokenizer proxy_server_bypass_list(config_.proxy_bypass, L";"); + while (proxy_server_bypass_list.GetNext()) { + std::wstring bypass_url_domain = proxy_server_bypass_list.token(); + if (bypass_url_domain == L"<local>") { + // Any name without a DOT (.) is considered to be local. + if (url.host().find(L'.') == std::wstring::npos) + return true; + continue; + } + + // The proxy server bypass list can contain entities with http/https + // If no scheme is specified then it indicates that all schemes are + // allowed for the current entry. For matching this we just use + // the protocol scheme of the url passed in. + if (bypass_url_domain.find(L"://") == std::wstring::npos) { + std::wstring bypass_url_domain_with_scheme = ASCIIToWide(url.scheme()); + bypass_url_domain_with_scheme += L"://"; + bypass_url_domain_with_scheme += bypass_url_domain; + + bypass_url_domain = bypass_url_domain_with_scheme; + } + + StringToLowerASCII(bypass_url_domain); + + if (MatchPattern(url_domain, bypass_url_domain)) + return true; + } + + return false; +} + +} // namespace net diff --git a/net/proxy/proxy_service.h b/net/proxy/proxy_service.h new file mode 100644 index 0000000..c52926e --- /dev/null +++ b/net/proxy/proxy_service.h @@ -0,0 +1,303 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef NET_PROXY_PROXY_SERVICE_H_ +#define NET_PROXY_PROXY_SERVICE_H_ + +#include <map> +#include <vector> + +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/thread.h" +#include "base/time.h" +#include "net/base/completion_callback.h" + +typedef LPVOID HINTERNET; // From winhttp.h + +class GURL; + +namespace net { + +class ProxyInfo; +class ProxyResolver; + +// Proxy configuration used to by the ProxyService. +class ProxyConfig { + public: + typedef int ID; + + // Indicates an invalid proxy config. + enum { INVALID_ID = 0 }; + + ProxyConfig(); + // Default copy-constructor an assignment operator are OK! + + // Used to numerically identify this configuration. + ID id() const { return id_; } + + // True if the proxy configuration should be auto-detected. + bool auto_detect; + + // If non-empty, indicates the URL of the proxy auto-config file to use. + std::wstring pac_url; + + // If non-empty, indicates the proxy server to use (of the form host:port). + std::wstring proxy_server; + + // If non-empty, indicates a comma-delimited list of hosts that should bypass + // any proxy configuration. For these hosts, a direct connection should + // always be used. + std::wstring proxy_bypass; + + // Returns true if the given config is equivalent to this config. + bool Equals(const ProxyConfig& other) const; + + private: + static int last_id_; + int id_; +}; + +// Contains the information about when to retry a proxy server. +struct ProxyRetryInfo { + // We should not retry until this time. + TimeTicks bad_until; + + // This is the current delay. If the proxy is still bad, we need to increase + // this delay. + TimeDelta current_delay; +}; + +// Map of proxy servers with the associated RetryInfo structures. +typedef std::map<std::wstring, ProxyRetryInfo> ProxyRetryInfoMap; + +// This class can be used to resolve the proxy server to use when loading a +// HTTP(S) URL. It uses to the given ProxyResolver to handle the actual proxy +// resolution. See ProxyResolverWinHttp for example. The consumer of this +// class is responsible for ensuring that the ProxyResolver instance remains +// valid for the lifetime of the ProxyService. +class ProxyService { + public: + explicit ProxyService(ProxyResolver* resolver); + + // Used internally to handle PAC queries. + class PacRequest; + + // Returns OK if proxy information could be provided synchronously. Else, + // ERR_IO_PENDING is returned to indicate that the result will be available + // when the callback is run. The callback is run on the thread that calls + // ResolveProxy. + // + // The caller is responsible for ensuring that |results| and |callback| + // remain valid until the callback is run or until |pac_request| is cancelled + // via CancelPacRequest. |pac_request| is only valid while the completion + // callback is still pending. + // + // We use the three possible proxy access types in the following order, and + // we only use one of them (no falling back to other access types if the + // chosen one doesn't work). + // 1. named proxy + // 2. PAC URL + // 3. WPAD auto-detection + // + int ResolveProxy(const GURL& url, + ProxyInfo* results, + CompletionCallback* callback, + PacRequest** pac_request); + + // This method is called after a failure to connect or resolve a host name. + // It gives the proxy service an opportunity to reconsider the proxy to use. + // The |results| parameter contains the results returned by an earlier call + // to ResolveProxy. The semantics of this call are otherwise similar to + // ResolveProxy. + // + // Returns ERR_FAILED if there is not another proxy config to try. + // + int ReconsiderProxyAfterError(const GURL& url, + ProxyInfo* results, + CompletionCallback* callback, + PacRequest** pac_request); + + // Call this method with a non-null |pac_request| to cancel the PAC request. + void CancelPacRequest(PacRequest* pac_request); + + private: + friend class PacRequest; + + ProxyResolver* resolver() { return resolver_; } + Thread* pac_thread() { return pac_thread_.get(); } + + // Identifies the proxy configuration. + ProxyConfig::ID config_id() const { return config_.id(); } + + // Checks to see if the proxy configuration changed, and then updates config_ + // to reference the new configuration. + void UpdateConfig(); + + // 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 + // error code, indicating a bad proxy configuration. + void DidCompletePacRequest(int config_id, int result_code); + + // Returns true if the URL passed in should not go through the proxy server. + // 1. If the bypass proxy list contains the string <local> and the URL + // passed in is a local URL, i.e. a URL without a DOT (.) + // 2. The URL matches one of the entities in the proxy bypass list. + bool ShouldBypassProxyForURL(const GURL& url); + + ProxyResolver* resolver_; + scoped_ptr<Thread> pac_thread_; + + // We store the IE proxy config and a counter that is incremented each time + // the config changes. + ProxyConfig config_; + + // Indicates that the configuration is bad and should be ignored. + bool config_is_bad_; + + // The time when the proxy configuration was last read from the system. + TimeTicks config_last_update_time_; + + // Map of the known bad proxies and the information about the retry time. + ProxyRetryInfoMap proxy_retry_info_; + + DISALLOW_COPY_AND_ASSIGN(ProxyService); +}; + +// This class is used to hold a list of proxies returned by GetProxyForUrl or +// manually configured. It handles proxy fallback if multiple servers are +// specified. +class ProxyList { + public: + // Initializes the proxy list to a string containing one or more proxy servers + // delimited by a semicolon. + void Set(const std::wstring& proxy_list); + + // Initializes the proxy list to a vector containing one or more proxy + // servers. + void SetVector(const std::vector<std::wstring>& proxy_list); + + // Remove all proxies known to be bad from the proxy list. + void RemoveBadProxies(const ProxyRetryInfoMap& proxy_retry_info); + + // Returns the first valid proxy server in the list. + std::wstring Get() const; + + // Returns all the valid proxies, delimited by a semicolon. + std::wstring GetList() const; + + // Marks the current proxy server as bad and deletes it from the list. The + // list of known bad proxies is given by proxy_retry_info. Returns true if + // there is another server available in the list. + bool Fallback(ProxyRetryInfoMap* proxy_retry_info); + + private: + // List of proxies. + std::vector<std::wstring> proxies_; +}; + +// This object holds proxy information returned by ResolveProxy. +class ProxyInfo { + public: + ProxyInfo(); + + // Use the same proxy server as the given |proxy_info|. + void Use(const ProxyInfo& proxy_info); + + // Use a direct connection. + void UseDirect(); + + // Use a specific proxy server, of the form: <hostname> [":" <port>] + // This may optionally be a semi-colon delimited list of proxy servers. + void UseNamedProxy(const std::wstring& proxy_server); + + // Apply this proxy information to the given WinHTTP request handle. + void Apply(HINTERNET request_handle); + + // Returns true if this proxy info specifies a direct connection. + bool is_direct() const { return proxy_list_.Get().empty(); } + + // Returns the first valid proxy server. + std::wstring proxy_server() const { return proxy_list_.Get(); } + + std::string GetProxyServer() const { return WideToASCII(proxy_server()); } + + // Marks the current proxy as bad. Returns true if there is another proxy + // available to try in proxy list_. + bool Fallback(ProxyRetryInfoMap* proxy_retry_info) { + return proxy_list_.Fallback(proxy_retry_info); + } + + // Remove all proxies known to be bad from the proxy list. + void RemoveBadProxies(const ProxyRetryInfoMap& proxy_retry_info) { + proxy_list_.RemoveBadProxies(proxy_retry_info); + } + + private: + friend class ProxyService; + + // If proxy_list_ is set to empty, then a "direct" connection is indicated. + ProxyList proxy_list_; + + // This value identifies the proxy config used to initialize this object. + ProxyConfig::ID config_id_; + + // This flag is false when the proxy configuration was known to be bad when + // this proxy info was initialized. In such cases, we know that if this + // proxy info does not yield a connection that we might want to reconsider + // the proxy config given by config_id_. + bool config_was_tried_; + + DISALLOW_COPY_AND_ASSIGN(ProxyInfo); +}; + +// This interface provides the low-level functions to access the proxy +// configuration and resolve proxies for given URLs synchronously. +class ProxyResolver { + public: + virtual ~ProxyResolver() {} + + // Get the proxy configuration. Returns OK if successful or an error code if + // otherwise. |config| should be in its initial state when this method is + // called. + virtual int GetProxyConfig(ProxyConfig* config) = 0; + + // Query the proxy auto-config file (specified by |pac_url|) for the proxy to + // use to load the given |query_url|. Returns OK if successful or an error + // code if otherwise. + virtual int GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + ProxyInfo* results) = 0; +}; + +} // namespace net + +#endif // NET_PROXY_PROXY_SERVICE_H_ diff --git a/net/proxy/proxy_service_unittest.cc b/net/proxy/proxy_service_unittest.cc new file mode 100644 index 0000000..f4b9292 --- /dev/null +++ b/net/proxy/proxy_service_unittest.cc @@ -0,0 +1,408 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "googleurl/src/gurl.h" +#include "net/base/net_errors.h" +#include "net/proxy/proxy_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class MockProxyResolver : public net::ProxyResolver { + public: + MockProxyResolver() : fail_get_proxy_for_url(false) { + config.reset(new net::ProxyConfig); + } + virtual int GetProxyConfig(net::ProxyConfig* results) { + *results = *(config.get()); + return net::OK; + } + virtual int GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + net::ProxyInfo* results) { + if (pac_url != config->pac_url) + return net::ERR_INVALID_ARGUMENT; + if (fail_get_proxy_for_url) + return net::ERR_FAILED; + if (GURL(query_url).host() == info_predicate_query_host) { + results->Use(info); + } else { + results->UseDirect(); + } + return net::OK; + } + scoped_ptr<net::ProxyConfig> config; + net::ProxyInfo info; + + // info is only returned if query_url in GetProxyForURL matches this: + std::string info_predicate_query_host; + + // If true, then GetProxyForURL will fail, which simulates failure to + // download or execute the PAC file. + bool fail_get_proxy_for_url; +}; + +} // namespace + +TEST(ProxyServiceTest, Direct) { + MockProxyResolver resolver; + net::ProxyService service(&resolver); + + GURL url("http://www.google.com/"); + + net::ProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info.is_direct()); +} + +TEST(ProxyServiceTest, PAC) { + MockProxyResolver resolver; + resolver.config->pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy"); + resolver.info_predicate_query_host = "www.google.com"; + + net::ProxyService service(&resolver); + + GURL url("http://www.google.com/"); + + net::ProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server(), L"foopy"); +} + +TEST(ProxyServiceTest, PAC_FailoverToDirect) { + MockProxyResolver resolver; + resolver.config->pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy:8080"); + resolver.info_predicate_query_host = "www.google.com"; + + net::ProxyService service(&resolver); + + GURL url("http://www.google.com/"); + + net::ProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server(), L"foopy:8080"); + + // Now, imagine that connecting to foopy:8080 fails. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info.is_direct()); +} + +TEST(ProxyServiceTest, PAC_FailsToDownload) { + // Test what happens when we fail to download the PAC URL. + + MockProxyResolver resolver; + resolver.config->pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy:8080"); + resolver.info_predicate_query_host = "www.google.com"; + resolver.fail_get_proxy_for_url = true; + + net::ProxyService service(&resolver); + + GURL url("http://www.google.com/"); + net::ProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info.is_direct()); + + rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info.is_direct()); + + resolver.fail_get_proxy_for_url = false; + resolver.info.UseNamedProxy(L"foopy_valid:8080"); + + // But, if that fails, then we should give the proxy config another shot + // since we have never tried it with this URL before. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server(), L"foopy_valid:8080"); +} + +TEST(ProxyServiceTest, ProxyFallback) { + // Test what happens when we specify multiple proxy servers and some of them + // are bad. + + MockProxyResolver resolver; + resolver.config->pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy1:8080;foopy2:9090"); + resolver.info_predicate_query_host = "www.google.com"; + resolver.fail_get_proxy_for_url = false; + + net::ProxyService service(&resolver); + + GURL url("http://www.google.com/"); + + // Get the proxy information. + net::ProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + + // The first item is valid. + EXPECT_EQ(info.proxy_server(), L"foopy1:8080"); + + // Fake an error on the proxy. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + + // The second proxy should be specified. + EXPECT_EQ(info.proxy_server(), L"foopy2:9090"); + + // Create a new resolver that returns 3 proxies. The second one is already + // known to be bad. + resolver.config->pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy3:7070;foopy1:8080;foopy2:9090"); + resolver.info_predicate_query_host = "www.google.com"; + resolver.fail_get_proxy_for_url = false; + + rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server(), L"foopy3:7070"); + + // We fake another error. It should now try the third one. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_EQ(info.proxy_server(), L"foopy2:9090"); + + // Fake another error, the last proxy is gone, the list should now be empty. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); // We try direct. + EXPECT_TRUE(info.is_direct()); + + // If it fails again, we don't have anything else to try. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::ERR_FAILED); // We try direct. + + // TODO(nsylvain): Test that the proxy can be retried after the delay. +} + +TEST(ProxyServiceTest, ProxyFallback_NewSettings) { + // Test proxy failover when new settings are available. + + MockProxyResolver resolver; + resolver.config->pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy1:8080;foopy2:9090"); + resolver.info_predicate_query_host = "www.google.com"; + resolver.fail_get_proxy_for_url = false; + + net::ProxyService service(&resolver); + + GURL url("http://www.google.com/"); + + // Get the proxy information. + net::ProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + + // The first item is valid. + EXPECT_EQ(info.proxy_server(), L"foopy1:8080"); + + // Fake an error on the proxy, and also a new configuration on the proxy. + resolver.config.reset(new net::ProxyConfig); + resolver.config->pac_url = L"http://foopy-new/proxy.pac"; + + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + + // The first proxy is still there since the configuration changed. + EXPECT_EQ(info.proxy_server(), L"foopy1:8080"); + + // We fake another error. It should now ignore the first one. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_EQ(info.proxy_server(), L"foopy2:9090"); + + // We simulate a new configuration. + resolver.config.reset(new net::ProxyConfig); + resolver.config->pac_url = L"http://foopy-new2/proxy.pac"; + + // We fake anothe error. It should go back to the first proxy. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_EQ(info.proxy_server(), L"foopy1:8080"); +} + +TEST(ProxyServiceTest, ProxyFallback_BadConfig) { + // Test proxy failover when the configuration is bad. + + MockProxyResolver resolver; + resolver.config->pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy1:8080;foopy2:9090"); + resolver.info_predicate_query_host = "www.google.com"; + resolver.fail_get_proxy_for_url = false; + + net::ProxyService service(&resolver); + + GURL url("http://www.google.com/"); + + // Get the proxy information. + net::ProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + + // The first item is valid. + EXPECT_EQ(info.proxy_server(), L"foopy1:8080"); + + // Fake a proxy error. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + + // The first proxy is ignored, and the second one is selected. + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server(), L"foopy2:9090"); + + // Fake a PAC failure. + net::ProxyInfo info2; + resolver.fail_get_proxy_for_url = true; + rv = service.ResolveProxy(url, &info2, NULL, NULL); + EXPECT_EQ(rv, net::OK); + + // No proxy servers are returned. It's a direct connection. + EXPECT_TRUE(info2.is_direct()); + + // The PAC is now fixed and will return a proxy server. + // It should also clear the list of bad proxies. + resolver.fail_get_proxy_for_url = false; + + // Try to resolve, it will still return "direct" because we have no reason + // to check the config since everything works. + net::ProxyInfo info3; + rv = service.ResolveProxy(url, &info3, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info3.is_direct()); + + // But if the direct connection fails, we check if the ProxyInfo tried to + // resolve the proxy before, and if not (like in this case), we give the + // PAC another try. + rv = service.ReconsiderProxyAfterError(url, &info3, NULL, NULL); + EXPECT_EQ(rv, net::OK); + + // The first proxy is still there since the list of bad proxies got cleared. + EXPECT_FALSE(info3.is_direct()); + EXPECT_EQ(info3.proxy_server(), L"foopy1:8080"); +} + +TEST(ProxyServiceTest, ProxyBypassList) { + // Test what happens when a proxy bypass list is specified. + + MockProxyResolver resolver; + resolver.config->proxy_server = L"foopy1:8080;foopy2:9090"; + resolver.config->auto_detect = false; + resolver.config->proxy_bypass = L"<local>"; + + net::ProxyService service(&resolver); + GURL url("http://www.google.com/"); + // Get the proxy information. + net::ProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + + net::ProxyService service1(&resolver); + GURL test_url1("local"); + net::ProxyInfo info1; + rv = service1.ResolveProxy(test_url1, &info1, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info1.is_direct()); + + resolver.config->proxy_bypass = L"<local>;*.org"; + net::ProxyService service2(&resolver); + GURL test_url2("http://www.webkit.org"); + net::ProxyInfo info2; + rv = service2.ResolveProxy(test_url2, &info2, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info2.is_direct()); + + resolver.config->proxy_bypass = L"<local>;*.org;7*"; + net::ProxyService service3(&resolver); + GURL test_url3("http://74.125.19.147"); + net::ProxyInfo info3; + rv = service3.ResolveProxy(test_url3, &info3, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info3.is_direct()); + + resolver.config->proxy_bypass = L"<local>;*.org;"; + net::ProxyService service4(&resolver); + GURL test_url4("http://www.msn.com"); + net::ProxyInfo info4; + rv = service4.ResolveProxy(test_url4, &info4, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info4.is_direct()); +} + +TEST(ProxyServiceTest, PerProtocolProxyTests) { + MockProxyResolver resolver; + resolver.config->proxy_server = L"http=foopy1:8080;https=foopy2:8080"; + resolver.config->auto_detect = false; + + net::ProxyService service1(&resolver); + GURL test_url1("http://www.msn.com"); + net::ProxyInfo info1; + int rv = service1.ResolveProxy(test_url1, &info1, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info1.is_direct()); + EXPECT_TRUE(info1.proxy_server() == L"foopy1:8080"); + + net::ProxyService service2(&resolver); + GURL test_url2("ftp://ftp.google.com"); + net::ProxyInfo info2; + rv = service2.ResolveProxy(test_url2, &info2, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info2.is_direct()); + EXPECT_TRUE(info2.proxy_server() == L""); + + net::ProxyService service3(&resolver); + GURL test_url3("https://webbranch.techcu.com"); + net::ProxyInfo info3; + rv = service3.ResolveProxy(test_url3, &info3, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info3.is_direct()); + EXPECT_TRUE(info3.proxy_server() == L"foopy2:8080"); + + resolver.config->proxy_server = L"foopy1:8080"; + net::ProxyService service4(&resolver); + GURL test_url4("www.microsoft.com"); + net::ProxyInfo info4; + rv = service4.ResolveProxy(test_url4, &info4, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info4.is_direct()); + EXPECT_TRUE(info4.proxy_server() == L"foopy1:8080"); +} |