diff options
author | joi@chromium.org <joi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-17 17:29:05 +0000 |
---|---|---|
committer | joi@chromium.org <joi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-17 17:29:05 +0000 |
commit | cfbcab964ac927102ce49c20ea1c4e1bcd667a04 (patch) | |
tree | ff34b602cae9072e4bd5b43f75d73abb91ba142b /net | |
parent | 0179f39b00af02a9abde26f5003f213e25a4444a (diff) | |
download | chromium_src-cfbcab964ac927102ce49c20ea1c4e1bcd667a04.zip chromium_src-cfbcab964ac927102ce49c20ea1c4e1bcd667a04.tar.gz chromium_src-cfbcab964ac927102ce49c20ea1c4e1bcd667a04.tar.bz2 |
Adds support for the DHCP portion of the WPAD (proxy auto-discovery) protocol.
This is Windows-only for now, and is disabled by default. Start
Chrome with the flag --enable-dhcp-wpad to enable the feature. See
discussion in comment on DhcpProxyScriptFetcherFactory for why this
needs to be done in a per-platform way rather than cross-platform.
The code is factored so that adding other platform implementations
will be straight forward.
Most of the implementation is stand-alone and extends the
ScriptProxyFetcher class hierarchy (and makes its interface slightly
more generic). The integration point into existing code is in
InitProxyResolver, which previously handled fallback from DNS
auto-detect to custom PAC URL and now does fallback from DHCP to DNS
to custom PAC URL.
BUG=18575
TEST=net_unittests has good coverage for the new and changed code, but
manual tests on a network with a PAC URL configured in DHCP are also
needed.
Review URL: http://codereview.chromium.org/6831025
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@85646 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
27 files changed, 2552 insertions, 165 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index 9979394..19e5da0 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -446,6 +446,11 @@ NET_ERROR(RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, -346) // headers are missing, so we're expecting additional frames to complete them. NET_ERROR(INCOMPLETE_SPDY_HEADERS, -347) +// No PAC URL configuration could be retrieved from DHCP. This can indicate +// either a failure to retrieve the DHCP configuration, or that there was no +// PAC URL configured in DHCP. +NET_ERROR(PAC_NOT_IN_DHCP, -348) + // The cache does not have the requested entry. NET_ERROR(CACHE_MISS, -400) diff --git a/net/base/net_log_event_type_list.h b/net/base/net_log_event_type_list.h index 69c49e3..6a8e6b4 100644 --- a/net/base/net_log_event_type_list.h +++ b/net/base/net_log_event_type_list.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -119,12 +119,12 @@ EVENT_TYPE(INIT_PROXY_RESOLVER_WAIT) // // The START event has the parameters: // { -// "url": <URL string of script being fetched>, +// "source": <String describing where PAC script comes from>, // } // // If the fetch failed, then the END phase has these parameters: // { -// "error_code": <Net error code integer>, +// "net_error": <Net error code integer>, // } EVENT_TYPE(INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT) @@ -133,7 +133,7 @@ EVENT_TYPE(INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT) // // If the parsing of the script failed, the END phase will have parameters: // { -// "error_code": <Net error code integer>, +// "net_error": <Net error code integer>, // } EVENT_TYPE(INIT_PROXY_RESOLVER_SET_PAC_SCRIPT) @@ -141,9 +141,9 @@ EVENT_TYPE(INIT_PROXY_RESOLVER_SET_PAC_SCRIPT) // configured script fetcher. (This indicates a configuration error). EVENT_TYPE(INIT_PROXY_RESOLVER_HAS_NO_FETCHER) -// This event is emitted after deciding to fall-back to the next PAC -// script in the list. -EVENT_TYPE(INIT_PROXY_RESOLVER_FALLING_BACK_TO_NEXT_PAC_URL) +// This event is emitted after deciding to fall-back to the next source +// of PAC scripts in the list. +EVENT_TYPE(INIT_PROXY_RESOLVER_FALLING_BACK_TO_NEXT_PAC_SOURCE) // ------------------------------------------------------------------------ // ProxyService diff --git a/net/net.gyp b/net/net.gyp index 4aeea97..979ff9e 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -407,6 +407,16 @@ 'http/proxy_client_socket.h', 'ocsp/nss_ocsp.cc', 'ocsp/nss_ocsp.h', + 'proxy/dhcp_proxy_script_fetcher.cc', + 'proxy/dhcp_proxy_script_fetcher.h', + 'proxy/dhcp_proxy_script_fetcher_factory.cc', + 'proxy/dhcp_proxy_script_fetcher_factory.h', + 'proxy/dhcp_proxy_script_adapter_fetcher_win.cc', + 'proxy/dhcp_proxy_script_adapter_fetcher_win.h', + 'proxy/dhcp_proxy_script_fetcher_win.cc', + 'proxy/dhcp_proxy_script_fetcher_win.h', + 'proxy/dhcpcsvc_init_win.cc', + 'proxy/dhcpcsvc_init_win.h', 'proxy/init_proxy_resolver.cc', 'proxy/init_proxy_resolver.h', 'proxy/multi_threaded_proxy_resolver.cc', @@ -913,6 +923,9 @@ 'http/mock_sspi_library_win.h', 'http/mock_sspi_library_win.cc', 'http/url_security_manager_unittest.cc', + 'proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc', + 'proxy/dhcp_proxy_script_fetcher_factory_unittest.cc', + 'proxy/dhcp_proxy_script_fetcher_win_unittest.cc', 'proxy/init_proxy_resolver_unittest.cc', 'proxy/multi_threaded_proxy_resolver_unittest.cc', 'proxy/network_delegate_error_observer_unittest.cc', @@ -1128,6 +1141,8 @@ 'disk_cache/disk_cache_test_util.h', 'proxy/mock_proxy_resolver.cc', 'proxy/mock_proxy_resolver.h', + 'proxy/mock_proxy_script_fetcher.cc', + 'proxy/mock_proxy_script_fetcher.h', 'proxy/proxy_config_service_common_unittest.cc', 'proxy/proxy_config_service_common_unittest.h', 'socket/socket_test_util.cc', diff --git a/net/proxy/dhcp_proxy_script_adapter_fetcher_win.cc b/net/proxy/dhcp_proxy_script_adapter_fetcher_win.cc new file mode 100644 index 0000000..f09537c --- /dev/null +++ b/net/proxy/dhcp_proxy_script_adapter_fetcher_win.cc @@ -0,0 +1,286 @@ +// Copyright (c) 2011 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/dhcp_proxy_script_adapter_fetcher_win.h" + +#include "base/message_loop_proxy.h" +#include "base/sys_string_conversions.h" +#include "base/task.h" +#include "base/threading/worker_pool.h" +#include "base/time.h" +#include "net/base/net_errors.h" +#include "net/proxy/dhcpcsvc_init_win.h" +#include "net/proxy/proxy_script_fetcher_impl.h" +#include "net/url_request/url_request_context.h" + +#include <windows.h> +#include <winsock2.h> +#include <dhcpcsdk.h> +#pragma comment(lib, "dhcpcsvc.lib") + +namespace { + +// Maximum amount of time to wait for response from the Win32 DHCP API. +const int kTimeoutMs = 2000; + +} // namespace + +namespace net { + +DhcpProxyScriptAdapterFetcher::DhcpProxyScriptAdapterFetcher( + URLRequestContext* url_request_context) + : state_(STATE_START), + result_(ERR_IO_PENDING), + callback_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST( + script_fetcher_callback_( + this, &DhcpProxyScriptAdapterFetcher::OnFetcherDone)), + url_request_context_(url_request_context) { +} + +DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() { + Cancel(); + + // The WeakPtr we passed to the worker thread may be destroyed on the + // worker thread. This detaches any outstanding WeakPtr state from + // the current thread. + base::SupportsWeakPtr<DhcpProxyScriptAdapterFetcher>::DetachFromThread(); +} + +void DhcpProxyScriptAdapterFetcher::Fetch( + const std::string& adapter_name, CompletionCallback* callback) { + DCHECK(CalledOnValidThread()); + DCHECK_EQ(state_, STATE_START); + result_ = ERR_IO_PENDING; + pac_script_ = string16(); + state_ = STATE_WAIT_DHCP; + callback_ = callback; + + wait_timer_.Start(ImplGetTimeout(), + this, &DhcpProxyScriptAdapterFetcher::OnTimeout); + worker_thread_ = ImplCreateWorkerThread(AsWeakPtr()); + worker_thread_->Start(adapter_name); +} + +void DhcpProxyScriptAdapterFetcher::Cancel() { + DCHECK(CalledOnValidThread()); + callback_ = NULL; + wait_timer_.Stop(); + script_fetcher_.reset(); + + switch (state_) { + case STATE_WAIT_DHCP: + // Nothing to do here, we let the worker thread run to completion, + // the task it posts back when it completes will check the state. + break; + case STATE_WAIT_URL: + break; + case STATE_START: + case STATE_FINISH: + case STATE_CANCEL: + break; + } + + if (state_ != STATE_FINISH) { + result_ = ERR_ABORTED; + state_ = STATE_CANCEL; + } +} + +bool DhcpProxyScriptAdapterFetcher::DidFinish() const { + DCHECK(CalledOnValidThread()); + return state_ == STATE_FINISH; +} + +int DhcpProxyScriptAdapterFetcher::GetResult() const { + DCHECK(CalledOnValidThread()); + return result_; +} + +string16 DhcpProxyScriptAdapterFetcher::GetPacScript() const { + DCHECK(CalledOnValidThread()); + return pac_script_; +} + +GURL DhcpProxyScriptAdapterFetcher::GetPacURL() const { + DCHECK(CalledOnValidThread()); + return pac_url_; +} + +DhcpProxyScriptAdapterFetcher::WorkerThread::WorkerThread( + const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner) + : owner_(owner), + origin_loop_(base::MessageLoopProxy::CreateForCurrentThread()) { +} + +DhcpProxyScriptAdapterFetcher::WorkerThread::~WorkerThread() { +} + +void DhcpProxyScriptAdapterFetcher::WorkerThread::Start( + const std::string& adapter_name) { + bool succeeded = base::WorkerPool::PostTask( + FROM_HERE, + NewRunnableMethod( + this, + &DhcpProxyScriptAdapterFetcher::WorkerThread::ThreadFunc, + adapter_name), + true); + DCHECK(succeeded); +} + +void DhcpProxyScriptAdapterFetcher::WorkerThread::ThreadFunc( + const std::string& adapter_name) { + std::string url = ImplGetPacURLFromDhcp(adapter_name); + + bool succeeded = origin_loop_->PostTask( + FROM_HERE, + NewRunnableMethod( + this, + &DhcpProxyScriptAdapterFetcher::WorkerThread::OnThreadDone, + url)); + DCHECK(succeeded); +} + +void DhcpProxyScriptAdapterFetcher::WorkerThread::OnThreadDone( + const std::string& url) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (owner_) + owner_->OnQueryDhcpDone(url); +} + +std::string + DhcpProxyScriptAdapterFetcher::WorkerThread::ImplGetPacURLFromDhcp( + const std::string& adapter_name) { + return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name); +} + +void DhcpProxyScriptAdapterFetcher::OnQueryDhcpDone( + const std::string& url) { + DCHECK(CalledOnValidThread()); + // Because we can't cancel the call to the Win32 API, we can expect + // it to finish while we are in a few different states. The expected + // one is WAIT_DHCP, but it could be in CANCEL if Cancel() was called, + // or FINISH if timeout occurred. + DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_CANCEL || + state_ == STATE_FINISH); + if (state_ != STATE_WAIT_DHCP) + return; + + wait_timer_.Stop(); + + pac_url_ = GURL(url); + if (pac_url_.is_empty() || !pac_url_.is_valid()) { + result_ = ERR_PAC_NOT_IN_DHCP; + TransitionToFinish(); + } else { + state_ = STATE_WAIT_URL; + script_fetcher_.reset(ImplCreateScriptFetcher()); + script_fetcher_->Fetch(pac_url_, &pac_script_, &script_fetcher_callback_); + } +} + +void DhcpProxyScriptAdapterFetcher::OnTimeout() { + DCHECK_EQ(state_, STATE_WAIT_DHCP); + result_ = ERR_TIMED_OUT; + TransitionToFinish(); +} + +void DhcpProxyScriptAdapterFetcher::OnFetcherDone(int result) { + DCHECK(CalledOnValidThread()); + DCHECK(state_ == STATE_WAIT_URL || state_ == STATE_CANCEL); + if (state_ == STATE_CANCEL) + return; + + // At this point, pac_script_ has already been written to. + script_fetcher_.reset(); + result_ = result; + TransitionToFinish(); +} + +void DhcpProxyScriptAdapterFetcher::TransitionToFinish() { + DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL); + state_ = STATE_FINISH; + callback_->Run(result_); + callback_ = NULL; +} + +ProxyScriptFetcher* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() { + return new ProxyScriptFetcherImpl(url_request_context_); +} + +DhcpProxyScriptAdapterFetcher::WorkerThread* + DhcpProxyScriptAdapterFetcher::ImplCreateWorkerThread( + const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner) { + return new WorkerThread(owner); +} + +base::TimeDelta DhcpProxyScriptAdapterFetcher::ImplGetTimeout() const { + return base::TimeDelta::FromMilliseconds(kTimeoutMs); +} + +// static +std::string DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp( + const std::string& adapter_name) { + EnsureDhcpcsvcInit(); + + std::wstring adapter_name_wide = base::SysMultiByteToWide(adapter_name, + CP_ACP); + + DHCPCAPI_PARAMS_ARRAY send_params = { 0, NULL }; + + BYTE option_data[] = { 1, 252 }; + DHCPCAPI_PARAMS wpad_params = { 0 }; + wpad_params.OptionId = 252; + wpad_params.IsVendor = FALSE; // Surprising, but intentional. + + DHCPCAPI_PARAMS_ARRAY request_params = { 0 }; + request_params.nParams = 1; + request_params.Params = &wpad_params; + + // The maximum message size is typically 4096 bytes on Windows per + // http://support.microsoft.com/kb/321592 + DWORD result_buffer_size = 4096; + scoped_ptr_malloc<BYTE> result_buffer; + int retry_count = 0; + DWORD res = NO_ERROR; + do { + result_buffer.reset(reinterpret_cast<BYTE*>(malloc(result_buffer_size))); + + // Note that while the DHCPCAPI_REQUEST_SYNCHRONOUS flag seems to indicate + // there might be an asynchronous mode, there seems to be (at least in + // terms of well-documented use of this API) only a synchronous mode, with + // an optional "async notifications later if the option changes" mode. + // Even IE9, which we hope to emulate as IE is the most widely deployed + // previous implementation of the DHCP aspect of WPAD and the only one + // on Windows (Konqueror is the other, on Linux), uses this API with the + // synchronous flag. There seem to be several Microsoft Knowledge Base + // articles about calls to this function failing when other flags are used + // (e.g. http://support.microsoft.com/kb/885270) so we won't take any + // chances on non-standard, poorly documented usage. + res = ::DhcpRequestParams(DHCPCAPI_REQUEST_SYNCHRONOUS, + NULL, + const_cast<LPWSTR>(adapter_name_wide.c_str()), + NULL, + send_params, request_params, + result_buffer.get(), &result_buffer_size, + NULL); + ++retry_count; + } while (res == ERROR_MORE_DATA && retry_count <= 3); + + if (res != NO_ERROR) { + NOTREACHED(); + } else if (wpad_params.nBytesData) { + // The result should be ASCII, not wide character. + DCHECK_EQ(strlen(reinterpret_cast<const char*>(wpad_params.Data)) + 1, + wpad_params.nBytesData); + // Return only up to the first null in case of embedded NULLs; if the + // server is giving us back a buffer with embedded NULLs, something is + // broken anyway. + return std::string(reinterpret_cast<const char *>(wpad_params.Data)); + } + + return ""; +} + +} // namespace net diff --git a/net/proxy/dhcp_proxy_script_adapter_fetcher_win.h b/net/proxy/dhcp_proxy_script_adapter_fetcher_win.h new file mode 100644 index 0000000..bab7468 --- /dev/null +++ b/net/proxy/dhcp_proxy_script_adapter_fetcher_win.h @@ -0,0 +1,198 @@ +// Copyright (c) 2011 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_DHCP_SCRIPT_ADAPTER_FETCHER_WIN_H_ +#define NET_PROXY_DHCP_SCRIPT_ADAPTER_FETCHER_WIN_H_ +#pragma once + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/string16.h" +#include "base/threading/non_thread_safe.h" +#include "base/timer.h" +#include "net/base/completion_callback.h" +#include "googleurl/src/gurl.h" + +namespace base { +class MessageLoopProxy; +} + +namespace net { + +class ProxyScriptFetcher; +class URLRequestContext; + +// For a given adapter, this class takes care of first doing a DHCP lookup +// to get the PAC URL, then if there is one, trying to fetch it. +class DhcpProxyScriptAdapterFetcher + : public base::SupportsWeakPtr<DhcpProxyScriptAdapterFetcher>, + public base::NonThreadSafe { + public: + // |url_request_context| must outlive DhcpProxyScriptAdapterFetcher. + explicit DhcpProxyScriptAdapterFetcher( + URLRequestContext* url_request_context); + virtual ~DhcpProxyScriptAdapterFetcher(); + + // Starts a fetch. On completion (but not cancellation), |callback| + // will be invoked with the network error indicating success or failure + // of fetching a DHCP-configured PAC file on this adapter. + // + // On completion, results can be obtained via |GetPacScript()|, |GetPacURL()|. + // + // You may only call Fetch() once on a given instance of + // DhcpProxyScriptAdapterFetcher. + virtual void Fetch(const std::string& adapter_name, + CompletionCallback* callback); + + // Cancels the fetch on this adapter. + virtual void Cancel(); + + // Returns true if in the FINISH state (not CANCEL). + virtual bool DidFinish() const; + + // Returns the network error indicating the result of the fetch. Will + // return IO_PENDING until the fetch is complete or cancelled. This is + // the same network error passed to the |callback| provided to |Fetch()|. + virtual int GetResult() const; + + // Returns the contents of the PAC file retrieved. Only valid if + // |IsComplete()| is true. Returns the empty string if |GetResult()| + // returns anything other than OK. + virtual string16 GetPacScript() const; + + // Returns the PAC URL retrieved from DHCP. Only guaranteed to be + // valid if |IsComplete()| is true. Returns an empty URL if no URL was + // configured in DHCP. May return a valid URL even if |result()| does + // not return OK (this would indicate that we found a URL configured in + // DHCP but failed to download it). + virtual GURL GetPacURL() const; + + // Returns the PAC URL configured in DHCP for the given |adapter_name|, or + // the empty string if none is configured. + // + // This function executes synchronously due to limitations of the Windows + // DHCP client API. + static std::string GetPacURLFromDhcp(const std::string& adapter_name); + + protected: + // This inner class is used to encapsulate the worker thread, which has + // only a weak reference back to the main object, so that the main object + // can be destroyed before the thread ends. This also keeps the main + // object completely thread safe and allows it to be non-refcounted. + class WorkerThread : public base::RefCountedThreadSafe<WorkerThread> { + public: + // Creates and initializes (but does not start) the worker thread. + explicit WorkerThread( + const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner); + virtual ~WorkerThread(); + + // Starts the worker thread, fetching information for |adapter_name| using + // |get_pac_from_url_func|. + void Start(const std::string& adapter_name); + + protected: + // Virtual method introduced to allow unit testing. + virtual std::string ImplGetPacURLFromDhcp(const std::string& adapter_name); + + private: + // This is the method that runs on the worker thread. + void ThreadFunc(const std::string& adapter_name); + + // Callback for the above; this executes back on the main thread, + // not the worker thread. + void OnThreadDone(const std::string& url); + + // All work except ThreadFunc and (sometimes) destruction should occur + // on the thread that constructs the object. + base::ThreadChecker thread_checker_; + + // May only be accessed on the thread that constructs the object. + base::WeakPtr<DhcpProxyScriptAdapterFetcher> owner_; + + // Used by worker thread to post a message back to the original + // thread. Fine to use a proxy since in the case where the original + // thread has gone away, that would mean the |owner_| object is gone + // anyway, so there is nobody to receive the result. + scoped_refptr<base::MessageLoopProxy> origin_loop_; + + DISALLOW_COPY_AND_ASSIGN(WorkerThread); + }; + + // Event/state transition handlers + void OnQueryDhcpDone(const std::string& url); + void OnTimeout(); + void OnFetcherDone(int result); + void TransitionToFinish(); + + // Virtual methods introduced to allow unit testing. + virtual ProxyScriptFetcher* ImplCreateScriptFetcher(); + virtual WorkerThread* ImplCreateWorkerThread( + const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner); + virtual base::TimeDelta ImplGetTimeout() const; + + // This is the state machine for fetching from a given adapter. + // + // The state machine goes from START->WAIT_DHCP when it starts + // a worker thread to fetch the PAC URL from DHCP. + // + // In state WAIT_DHCP, if the DHCP query finishes and has no URL, it + // moves to state FINISH. If there is a URL, it starts a + // ProxyScriptFetcher to fetch it and moves to state WAIT_URL. + // + // It goes from WAIT_URL->FINISH when the ProxyScriptFetcher completes. + // + // In state FINISH, completion is indicated to the outer class, with + // the results of the fetch if a PAC script was successfully fetched. + // + // In state WAIT_DHCP, our timeout occurring can push us to FINISH. + // + // In any state except FINISH, a call to Cancel() will move to state + // CANCEL and cause all outstanding work to be cancelled or its + // results ignored when available. + enum State { + STATE_START, + STATE_WAIT_DHCP, + STATE_WAIT_URL, + STATE_FINISH, + STATE_CANCEL, + }; + + // Current state of this state machine. + State state_; + + // A network error indicating result of operation. + int result_; + + // Empty string or the PAC script downloaded. + string16 pac_script_; + + // Empty URL or the PAC URL configured in DHCP. + GURL pac_url_; + + // Callback to let our client know we're done. Invalid in states + // START, FINISH and CANCEL. + CompletionCallback* callback_; + + // Container for our worker thread. NULL if not currently running. + scoped_refptr<WorkerThread> worker_thread_; + + // Fetcher to retrieve PAC files once URL is known. + scoped_ptr<ProxyScriptFetcher> script_fetcher_; + + // Callback from the script fetcher. + CompletionCallbackImpl<DhcpProxyScriptAdapterFetcher> + script_fetcher_callback_; + + // Implements a timeout on the call to the Win32 DHCP API. + base::OneShotTimer<DhcpProxyScriptAdapterFetcher> wait_timer_; + + scoped_refptr<URLRequestContext> url_request_context_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(DhcpProxyScriptAdapterFetcher); +}; + +} // namespace net + +#endif // NET_PROXY_DHCP_SCRIPT_ADAPTER_FETCHER_WIN_H_ diff --git a/net/proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc b/net/proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc new file mode 100644 index 0000000..b4b05f9 --- /dev/null +++ b/net/proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc @@ -0,0 +1,300 @@ +// Copyright (c) 2011 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/dhcp_proxy_script_adapter_fetcher_win.h" + +#include "base/perftimer.h" +#include "base/synchronization/waitable_event.h" +#include "base/timer.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/proxy/mock_proxy_script_fetcher.h" +#include "net/proxy/proxy_script_fetcher_impl.h" +#include "net/test/test_server.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +const char* const kPacUrl = "http://pacserver/script.pac"; + +// In net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc there are a few +// tests that exercise DhcpProxyScriptAdapterFetcher end-to-end along with +// DhcpProxyScriptFetcherWin, i.e. it tests the end-to-end usage of Win32 +// APIs and the network. In this file we test only by stubbing out +// functionality. + +// Version of DhcpProxyScriptAdapterFetcher that mocks out dependencies +// to allow unit testing. +class MockDhcpProxyScriptAdapterFetcher + : public DhcpProxyScriptAdapterFetcher { + public: + explicit MockDhcpProxyScriptAdapterFetcher(URLRequestContext* context) + : DhcpProxyScriptAdapterFetcher(context), + dhcp_delay_ms_(1), + timeout_ms_(100), + configured_url_(kPacUrl), + fetcher_delay_ms_(1), + fetcher_result_(OK), + pac_script_("bingo") { + } + + void Cancel() { + DhcpProxyScriptAdapterFetcher::Cancel(); + fetcher_ = NULL; + } + + virtual ProxyScriptFetcher* ImplCreateScriptFetcher() OVERRIDE { + // We don't maintain ownership of the fetcher, it is transferred to + // the caller. + fetcher_ = new MockProxyScriptFetcher(); + if (fetcher_delay_ms_ != -1) { + fetcher_timer_.Start( + base::TimeDelta::FromMilliseconds(fetcher_delay_ms_), + this, &MockDhcpProxyScriptAdapterFetcher::OnFetcherTimer); + } + return fetcher_; + } + + class DelayingWorkerThread : public WorkerThread { + public: + explicit DelayingWorkerThread( + const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner) + : WorkerThread(owner), + test_finished_event_(true, false) { + } + + std::string ImplGetPacURLFromDhcp( + const std::string& adapter_name) OVERRIDE { + PerfTimer timer; + test_finished_event_.TimedWait(dhcp_delay_); + return configured_url_; + } + + base::WaitableEvent test_finished_event_; + TimeDelta dhcp_delay_; + std::string configured_url_; + }; + + virtual WorkerThread* ImplCreateWorkerThread( + const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner) OVERRIDE { + worker_thread_ = new DelayingWorkerThread(owner); + worker_thread_->dhcp_delay_ = TimeDelta::FromMilliseconds(dhcp_delay_ms_); + worker_thread_->configured_url_ = configured_url_; + return worker_thread_; + } + + // Use a shorter timeout so tests can finish more quickly. + virtual base::TimeDelta ImplGetTimeout() const OVERRIDE { + return base::TimeDelta::FromMilliseconds(timeout_ms_); + } + + void OnFetcherTimer() { + // Note that there is an assumption by this mock implementation that + // DhcpProxyScriptAdapterFetcher::Fetch will call ImplCreateScriptFetcher + // and call Fetch on the fetcher before the message loop is re-entered. + // This holds true today, but if you hit this DCHECK the problem can + // possibly be resolved by having a separate subclass of + // MockProxyScriptFetcher that adds the delay internally (instead of + // the simple approach currently used in ImplCreateScriptFetcher above). + DCHECK(fetcher_ && fetcher_->has_pending_request()); + fetcher_->NotifyFetchCompletion(fetcher_result_, pac_script_); + fetcher_ = NULL; + } + + bool IsWaitingForFetcher() const { + return state_ == STATE_WAIT_URL; + } + + bool WasCancelled() const { + return state_ == STATE_CANCEL; + } + + void FinishTest() { + DCHECK(worker_thread_); + worker_thread_->test_finished_event_.Signal(); + } + + int dhcp_delay_ms_; + int timeout_ms_; + std::string configured_url_; + int fetcher_delay_ms_; + int fetcher_result_; + std::string pac_script_; + MockProxyScriptFetcher* fetcher_; + base::OneShotTimer<MockDhcpProxyScriptAdapterFetcher> fetcher_timer_; + scoped_refptr<DelayingWorkerThread> worker_thread_; +}; + +class FetcherClient { + public: + FetcherClient() + : url_request_context_(new TestURLRequestContext()), + fetcher_( + new MockDhcpProxyScriptAdapterFetcher(url_request_context_.get())) { + } + + void WaitForResult(int expected_error) { + EXPECT_EQ(expected_error, callback_.WaitForResult()); + } + + void RunTest() { + fetcher_->Fetch("adapter name", &callback_); + } + + void FinishTestAllowCleanup() { + fetcher_->FinishTest(); + MessageLoop::current()->RunAllPending(); + } + + TestCompletionCallback callback_; + scoped_refptr<URLRequestContext> url_request_context_; + scoped_ptr<MockDhcpProxyScriptAdapterFetcher> fetcher_; + string16 pac_text_; +}; + +TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLNotInDhcp) { + FetcherClient client; + client.fetcher_->configured_url_ = ""; + client.RunTest(); + client.WaitForResult(ERR_PAC_NOT_IN_DHCP); + ASSERT_TRUE(client.fetcher_->DidFinish()); + EXPECT_EQ(ERR_PAC_NOT_IN_DHCP, client.fetcher_->GetResult()); + EXPECT_EQ(string16(L""), client.fetcher_->GetPacScript()); +} + +TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLInDhcp) { + FetcherClient client; + client.RunTest(); + client.WaitForResult(OK); + ASSERT_TRUE(client.fetcher_->DidFinish()); + EXPECT_EQ(OK, client.fetcher_->GetResult()); + EXPECT_EQ(string16(L"bingo"), client.fetcher_->GetPacScript()); + EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); +} + +TEST(DhcpProxyScriptAdapterFetcher, TimeoutDuringDhcp) { + // Does a Fetch() with a long enough delay on accessing DHCP that the + // fetcher should time out. This is to test a case manual testing found, + // where under certain circumstances (e.g. adapter enabled for DHCP and + // needs to retrieve its configuration from DHCP, but no DHCP server + // present on the network) accessing DHCP can take on the order of tens + // of seconds. + FetcherClient client; + client.fetcher_->dhcp_delay_ms_ = 20 * 1000; + client.fetcher_->timeout_ms_ = 25; + + PerfTimer timer; + client.RunTest(); + client.WaitForResult(ERR_TIMED_OUT); + + // The timeout should occur within about 25 ms, way before the 20s set as + // the API delay above. + ASSERT_GT(base::TimeDelta::FromMilliseconds(35), timer.Elapsed()); + ASSERT_TRUE(client.fetcher_->DidFinish()); + EXPECT_EQ(ERR_TIMED_OUT, client.fetcher_->GetResult()); + EXPECT_EQ(string16(L""), client.fetcher_->GetPacScript()); + EXPECT_EQ(GURL(), client.fetcher_->GetPacURL()); + client.FinishTestAllowCleanup(); +} + +TEST(DhcpProxyScriptAdapterFetcher, CancelWhileDhcp) { + FetcherClient client; + client.fetcher_->dhcp_delay_ms_ = 10; + client.RunTest(); + client.fetcher_->Cancel(); + MessageLoop::current()->RunAllPending(); + ASSERT_FALSE(client.fetcher_->DidFinish()); + ASSERT_TRUE(client.fetcher_->WasCancelled()); + EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult()); + EXPECT_EQ(string16(L""), client.fetcher_->GetPacScript()); + EXPECT_EQ(GURL(), client.fetcher_->GetPacURL()); + client.FinishTestAllowCleanup(); +} + +TEST(DhcpProxyScriptAdapterFetcher, CancelWhileFetcher) { + FetcherClient client; + // This causes the mock fetcher not to pretend the + // fetcher finishes after a timeout. + client.fetcher_->fetcher_delay_ms_ = -1; + client.RunTest(); + int max_loops = 4; + while (!client.fetcher_->IsWaitingForFetcher() && max_loops--) { + base::PlatformThread::Sleep(10); + MessageLoop::current()->RunAllPending(); + } + client.fetcher_->Cancel(); + MessageLoop::current()->RunAllPending(); + ASSERT_FALSE(client.fetcher_->DidFinish()); + ASSERT_TRUE(client.fetcher_->WasCancelled()); + EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult()); + EXPECT_EQ(string16(L""), client.fetcher_->GetPacScript()); + // GetPacURL() still returns the URL fetched in this case. + EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); + client.FinishTestAllowCleanup(); +} + +TEST(DhcpProxyScriptAdapterFetcher, CancelAtCompletion) { + FetcherClient client; + client.RunTest(); + client.WaitForResult(OK); + client.fetcher_->Cancel(); + // Canceling after you're done should have no effect, so these + // are identical expectations to the NormalCaseURLInDhcp test. + ASSERT_TRUE(client.fetcher_->DidFinish()); + EXPECT_EQ(OK, client.fetcher_->GetResult()); + EXPECT_EQ(string16(L"bingo"), client.fetcher_->GetPacScript()); + EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); + client.FinishTestAllowCleanup(); +} + +// Does a real fetch on a mock DHCP configuration. +class MockDhcpRealFetchProxyScriptAdapterFetcher + : public MockDhcpProxyScriptAdapterFetcher { + public: + explicit MockDhcpRealFetchProxyScriptAdapterFetcher( + URLRequestContext* context) + : MockDhcpProxyScriptAdapterFetcher(context), + url_request_context_(context) { + } + + // Returns a real proxy script fetcher. + ProxyScriptFetcher* ImplCreateScriptFetcher() OVERRIDE { + ProxyScriptFetcher* fetcher = + new ProxyScriptFetcherImpl(url_request_context_); + return fetcher; + } + + URLRequestContext* url_request_context_; +}; + +TEST(DhcpProxyScriptAdapterFetcher, MockDhcpRealFetch) { + TestServer test_server( + TestServer::TYPE_HTTP, + FilePath(FILE_PATH_LITERAL("net/data/proxy_script_fetcher_unittest"))); + ASSERT_TRUE(test_server.Start()); + + GURL configured_url = test_server.GetURL("files/downloadable.pac"); + + FetcherClient client; + scoped_refptr<URLRequestContext> url_request_context( + new TestURLRequestContext()); + client.fetcher_.reset( + new MockDhcpRealFetchProxyScriptAdapterFetcher( + url_request_context.get())); + client.fetcher_->configured_url_ = configured_url.spec(); + client.RunTest(); + client.WaitForResult(OK); + ASSERT_TRUE(client.fetcher_->DidFinish()); + EXPECT_EQ(OK, client.fetcher_->GetResult()); + EXPECT_EQ(string16(L"-downloadable.pac-\n"), client.fetcher_->GetPacScript()); + EXPECT_EQ(configured_url, + client.fetcher_->GetPacURL()); +} + +} // namespace + +} // namespace net diff --git a/net/proxy/dhcp_proxy_script_fetcher.cc b/net/proxy/dhcp_proxy_script_fetcher.cc new file mode 100644 index 0000000..3797ecd --- /dev/null +++ b/net/proxy/dhcp_proxy_script_fetcher.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2011 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/dhcp_proxy_script_fetcher.h" + +#include "net/base/net_errors.h" + +namespace net { + +DhcpProxyScriptFetcher::DhcpProxyScriptFetcher() { +} + +DhcpProxyScriptFetcher::~DhcpProxyScriptFetcher() { +} + +std::string DhcpProxyScriptFetcher::GetFetcherName() const { + return ""; +} + +DoNothingDhcpProxyScriptFetcher::DoNothingDhcpProxyScriptFetcher() { +} + +DoNothingDhcpProxyScriptFetcher::~DoNothingDhcpProxyScriptFetcher() { +} + +int DoNothingDhcpProxyScriptFetcher::Fetch(string16* utf16_text, + CompletionCallback* callback) { + return ERR_NOT_IMPLEMENTED; +} + +void DoNothingDhcpProxyScriptFetcher::Cancel() { +} + +const GURL& DoNothingDhcpProxyScriptFetcher::GetPacURL() const { + return gurl_; +} + +} // namespace net diff --git a/net/proxy/dhcp_proxy_script_fetcher.h b/net/proxy/dhcp_proxy_script_fetcher.h new file mode 100644 index 0000000..adb011d --- /dev/null +++ b/net/proxy/dhcp_proxy_script_fetcher.h @@ -0,0 +1,98 @@ +// Copyright (c) 2011 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_DHCP_SCRIPT_FETCHER_H_ +#define NET_PROXY_DHCP_SCRIPT_FETCHER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/string16.h" +#include "net/base/completion_callback.h" +#include "net/proxy/proxy_script_fetcher.h" +#include "net/url_request/url_request_context.h" + +namespace net { + +// Interface for classes that can fetch a proxy script as configured via DHCP. +// +// The Fetch method on this interface tries to retrieve the most appropriate +// PAC script configured via DHCP. +// +// Normally there are zero or one DHCP scripts configured, but in the +// presence of multiple adapters with DHCP enabled, the fetcher resolves +// which PAC script to use if one or more are available. +class DhcpProxyScriptFetcher { + public: + // Destruction should cancel any outstanding requests. + virtual ~DhcpProxyScriptFetcher(); + + // Attempts to retrieve the most appropriate PAC script configured via DHCP, + // and invokes |callback| on completion. + // + // Returns OK on success, otherwise the error code. If the return code is + // ERR_IO_PENDING, then the request completes asynchronously, and |callback| + // will be invoked later with the final error code. + // + // After synchronous or asynchronous completion with a result code of OK, + // |*utf16_text| is filled with the response. On failure, the result text is + // an empty string, and the result code is a network error. Some special + // network errors that may occur are: + // + // ERR_PAC_NOT_IN_DHCP -- no script configured in DHCP. + // + // The following all indicate there was one or more script configured + // in DHCP but all failed to download, and the error for the most + // preferred adapter that had a script configured was what the error + // code says: + // + // ERR_TIMED_OUT -- fetch took too long to complete. + // ERR_FILE_TOO_BIG -- response body was too large. + // ERR_PAC_STATUS_NOT_OK -- script failed to download. + // ERR_NOT_IMPLEMENTED -- script required authentication. + // + // If the request is cancelled (either using the "Cancel()" method or by + // deleting |this|), then no callback is invoked. + // + // Only one fetch is allowed to be outstanding at a time. + virtual int Fetch(string16* utf16_text, + CompletionCallback* callback) = 0; + + // Aborts the in-progress fetch (if any). + virtual void Cancel() = 0; + + // After successful completion of |Fetch()|, this will return the URL + // retrieved from DHCP. It is reset if/when |Fetch()| is called again. + virtual const GURL& GetPacURL() const = 0; + + // Intended for unit tests only, so they can test that factories return + // the right types under given circumstances. + virtual std::string GetFetcherName() const; + + protected: + DhcpProxyScriptFetcher(); + + private: + DISALLOW_COPY_AND_ASSIGN(DhcpProxyScriptFetcher); +}; + +// A do-nothing retriever, always returns synchronously with +// ERR_NOT_IMPLEMENTED result and empty text. +class DoNothingDhcpProxyScriptFetcher : public DhcpProxyScriptFetcher { + public: + DoNothingDhcpProxyScriptFetcher(); + virtual ~DoNothingDhcpProxyScriptFetcher(); + + virtual int Fetch(string16* utf16_text, + CompletionCallback* callback) OVERRIDE; + virtual void Cancel() OVERRIDE; + virtual const GURL& GetPacURL() const OVERRIDE; + private: + GURL gurl_; + DISALLOW_COPY_AND_ASSIGN(DoNothingDhcpProxyScriptFetcher); +}; + +} // namespace net + +#endif // NET_PROXY_DHCP_SCRIPT_FETCHER_H_ diff --git a/net/proxy/dhcp_proxy_script_fetcher_factory.cc b/net/proxy/dhcp_proxy_script_fetcher_factory.cc new file mode 100644 index 0000000..dfdf58e3 --- /dev/null +++ b/net/proxy/dhcp_proxy_script_fetcher_factory.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2011 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/dhcp_proxy_script_fetcher_factory.h" + +#include "net/base/net_errors.h" +#include "net/proxy/dhcp_proxy_script_fetcher.h" + +#if defined(OS_WIN) +#include "net/proxy/dhcp_proxy_script_fetcher_win.h" +#endif + +namespace net { + +DhcpProxyScriptFetcherFactory::DhcpProxyScriptFetcherFactory() { + // TODO(joi): Change this default, and the comment on |set_enabled()|, + // when the time is right. + set_enabled(false); +} + +DhcpProxyScriptFetcher* DhcpProxyScriptFetcherFactory::Create( + URLRequestContext* context) { + if (!feature_enabled_) { + return new DoNothingDhcpProxyScriptFetcher(); + } else { + DCHECK(IsSupported()); + DhcpProxyScriptFetcher* ret = NULL; +#if defined(OS_WIN) + ret = new DhcpProxyScriptFetcherWin(context); +#endif + DCHECK(ret); + return ret; + } +} + +void DhcpProxyScriptFetcherFactory::set_enabled(bool enabled) { + if (IsSupported()) { + feature_enabled_ = enabled; + } +} + +bool DhcpProxyScriptFetcherFactory::enabled() const { + return feature_enabled_; +} + +// static +bool DhcpProxyScriptFetcherFactory::IsSupported() { +#if defined(OS_WIN) + return true; +#else + return false; +#endif +} + +} // namespace net diff --git a/net/proxy/dhcp_proxy_script_fetcher_factory.h b/net/proxy/dhcp_proxy_script_fetcher_factory.h new file mode 100644 index 0000000..20e4104 --- /dev/null +++ b/net/proxy/dhcp_proxy_script_fetcher_factory.h @@ -0,0 +1,68 @@ +// Copyright (c) 2011 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_DHCP_SCRIPT_FETCHER_FACTORY_H_ +#define NET_PROXY_DHCP_SCRIPT_FETCHER_FACTORY_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/memory/singleton.h" +#include "net/base/completion_callback.h" +#include "net/url_request/url_request_context.h" + +namespace net { + +class DhcpProxyScriptFetcher; + +// Factory object for creating the appropriate concrete base class of +// DhcpProxyScriptFetcher for your operating system and settings. +// +// You might think we could just implement a DHCP client at the protocol +// level and have cross-platform support for retrieving PAC configuration +// from DHCP, but unfortunately the DHCP protocol assumes there is a single +// client per machine (specifically per network interface card), and there +// is an implicit state machine between the client and server, so adding a +// second client to the machine would not be advisable (see e.g. some +// discussion of what can happen at this URL: +// http://www.net.princeton.edu/multi-dhcp-one-interface-handling.html). +// +// Therefore, we have platform-specific implementations, and so we use +// this factory to select the right one. +class DhcpProxyScriptFetcherFactory { + public: + // Creates a new factory object with default settings. + DhcpProxyScriptFetcherFactory(); + + // Ownership is transferred to the caller. url_request_context must be valid + // and its lifetime must exceed that of the returned DhcpProxyScriptFetcher. + // + // Note that while a request is in progress, the fetcher may be holding a + // reference to |url_request_context|. Be careful not to create cycles + // between the fetcher and the context; you can break such cycles by calling + // Cancel(). + DhcpProxyScriptFetcher* Create(URLRequestContext* url_request_context); + + // Attempts to enable/disable the DHCP WPAD feature. Does nothing + // if |IsSupported()| returns false. + // + // The current default is |enabled() == false|. + void set_enabled(bool enabled); + + // Returns true if the DHCP WPAD feature is enabled. Always returns + // false if |IsSupported()| is false. + bool enabled() const; + + // Returns true if the DHCP WPAD feature is supported on the current + // operating system. + static bool IsSupported(); + + protected: + bool feature_enabled_; + + DISALLOW_COPY_AND_ASSIGN(DhcpProxyScriptFetcherFactory); +}; + +} // namespace net + +#endif // NET_PROXY_DHCP_SCRIPT_FETCHER_FACTORY_H_ diff --git a/net/proxy/dhcp_proxy_script_fetcher_factory_unittest.cc b/net/proxy/dhcp_proxy_script_fetcher_factory_unittest.cc new file mode 100644 index 0000000..2c29120 --- /dev/null +++ b/net/proxy/dhcp_proxy_script_fetcher_factory_unittest.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2011 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/dhcp_proxy_script_fetcher.h" +#include "net/proxy/dhcp_proxy_script_fetcher_factory.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h"
+ +namespace net { +namespace { + +TEST(DhcpProxyScriptFetcherFactoryTest, DoNothingWhenDisabled) { + DhcpProxyScriptFetcherFactory factory; + // Non-do-nothing fetchers would DCHECK on the NULL pointer. + DhcpProxyScriptFetcher* fetcher = factory.Create(NULL); + ASSERT_TRUE(fetcher); + EXPECT_EQ("", fetcher->GetFetcherName()); +} + +#if defined(OS_WIN) +TEST(DhcpProxyScriptFetcherFactoryTest, WindowsFetcherOnWindows) { + DhcpProxyScriptFetcherFactory factory; + factory.set_enabled(true); + + scoped_refptr<TestURLRequestContext> context(new TestURLRequestContext()); + DhcpProxyScriptFetcher* fetcher = factory.Create(context); + ASSERT_TRUE(fetcher); + EXPECT_EQ("win", fetcher->GetFetcherName()); +} +#endif // defined(OS_WIN) + +TEST(DhcpProxyScriptFetcherFactoryTest, IsSupported) { +#if defined(OS_WIN) + ASSERT_TRUE(DhcpProxyScriptFetcherFactory::IsSupported()); +#else + ASSERT_FALSE(DhcpProxyScriptFetcherFactory::IsSupported()); +#endif // defined(OS_WIN) +} + +TEST(DhcpProxyScriptFetcherFactoryTest, SetEnabled) { + DhcpProxyScriptFetcherFactory factory; + EXPECT_FALSE(factory.enabled()); + + factory.set_enabled(false); + EXPECT_FALSE(factory.enabled()); + + factory.set_enabled(true); +#if defined(OS_WIN) + EXPECT_TRUE(factory.enabled()); +#else + EXPECT_FALSE(factory.enabled()); +#endif // defined(OS_WIN) +} + +} // namespace +} // namespace net diff --git a/net/proxy/dhcp_proxy_script_fetcher_win.cc b/net/proxy/dhcp_proxy_script_fetcher_win.cc new file mode 100644 index 0000000..35109f6 --- /dev/null +++ b/net/proxy/dhcp_proxy_script_fetcher_win.cc @@ -0,0 +1,246 @@ +// Copyright (c) 2011 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/dhcp_proxy_script_fetcher_win.h" + +#include "net/base/net_errors.h" +#include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h" + +#include <winsock2.h> +#include <iphlpapi.h> +#pragma comment(lib, "iphlpapi.lib") + +namespace { + +// How long to wait at maximum after we get results (a PAC file or +// knowledge that no PAC file is configured) from whichever network +// adapter finishes first. +const int kMaxWaitAfterFirstResultMs = 400; + +} // namespace + +namespace net { + +DhcpProxyScriptFetcherWin::DhcpProxyScriptFetcherWin( + URLRequestContext* url_request_context) + : state_(STATE_START), + ALLOW_THIS_IN_INITIALIZER_LIST(fetcher_callback_( + this, &DhcpProxyScriptFetcherWin::OnFetcherDone)), + num_pending_fetchers_(0), + url_request_context_(url_request_context) { + DCHECK(url_request_context_); +} + +DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() { + Cancel(); +} + +int DhcpProxyScriptFetcherWin::Fetch(string16* utf16_text, + CompletionCallback* callback) { + DCHECK(CalledOnValidThread()); + if (state_ != STATE_START && state_ != STATE_DONE) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + + std::set<std::string> adapter_names; + if (!ImplGetCandidateAdapterNames(&adapter_names)) { + return ERR_UNEXPECTED; + } + if (adapter_names.empty()) { + return ERR_PAC_NOT_IN_DHCP; + } + + state_ = STATE_NO_RESULTS; + + client_callback_ = callback; + destination_string_ = utf16_text; + + for (std::set<std::string>::iterator it = adapter_names.begin(); + it != adapter_names.end(); + ++it) { + DhcpProxyScriptAdapterFetcher* fetcher(ImplCreateAdapterFetcher()); + fetcher->Fetch(*it, &fetcher_callback_); + fetchers_.push_back(fetcher); + } + num_pending_fetchers_ = fetchers_.size(); + + return ERR_IO_PENDING; +} + +void DhcpProxyScriptFetcherWin::Cancel() { + DCHECK(CalledOnValidThread()); + + if (state_ != STATE_DONE) { + wait_timer_.Stop(); + state_ = STATE_DONE; + + for (FetcherVector::iterator it = fetchers_.begin(); + it != fetchers_.end(); + ++it) { + (*it)->Cancel(); + } + + fetchers_.reset(); + } +} + +std::string DhcpProxyScriptFetcherWin::GetFetcherName() const { + DCHECK(CalledOnValidThread()); + return "win"; +} + +const GURL& DhcpProxyScriptFetcherWin::GetPacURL() const { + DCHECK(CalledOnValidThread()); + DCHECK_EQ(state_, STATE_DONE); + + return pac_url_; +} + +void DhcpProxyScriptFetcherWin::OnFetcherDone(int result) { + DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS); + + if (--num_pending_fetchers_ == 0) { + TransitionToDone(); + return; + } + + // If the only pending adapters are those less preferred than one + // with a valid PAC script, we do not need to wait any longer. + for (FetcherVector::iterator it = fetchers_.begin(); + it != fetchers_.end(); + ++it) { + bool did_finish = (*it)->DidFinish(); + int result = (*it)->GetResult(); + if (did_finish && result == OK) { + TransitionToDone(); + return; + } + if (!did_finish || result != ERR_PAC_NOT_IN_DHCP) { + break; + } + } + + // Once we have a single result, we set a maximum on how long to wait + // for the rest of the results. + if (state_ == STATE_NO_RESULTS) { + state_ = STATE_SOME_RESULTS; + wait_timer_.Start( + base::TimeDelta::FromMilliseconds(ImplGetMaxWaitMs()), + this, &DhcpProxyScriptFetcherWin::OnWaitTimer); + } +} + +void DhcpProxyScriptFetcherWin::OnWaitTimer() { + DCHECK_EQ(state_, STATE_SOME_RESULTS); + TransitionToDone(); +} + +void DhcpProxyScriptFetcherWin::TransitionToDone() { + DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS); + + // Should have returned immediately at Fetch() if no adapters to check. + DCHECK(!fetchers_.empty()); + + // Scan twice for the result; once through the whole list for success, + // then if no success, return result for most preferred network adapter, + // preferring "real" network errors to the ERR_PAC_NOT_IN_DHCP error. + // Default to ERR_ABORTED if no fetcher completed. + int result = ERR_ABORTED; + for (FetcherVector::iterator it = fetchers_.begin(); + it != fetchers_.end(); + ++it) { + if ((*it)->DidFinish() && (*it)->GetResult() == OK) { + result = OK; + *destination_string_ = (*it)->GetPacScript(); + pac_url_ = (*it)->GetPacURL(); + break; + } + } + if (result != OK) { + destination_string_->clear(); + for (FetcherVector::iterator it = fetchers_.begin(); + it != fetchers_.end(); + ++it) { + if ((*it)->DidFinish()) { + result = (*it)->GetResult(); + if (result != ERR_PAC_NOT_IN_DHCP) { + break; + } + } + } + } + + Cancel(); + DCHECK_EQ(state_, STATE_DONE); + DCHECK(fetchers_.empty()); + + client_callback_->Run(result); +} + +DhcpProxyScriptAdapterFetcher* + DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() { + return new DhcpProxyScriptAdapterFetcher(url_request_context_); +} + +bool DhcpProxyScriptFetcherWin::ImplGetCandidateAdapterNames( + std::set<std::string>* adapter_names) { + return GetCandidateAdapterNames(adapter_names); +} + +int DhcpProxyScriptFetcherWin::ImplGetMaxWaitMs() { + return kMaxWaitAfterFirstResultMs; +} + +bool DhcpProxyScriptFetcherWin::GetCandidateAdapterNames( + std::set<std::string>* adapter_names) { + DCHECK(adapter_names); + adapter_names->clear(); + + // The GetAdaptersAddresses MSDN page recommends using a size of 15000 to + // avoid reallocation. + ULONG adapters_size = 15000; + scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> adapters; + ULONG error = ERROR_SUCCESS; + int num_tries = 0; + do { + adapters.reset( + reinterpret_cast<IP_ADAPTER_ADDRESSES*>(malloc(adapters_size))); + // Return only unicast addresses, and skip information we do not need. + error = GetAdaptersAddresses(AF_UNSPEC, + GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_MULTICAST | + GAA_FLAG_SKIP_DNS_SERVER | + GAA_FLAG_SKIP_FRIENDLY_NAME, + NULL, + adapters.get(), + &adapters_size); + ++num_tries; + } while (error == ERROR_BUFFER_OVERFLOW && num_tries <= 3); + + if (error == ERROR_NO_DATA) { + // There are no adapters that we care about. + return true; + } + + if (error != ERROR_SUCCESS) { + LOG(WARNING) << "Unexpected error retrieving WPAD configuration from DHCP."; + return false; + } + + IP_ADAPTER_ADDRESSES* adapter = NULL; + for (adapter = adapters.get(); adapter; adapter = adapter->Next) { + if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) + continue; + if ((adapter->Flags & IP_ADAPTER_DHCP_ENABLED) == 0) + continue; + + DCHECK(adapter->AdapterName); + adapter_names->insert(adapter->AdapterName); + } + + return true; +} + +} // namespace net diff --git a/net/proxy/dhcp_proxy_script_fetcher_win.h b/net/proxy/dhcp_proxy_script_fetcher_win.h new file mode 100644 index 0000000..3783971 --- /dev/null +++ b/net/proxy/dhcp_proxy_script_fetcher_win.h @@ -0,0 +1,123 @@ +// Copyright (c) 2011 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_DHCP_PROXY_SCRIPT_FETCHER_WIN_H_ +#define NET_PROXY_DHCP_PROXY_SCRIPT_FETCHER_WIN_H_ +#pragma once + +#include <set> +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/threading/non_thread_safe.h" +#include "base/timer.h" +#include "net/proxy/dhcp_proxy_script_fetcher.h" + +namespace net { + +class DhcpProxyScriptAdapterFetcher; +class URLRequestContext; + +// Windows-specific implementation. +class DhcpProxyScriptFetcherWin + : public DhcpProxyScriptFetcher, + public base::NonThreadSafe { + public: + // Creates a DhcpProxyScriptFetcherWin that issues requests through + // |url_request_context|. |url_request_context| must remain valid for + // the lifetime of DhcpProxyScriptFetcherWin. + explicit DhcpProxyScriptFetcherWin(URLRequestContext* url_request_context); + virtual ~DhcpProxyScriptFetcherWin(); + + // DhcpProxyScriptFetcher implementation. + int Fetch(string16* utf16_text, CompletionCallback* callback) OVERRIDE; + void Cancel() OVERRIDE; + const GURL& GetPacURL() const OVERRIDE; + std::string GetFetcherName() const OVERRIDE; + + // Sets |adapter_names| to contain the name of each network adapter on + // this machine that has DHCP enabled and is not a loop-back adapter. Returns + // false on error. + static bool GetCandidateAdapterNames(std::set<std::string>* adapter_names); + + protected: + // Event/state transition handlers + void OnFetcherDone(int result); + void OnWaitTimer(); + void TransitionToDone(); + + // Virtual methods introduced to allow unit testing. + virtual DhcpProxyScriptAdapterFetcher* ImplCreateAdapterFetcher(); + virtual bool ImplGetCandidateAdapterNames( + std::set<std::string>* adapter_names); + virtual int ImplGetMaxWaitMs(); + + // This is the outer state machine for fetching PAC configuration from + // DHCP. It relies for sub-states on the state machine of the + // DhcpProxyScriptAdapterFetcher class. + // + // The goal of the implementation is to the following work in parallel + // for all network adapters that are using DHCP: + // a) Try to get the PAC URL configured in DHCP; + // b) If one is configured, try to fetch the PAC URL. + // c) Once this is done for all adapters, or a timeout has passed after + // it has completed for the fastest adapter, return the PAC file + // available for the most preferred network adapter, if any. + // + // The state machine goes from START->NO_RESULTS when it creates + // and starts an DhcpProxyScriptAdapterFetcher for each adapter. It goes + // from NO_RESULTS->SOME_RESULTS when it gets the first result; at this + // point a wait timer is started. It goes from SOME_RESULTS->DONE in + // two cases: All results are known, or the wait timer expired. A call + // to Cancel() will also go straight to DONE from any state. Any + // way the DONE state is entered, we will at that point cancel any + // outstanding work and return the best known PAC script or the empty + // string. + // + // The state machine is reset for each Fetch(), a call to which is + // only valid in states START and DONE, as only one Fetch() is + // allowed to be outstanding at any given time. + enum State { + STATE_START, + STATE_NO_RESULTS, + STATE_SOME_RESULTS, + STATE_DONE, + }; + + // Current state of this state machine. + State state_; + + // Vector, in Windows' network adapter preference order, of + // DhcpProxyScriptAdapterFetcher objects that are or were attempting + // to fetch a PAC file based on DHCP configuration. + typedef ScopedVector<DhcpProxyScriptAdapterFetcher> FetcherVector; + FetcherVector fetchers_; + + // Callback invoked when any fetcher completes. + CompletionCallbackImpl<DhcpProxyScriptFetcherWin> fetcher_callback_; + + // Number of fetchers we are waiting for. + int num_pending_fetchers_; + + // Lets our client know we're done. Not valid in states START or DONE. + CompletionCallback* client_callback_; + + // Pointer to string we will write results to. Not valid in states + // START and DONE. + string16* destination_string_; + + // PAC URL retrieved from DHCP, if any. Valid only in state STATE_DONE. + GURL pac_url_; + + base::OneShotTimer<DhcpProxyScriptFetcherWin> wait_timer_; + + scoped_refptr<URLRequestContext> url_request_context_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(DhcpProxyScriptFetcherWin); +}; + +} // namespace net + +#endif // NET_PROXY_DHCP_PROXY_SCRIPT_FETCHER_WIN_H_ diff --git a/net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc b/net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc new file mode 100644 index 0000000..596e298 --- /dev/null +++ b/net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc @@ -0,0 +1,545 @@ +// Copyright (c) 2011 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/dhcp_proxy_script_fetcher_win.h" + +#include <vector> + +#include "base/message_loop.h" +#include "base/perftimer.h" +#include "base/rand_util.h" +#include "base/threading/platform_thread.h" +#include "net/base/completion_callback.h" +#include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +TEST(DhcpProxyScriptFetcherWin, AdapterNamesAndPacURLFromDhcp) { + // This tests our core Win32 implementation without any of the wrappers + // we layer on top to achieve asynchronous and parallel operations. + // + // We don't make assumptions about the environment this unit test is + // running in, so it just exercises the code to make sure there + // is no crash and no error returned, but does not assert on the number + // of interfaces or the information returned via DHCP. + std::set<std::string> adapter_names; + DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(&adapter_names); + for (std::set<std::string>::const_iterator it = adapter_names.begin(); + it != adapter_names.end(); + ++it) { + const std::string& adapter_name = *it; + std::string pac_url = + DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name); + printf("Adapter '%s' has PAC URL '%s' configured in DHCP.\n", + adapter_name.c_str(), + pac_url.c_str()); + } +} + +// Helper for RealFetch* tests below. +class RealFetchTester { + public: + RealFetchTester() + : context_((new TestURLRequestContext())), + fetcher_(new DhcpProxyScriptFetcherWin(context_.get())), + finished_(false), + ALLOW_THIS_IN_INITIALIZER_LIST( + completion_callback_(this, &RealFetchTester::OnCompletion)), + on_completion_is_error_(false) { + // Make sure the test ends. + timeout_.Start( + base::TimeDelta::FromSeconds(5), this, &RealFetchTester::OnTimeout); + } + + void RunTest() { + fetcher_->Fetch(&pac_text_, &completion_callback_); + } + + void RunTestWithCancel() { + RunTest(); + fetcher_->Cancel(); + } + + void RunTestWithDeferredCancel() { + RunTest(); + cancel_timer_.Start(base::TimeDelta::FromMilliseconds(1), + this, &RealFetchTester::OnCancelTimer); + } + + void OnCompletion(int result) { + if (on_completion_is_error_) { + FAIL() << "Received completion for test in which this is error."; + } + finished_ = true; + printf("Result code %d PAC data length %d\n", result, pac_text_.size()); + } + + void OnTimeout() { + printf("Timeout!"); + OnCompletion(0); + } + + void OnCancelTimer() { + fetcher_->Cancel(); + finished_ = true; + } + + void WaitUntilDone() { + while (!finished_) { + MessageLoop::current()->RunAllPending(); + } + MessageLoop::current()->RunAllPending(); + } + + // Attempts to give worker threads time to finish. This is currently + // very simplistic as completion (via completion callback or cancellation) + // immediately "detaches" any worker threads, so the best we can do is give + // them a little time. If we start running into Valgrind leaks, we can + // do something a bit more clever to track worker threads even when the + // DhcpProxyScriptFetcherWin state machine has finished. + void FinishTestAllowCleanup() { + base::PlatformThread::Sleep(30); + } + + scoped_refptr<URLRequestContext> context_; + scoped_ptr<DhcpProxyScriptFetcherWin> fetcher_; + bool finished_; + string16 pac_text_; + CompletionCallbackImpl<RealFetchTester> completion_callback_; + base::OneShotTimer<RealFetchTester> timeout_; + base::OneShotTimer<RealFetchTester> cancel_timer_; + bool on_completion_is_error_; +}; + +TEST(DhcpProxyScriptFetcherWin, RealFetch) { + // This tests a call to Fetch() with no stubbing out of dependencies. + // + // We don't make assumptions about the environment this unit test is + // running in, so it just exercises the code to make sure there + // is no crash and no unexpected error returned, but does not assert on + // results beyond that. + RealFetchTester fetcher; + fetcher.RunTest(); + + fetcher.WaitUntilDone(); + printf("PAC URL was %s\n", + fetcher.fetcher_->GetPacURL().possibly_invalid_spec().c_str()); + + fetcher.FinishTestAllowCleanup(); +} + +TEST(DhcpProxyScriptFetcherWin, RealFetchWithCancel) { + // Does a Fetch() with an immediate cancel. As before, just + // exercises the code without stubbing out dependencies. + RealFetchTester fetcher; + fetcher.RunTestWithCancel(); + MessageLoop::current()->RunAllPending(); + + // Attempt to avoid Valgrind leak reports in case worker thread is + // still running. + fetcher.FinishTestAllowCleanup(); +} + +// For RealFetchWithDeferredCancel, below. +class DelayingDhcpProxyScriptAdapterFetcher + : public DhcpProxyScriptAdapterFetcher { + public: + explicit DelayingDhcpProxyScriptAdapterFetcher( + URLRequestContext* url_request_context) + : DhcpProxyScriptAdapterFetcher(url_request_context) { + } + + class DelayingWorkerThread : public WorkerThread { + public: + explicit DelayingWorkerThread( + const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner) + : WorkerThread(owner) { + } + + std::string ImplGetPacURLFromDhcp( + const std::string& adapter_name) OVERRIDE { + base::PlatformThread::Sleep(20); + return WorkerThread::ImplGetPacURLFromDhcp(adapter_name); + } + }; + + WorkerThread* ImplCreateWorkerThread( + const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner) OVERRIDE { + return new DelayingWorkerThread(owner); + } +}; + +// For RealFetchWithDeferredCancel, below. +class DelayingDhcpProxyScriptFetcherWin + : public DhcpProxyScriptFetcherWin { + public: + explicit DelayingDhcpProxyScriptFetcherWin( + URLRequestContext* context) + : DhcpProxyScriptFetcherWin(context) { + } + + DhcpProxyScriptAdapterFetcher* ImplCreateAdapterFetcher() OVERRIDE { + return new DelayingDhcpProxyScriptAdapterFetcher(url_request_context_); + } +}; + +TEST(DhcpProxyScriptFetcherWin, RealFetchWithDeferredCancel) { + // Does a Fetch() with a slightly delayed cancel. As before, just + // exercises the code without stubbing out dependencies, but + // introduces a guaranteed 20 ms delay on the worker threads so that + // the cancel is called before they complete. + RealFetchTester fetcher; + fetcher.fetcher_.reset( + new DelayingDhcpProxyScriptFetcherWin(fetcher.context_)); + fetcher.on_completion_is_error_ = true; + fetcher.RunTestWithDeferredCancel(); + fetcher.WaitUntilDone(); +} + +// The remaining tests are to exercise our state machine in various +// situations, with actual network access fully stubbed out. + +class DummyDhcpProxyScriptAdapterFetcher + : public DhcpProxyScriptAdapterFetcher { + public: + DummyDhcpProxyScriptAdapterFetcher() + : DhcpProxyScriptAdapterFetcher(new TestURLRequestContext()), + did_finish_(false), + result_(OK), + pac_script_(L"bingo"), + fetch_delay_ms_(1), + client_callback_(NULL) { + } + + void Fetch(const std::string& adapter_name, + CompletionCallback* callback) OVERRIDE { + client_callback_ = callback; + timer_.Start(base::TimeDelta::FromMilliseconds(fetch_delay_ms_), + this, &DummyDhcpProxyScriptAdapterFetcher::OnTimer); + } + + void Cancel() OVERRIDE { + timer_.Stop(); + } + + bool DidFinish() const OVERRIDE { + return did_finish_; + } + + int GetResult() const OVERRIDE { + return result_; + } + + string16 GetPacScript() const OVERRIDE { + return pac_script_; + } + + void OnTimer() { + client_callback_->Run(result_); + } + + void Configure( + bool did_finish, int result, string16 pac_script, int fetch_delay_ms) { + did_finish_ = did_finish; + result_ = result; + pac_script_ = pac_script; + fetch_delay_ms_ = fetch_delay_ms; + } + + private: + bool did_finish_; + int result_; + string16 pac_script_; + int fetch_delay_ms_; + CompletionCallback* client_callback_; + base::OneShotTimer<DummyDhcpProxyScriptAdapterFetcher> timer_; +}; + +class MockDhcpProxyScriptFetcherWin : public DhcpProxyScriptFetcherWin { + public: + MockDhcpProxyScriptFetcherWin() + : DhcpProxyScriptFetcherWin(new TestURLRequestContext()), + next_adapter_fetcher_index_(0) { + } + + // Adds a fetcher object to the queue of fetchers used by + // |ImplCreateAdapterFetcher()|, and its name to the list of adapters + // returned by ImplGetCandidateAdapterNames. + void PushBackAdapter(const std::string& adapter_name, + DhcpProxyScriptAdapterFetcher* fetcher) { + adapter_names_.push_back(adapter_name); + adapter_fetchers_.push_back(fetcher); + } + + void ConfigureAndPushBackAdapter(const std::string& adapter_name, + bool did_finish, + int result, + string16 pac_script, + int fetch_delay_ms) { + scoped_ptr<DummyDhcpProxyScriptAdapterFetcher> adapter_fetcher( + new DummyDhcpProxyScriptAdapterFetcher()); + adapter_fetcher->Configure(did_finish, result, pac_script, fetch_delay_ms); + PushBackAdapter(adapter_name, adapter_fetcher.release()); + } + + DhcpProxyScriptAdapterFetcher* ImplCreateAdapterFetcher() OVERRIDE { + return adapter_fetchers_[next_adapter_fetcher_index_++]; + } + + bool ImplGetCandidateAdapterNames( + std::set<std::string>* adapter_names) OVERRIDE { + adapter_names->insert(adapter_names_.begin(), adapter_names_.end()); + return true; + } + + int ImplGetMaxWaitMs() OVERRIDE { + return 25; + } + + void ResetTestState() { + next_adapter_fetcher_index_ = 0; + adapter_fetchers_.clear(); + // String pointers contained herein will have been freed during test. + adapter_names_.clear(); + } + + int next_adapter_fetcher_index_; + + // Ownership is not here; it gets transferred to the implementation + // class via ImplCreateAdapterFetcher. + std::vector<DhcpProxyScriptAdapterFetcher*> adapter_fetchers_; + + std::vector<std::string> adapter_names_; +}; + +class FetcherClient { +public: + FetcherClient() + : finished_(false), + result_(ERR_UNEXPECTED), + ALLOW_THIS_IN_INITIALIZER_LIST( + completion_callback_(this, &FetcherClient::OnCompletion)) { + } + + void RunTest() { + int result = fetcher_.Fetch(&pac_text_, &completion_callback_); + ASSERT_EQ(ERR_IO_PENDING, result); + } + + void RunImmediateReturnTest() { + int result = fetcher_.Fetch(&pac_text_, &completion_callback_); + ASSERT_EQ(ERR_PAC_NOT_IN_DHCP, result); + } + + void RunMessageLoopUntilComplete() { + while (!finished_) { + MessageLoop::current()->RunAllPending(); + } + MessageLoop::current()->RunAllPending(); + } + + void OnCompletion(int result) { + finished_ = true; + result_ = result; + } + + void ResetTestState() { + finished_ = false; + result_ = ERR_UNEXPECTED; + pac_text_ = L""; + fetcher_.ResetTestState(); + } + + MockDhcpProxyScriptFetcherWin fetcher_; + bool finished_; + int result_; + string16 pac_text_; + CompletionCallbackImpl<FetcherClient> completion_callback_; +}; + +// We separate out each test's logic so that we can easily implement +// the ReuseFetcher test at the bottom. +void TestNormalCaseURLConfiguredOneAdapter(FetcherClient* client) { + scoped_ptr<DummyDhcpProxyScriptAdapterFetcher> adapter_fetcher( + new DummyDhcpProxyScriptAdapterFetcher()); + adapter_fetcher->Configure(true, OK, L"bingo", 1); + client->fetcher_.PushBackAdapter("a", adapter_fetcher.release()); + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_EQ(OK, client->result_); + ASSERT_EQ(L"bingo", client->pac_text_); +} + +TEST(DhcpProxyScriptFetcherWin, NormalCaseURLConfiguredOneAdapter) { + FetcherClient client; + TestNormalCaseURLConfiguredOneAdapter(&client); +} + +void TestNormalCaseURLConfiguredMultipleAdapters(FetcherClient* client) { + client->fetcher_.ConfigureAndPushBackAdapter( + "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"", 1); + client->fetcher_.ConfigureAndPushBackAdapter( + "second", true, OK, L"bingo", 50); + client->fetcher_.ConfigureAndPushBackAdapter( + "third", true, OK, L"rocko", 1); + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_EQ(OK, client->result_); + ASSERT_EQ(L"bingo", client->pac_text_); +} + +TEST(DhcpProxyScriptFetcherWin, NormalCaseURLConfiguredMultipleAdapters) { + FetcherClient client; + TestNormalCaseURLConfiguredMultipleAdapters(&client); +} + +void TestNormalCaseURLConfiguredMultipleAdaptersWithTimeout( + FetcherClient* client) { + client->fetcher_.ConfigureAndPushBackAdapter( + "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"", 1); + // This will time out. + client->fetcher_.ConfigureAndPushBackAdapter( + "second", false, ERR_IO_PENDING, L"bingo", 1000); + client->fetcher_.ConfigureAndPushBackAdapter( + "third", true, OK, L"rocko", 1); + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_EQ(OK, client->result_); + ASSERT_EQ(L"rocko", client->pac_text_); +} + +TEST(DhcpProxyScriptFetcherWin, + NormalCaseURLConfiguredMultipleAdaptersWithTimeout) { + FetcherClient client; + TestNormalCaseURLConfiguredMultipleAdaptersWithTimeout(&client); +} + +void TestFailureCaseURLConfiguredMultipleAdaptersWithTimeout( + FetcherClient* client) { + client->fetcher_.ConfigureAndPushBackAdapter( + "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"", 1); + // This will time out. + client->fetcher_.ConfigureAndPushBackAdapter( + "second", false, ERR_IO_PENDING, L"bingo", 1000); + // This is the first non-ERR_PAC_NOT_IN_DHCP error and as such + // should be chosen. + client->fetcher_.ConfigureAndPushBackAdapter( + "third", true, ERR_PAC_STATUS_NOT_OK, L"", 1); + client->fetcher_.ConfigureAndPushBackAdapter( + "fourth", true, ERR_NOT_IMPLEMENTED, L"", 1); + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_EQ(ERR_PAC_STATUS_NOT_OK, client->result_); + ASSERT_EQ(L"", client->pac_text_); +} + +TEST(DhcpProxyScriptFetcherWin, + FailureCaseURLConfiguredMultipleAdaptersWithTimeout) { + FetcherClient client; + TestFailureCaseURLConfiguredMultipleAdaptersWithTimeout(&client); +} + +void TestFailureCaseNoURLConfigured(FetcherClient* client) { + client->fetcher_.ConfigureAndPushBackAdapter( + "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"", 1); + // This will time out. + client->fetcher_.ConfigureAndPushBackAdapter( + "second", false, ERR_IO_PENDING, L"bingo", 1000); + // This is the first non-ERR_PAC_NOT_IN_DHCP error and as such + // should be chosen. + client->fetcher_.ConfigureAndPushBackAdapter( + "third", true, ERR_PAC_NOT_IN_DHCP, L"", 1); + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_EQ(ERR_PAC_NOT_IN_DHCP, client->result_); + ASSERT_EQ(L"", client->pac_text_); +} + +TEST(DhcpProxyScriptFetcherWin, FailureCaseNoURLConfigured) { + FetcherClient client; + TestFailureCaseNoURLConfigured(&client); +} + +void TestFailureCaseNoDhcpAdapters(FetcherClient* client) { + client->RunImmediateReturnTest(); + // In case there are any pending messages that get us in a bad state + // (there shouldn't be). + MessageLoop::current()->RunAllPending(); +} + +TEST(DhcpProxyScriptFetcherWin, FailureCaseNoDhcpAdapters) { + FetcherClient client; + TestFailureCaseNoDhcpAdapters(&client); +} + +void TestShortCircuitLessPreferredAdapters(FetcherClient* client) { + // Here we have a bunch of adapters; the first reports no PAC in DHCP, + // the second responds quickly with a PAC file, the rest take a long + // time. Verify that we complete quickly and do not wait for the slow + // adapters, i.e. we finish before timeout. + client->fetcher_.ConfigureAndPushBackAdapter( + "1", true, ERR_PAC_NOT_IN_DHCP, L"", 1); + client->fetcher_.ConfigureAndPushBackAdapter( + "2", true, OK, L"bingo", 1); + client->fetcher_.ConfigureAndPushBackAdapter( + "3", true, OK, L"wrongo", 1000); + + PerfTimer timer; + client->RunTest(); + client->RunMessageLoopUntilComplete(); + // Assert that the time passed is just less than the wait timer + // timeout (which we have mocked out above to be 25 ms), to avoid + // flakiness but still get a strong signal that it was the shortcut + // mechanism (in OnFetcherDone) that kicked in. + ASSERT_GT(TimeDelta::FromMilliseconds(23), timer.Elapsed()); +} + +TEST(DhcpProxyScriptFetcherWin, ShortCircuitLessPreferredAdapters) { + FetcherClient client; + TestShortCircuitLessPreferredAdapters(&client); +} + +TEST(DhcpProxyScriptFetcherWin, ReuseFetcher) { + FetcherClient client; + + // The ProxyScriptFetcher interface stipulates that only a single + // |Fetch()| may be in flight at once, but allows reuse, so test + // that the state transitions correctly from done to start in all + // cases we're testing. + + typedef void (*FetcherClientTestFunction)(FetcherClient*); + typedef std::vector<FetcherClientTestFunction> TestVector; + TestVector test_functions; + test_functions.push_back(TestNormalCaseURLConfiguredOneAdapter); + test_functions.push_back(TestNormalCaseURLConfiguredMultipleAdapters); + test_functions.push_back( + TestNormalCaseURLConfiguredMultipleAdaptersWithTimeout); + test_functions.push_back( + TestFailureCaseURLConfiguredMultipleAdaptersWithTimeout); + test_functions.push_back(TestFailureCaseNoURLConfigured); + test_functions.push_back(TestFailureCaseNoDhcpAdapters); + test_functions.push_back(TestShortCircuitLessPreferredAdapters); + + std::random_shuffle(test_functions.begin(), + test_functions.end(), + base::RandGenerator); + for (TestVector::const_iterator it = test_functions.begin(); + it != test_functions.end(); + ++it) { + (*it)(&client); + client.ResetTestState(); + } + + // Re-do the first test to make sure the last test that was run did + // not leave things in a bad state. + (*test_functions.begin())(&client); +} + +} // namespace + +} // namespace net diff --git a/net/proxy/dhcpcsvc_init_win.cc b/net/proxy/dhcpcsvc_init_win.cc new file mode 100644 index 0000000..3a0aa02 --- /dev/null +++ b/net/proxy/dhcpcsvc_init_win.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2011 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/dhcpcsvc_init_win.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" + +#include <dhcpcsdk.h> +#include <dhcpv6csdk.h> + +namespace { + +class DhcpcsvcInitSingleton { + public: + DhcpcsvcInitSingleton() { + DWORD version = 0; + DWORD err = DhcpCApiInitialize(&version); + DCHECK(err == ERROR_SUCCESS); // DCHECK_EQ complains of unsigned mismatch. + } + + ~DhcpcsvcInitSingleton() { + // Worker pool threads that use the DHCP API may still be running, so skip + // cleanup. + } +}; + +static base::LazyInstance<DhcpcsvcInitSingleton> g_dhcpcsvc_init_singleton( + base::LINKER_INITIALIZED); + +} // namespace + +namespace net { + +void EnsureDhcpcsvcInit() { + g_dhcpcsvc_init_singleton.Get(); +} + +} // namespace net diff --git a/net/proxy/dhcpcsvc_init_win.h b/net/proxy/dhcpcsvc_init_win.h new file mode 100644 index 0000000..9c557d0 --- /dev/null +++ b/net/proxy/dhcpcsvc_init_win.h @@ -0,0 +1,20 @@ +// Copyright (c) 2011 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_DHCPCSVC_INIT_WIN_H +#define NET_PROXY_DHCPCSVC_INIT_WIN_H +#pragma once + +namespace net { + +// Initialization of the Dhcpcsvc library must happen before any of its +// calls are made. This function will make sure that the appropriate +// initialization has been done, and that uninitialization is also +// performed at static uninitialization time. +// +// Note: This initializes only for DHCP, not DHCPv6. +void EnsureDhcpcsvcInit(); + +} // namespace net + +#endif // NET_PROXY_DHCPCSVC_INIT_WIN_H diff --git a/net/proxy/init_proxy_resolver.cc b/net/proxy/init_proxy_resolver.cc index d9d55ea..52c5421 100644 --- a/net/proxy/init_proxy_resolver.cc +++ b/net/proxy/init_proxy_resolver.cc @@ -10,6 +10,8 @@ #include "base/string_util.h" #include "net/base/net_log.h" #include "net/base/net_errors.h" +#include "net/proxy/dhcp_proxy_script_fetcher.h" +#include "net/proxy/dhcp_proxy_script_fetcher_factory.h" #include "net/proxy/proxy_config.h" #include "net/proxy/proxy_resolver.h" #include "net/proxy/proxy_script_fetcher.h" @@ -30,15 +32,18 @@ namespace net { // http://code.google.com/p/chromium/issues/detail?id=18575#c20 static const char kWpadUrl[] = "http://wpad/wpad.dat"; -InitProxyResolver::InitProxyResolver(ProxyResolver* resolver, - ProxyScriptFetcher* proxy_script_fetcher, - NetLog* net_log) +InitProxyResolver::InitProxyResolver( + ProxyResolver* resolver, + ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher, + NetLog* net_log) : resolver_(resolver), proxy_script_fetcher_(proxy_script_fetcher), + dhcp_proxy_script_fetcher_(dhcp_proxy_script_fetcher), ALLOW_THIS_IN_INITIALIZER_LIST(io_callback_( this, &InitProxyResolver::OnIOCompletion)), user_callback_(NULL), - current_pac_url_index_(0u), + current_pac_source_index_(0u), pac_mandatory_(false), next_state_(STATE_NONE), net_log_(BoundNetLog::Make( @@ -70,8 +75,8 @@ int InitProxyResolver::Init(const ProxyConfig& config, pac_mandatory_ = config.pac_mandatory(); - pac_urls_ = BuildPacUrlsFallbackList(config); - DCHECK(!pac_urls_.empty()); + pac_sources_ = BuildPacSourcesFallbackList(config); + DCHECK(!pac_sources_.empty()); next_state_ = STATE_WAIT; @@ -85,16 +90,19 @@ int InitProxyResolver::Init(const ProxyConfig& config, } // Initialize the fallback rules. -// (1) WPAD (DNS). -// (2) Custom PAC URL. -InitProxyResolver::UrlList InitProxyResolver::BuildPacUrlsFallbackList( +// (1) WPAD (DHCP). +// (2) WPAD (DNS). +// (3) Custom PAC URL. +InitProxyResolver::PacSourceList InitProxyResolver::BuildPacSourcesFallbackList( const ProxyConfig& config) const { - UrlList pac_urls; - if (config.auto_detect()) - pac_urls.push_back(PacURL(true, GURL())); + PacSourceList pac_sources; + if (config.auto_detect()) { + pac_sources.push_back(PacSource(PacSource::WPAD_DHCP, GURL())); + pac_sources.push_back(PacSource(PacSource::WPAD_DNS, GURL())); + } if (config.has_pac_url()) - pac_urls.push_back(PacURL(false, config.pac_url())); - return pac_urls; + pac_sources.push_back(PacSource(PacSource::CUSTOM, config.pac_url())); + return pac_sources; } void InitProxyResolver::OnIOCompletion(int result) { @@ -177,24 +185,32 @@ int InitProxyResolver::DoFetchPacScript() { next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE; - const PacURL& pac_url = current_pac_url(); + const PacSource& pac_source = current_pac_source(); - const GURL effective_pac_url = - pac_url.auto_detect ? GURL(kWpadUrl) : pac_url.url; + GURL effective_pac_url; + NetLogStringParameter* log_parameter = + CreateNetLogParameterAndDetermineURL(pac_source, &effective_pac_url); net_log_.BeginEvent( NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT, - make_scoped_refptr(new NetLogStringParameter( - "url", effective_pac_url.possibly_invalid_spec()))); + make_scoped_refptr(log_parameter)); + + if (pac_source.type == PacSource::WPAD_DHCP) { + if (!dhcp_proxy_script_fetcher_) { + net_log_.AddEvent(NetLog::TYPE_INIT_PROXY_RESOLVER_HAS_NO_FETCHER, NULL); + return ERR_UNEXPECTED; + } + + return dhcp_proxy_script_fetcher_->Fetch(&pac_script_, &io_callback_); + } if (!proxy_script_fetcher_) { net_log_.AddEvent(NetLog::TYPE_INIT_PROXY_RESOLVER_HAS_NO_FETCHER, NULL); return ERR_UNEXPECTED; } - return proxy_script_fetcher_->Fetch(effective_pac_url, - &pac_script_, - &io_callback_); + return proxy_script_fetcher_->Fetch( + effective_pac_url, &pac_script_, &io_callback_); } int InitProxyResolver::DoFetchPacScriptComplete(int result) { @@ -203,7 +219,7 @@ int InitProxyResolver::DoFetchPacScriptComplete(int result) { net_log_.EndEventWithNetErrorCode( NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT, result); if (result != OK) - return TryToFallbackPacUrl(result); + return TryToFallbackPacSource(result); next_state_ = STATE_SET_PAC_SCRIPT; return result; @@ -212,7 +228,7 @@ int InitProxyResolver::DoFetchPacScriptComplete(int result) { int InitProxyResolver::DoSetPacScript() { net_log_.BeginEvent(NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT, NULL); - const PacURL& pac_url = current_pac_url(); + const PacSource& pac_source = current_pac_source(); next_state_ = STATE_SET_PAC_SCRIPT_COMPLETE; @@ -221,9 +237,9 @@ int InitProxyResolver::DoSetPacScript() { if (resolver_->expects_pac_bytes()) { script_data = ProxyResolverScriptData::FromUTF16(pac_script_); } else { - script_data = pac_url.auto_detect ? - ProxyResolverScriptData::ForAutoDetect() : - ProxyResolverScriptData::FromURL(pac_url.url); + script_data = pac_source.type == PacSource::CUSTOM ? + ProxyResolverScriptData::FromURL(pac_source.url) : + ProxyResolverScriptData::ForAutoDetect(); } return resolver_->SetPacScript(script_data, &io_callback_); @@ -233,39 +249,59 @@ int InitProxyResolver::DoSetPacScriptComplete(int result) { net_log_.EndEventWithNetErrorCode( NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT, result); if (result != OK) - return TryToFallbackPacUrl(result); + return TryToFallbackPacSource(result); // Let the caller know which automatic setting we ended up initializing the // resolver for (there may have been multiple fallbacks to choose from.) if (effective_config_) { - if (current_pac_url().auto_detect && resolver_->expects_pac_bytes()) { - *effective_config_ = - ProxyConfig::CreateFromCustomPacURL(GURL(kWpadUrl)); - } else if (current_pac_url().auto_detect) { - *effective_config_ = ProxyConfig::CreateAutoDetect(); - } else { + if (current_pac_source().type == PacSource::CUSTOM) { *effective_config_ = - ProxyConfig::CreateFromCustomPacURL(current_pac_url().url); + ProxyConfig::CreateFromCustomPacURL(current_pac_source().url); effective_config_->set_pac_mandatory(pac_mandatory_); + } else { + if (resolver_->expects_pac_bytes()) { + GURL auto_detected_url; + + switch (current_pac_source().type) { + case PacSource::WPAD_DHCP: + auto_detected_url = dhcp_proxy_script_fetcher_->GetPacURL(); + break; + + case PacSource::WPAD_DNS: + auto_detected_url = GURL(kWpadUrl); + break; + + default: + NOTREACHED(); + } + + *effective_config_ = + ProxyConfig::CreateFromCustomPacURL(auto_detected_url); + } else { + // The resolver does its own resolution so we cannot know the + // URL. Just do the best we can and state that the configuration + // is to auto-detect proxy settings. + *effective_config_ = ProxyConfig::CreateAutoDetect(); + } } } return result; } -int InitProxyResolver::TryToFallbackPacUrl(int error) { +int InitProxyResolver::TryToFallbackPacSource(int error) { DCHECK_LT(error, 0); - if (current_pac_url_index_ + 1 >= pac_urls_.size()) { + if (current_pac_source_index_ + 1 >= pac_sources_.size()) { // Nothing left to fall back to. return error; } // Advance to next URL in our list. - ++current_pac_url_index_; + ++current_pac_source_index_; net_log_.AddEvent( - NetLog::TYPE_INIT_PROXY_RESOLVER_FALLING_BACK_TO_NEXT_PAC_URL, NULL); + NetLog::TYPE_INIT_PROXY_RESOLVER_FALLING_BACK_TO_NEXT_PAC_SOURCE, NULL); next_state_ = GetStartState(); @@ -277,9 +313,34 @@ InitProxyResolver::State InitProxyResolver::GetStartState() const { STATE_FETCH_PAC_SCRIPT : STATE_SET_PAC_SCRIPT; } -const InitProxyResolver::PacURL& InitProxyResolver::current_pac_url() const { - DCHECK_LT(current_pac_url_index_, pac_urls_.size()); - return pac_urls_[current_pac_url_index_]; +NetLogStringParameter* InitProxyResolver::CreateNetLogParameterAndDetermineURL( + const PacSource& pac_source, + GURL* effective_pac_url) { + DCHECK(effective_pac_url); + + std::string source_field; + switch (pac_source.type) { + case PacSource::WPAD_DHCP: + source_field = "WPAD DHCP"; + break; + case PacSource::WPAD_DNS: + *effective_pac_url = GURL(kWpadUrl); + source_field = "WPAD DNS: "; + source_field += effective_pac_url->possibly_invalid_spec(); + break; + case PacSource::CUSTOM: + *effective_pac_url = pac_source.url; + source_field = "Custom PAC URL: "; + source_field += effective_pac_url->possibly_invalid_spec(); + break; + } + return new NetLogStringParameter("source", source_field); +} + +const InitProxyResolver::PacSource& + InitProxyResolver::current_pac_source() const { + DCHECK_LT(current_pac_source_index_, pac_sources_.size()); + return pac_sources_[current_pac_source_index_]; } void InitProxyResolver::OnWaitTimerFired() { diff --git a/net/proxy/init_proxy_resolver.h b/net/proxy/init_proxy_resolver.h index 67cf1cb..ffb1b83 100644 --- a/net/proxy/init_proxy_resolver.h +++ b/net/proxy/init_proxy_resolver.h @@ -11,15 +11,19 @@ #include "base/string16.h" #include "base/time.h" #include "base/timer.h" +#include "base/scoped_ptr.h" #include "googleurl/src/gurl.h" #include "net/base/completion_callback.h" #include "net/base/net_log.h" namespace net { +class DhcpProxyScriptFetcher; +class NetLogParameter; class ProxyConfig; class ProxyResolver; class ProxyScriptFetcher; +class URLRequestContext; // InitProxyResolver is a helper class used by ProxyService to // initialize a ProxyResolver with the PAC script data specified @@ -39,10 +43,11 @@ class ProxyScriptFetcher; // class InitProxyResolver { public: - // |resolver|, |proxy_script_fetcher| and |net_log| must remain valid for - // the lifespan of InitProxyResolver. + // |resolver|, |proxy_script_fetcher|, |dhcp_proxy_script_fetcher| and + // |net_log| must remain valid for the lifespan of InitProxyResolver. InitProxyResolver(ProxyResolver* resolver, ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher, NetLog* net_log); // Aborts any in-progress request. @@ -63,14 +68,23 @@ class InitProxyResolver { CompletionCallback* callback); private: - struct PacURL { - PacURL(bool auto_detect, const GURL& url) - : auto_detect(auto_detect), url(url) {} - bool auto_detect; - GURL url; + // Represents the sources from which we can get PAC files; two types of + // auto-detect or a custom URL. + struct PacSource { + enum Type { + WPAD_DHCP, + WPAD_DNS, + CUSTOM + }; + + PacSource(Type type, const GURL& url) + : type(type), url(url) {} + + Type type; + GURL url; // Empty unless |type == PAC_SOURCE_CUSTOM|. }; - typedef std::vector<PacURL> UrlList; + typedef std::vector<PacSource> PacSourceList; enum State { STATE_NONE, @@ -83,7 +97,7 @@ class InitProxyResolver { }; // Returns ordered list of PAC urls to try for |config|. - UrlList BuildPacUrlsFallbackList(const ProxyConfig& config) const; + PacSourceList BuildPacSourcesFallbackList(const ProxyConfig& config) const; void OnIOCompletion(int result); int DoLoop(int result); @@ -99,17 +113,20 @@ class InitProxyResolver { int DoSetPacScriptComplete(int result); // Tries restarting using the next fallback PAC URL: - // |pac_urls_[++current_pac_url_index]|. + // |pac_sources_[++current_pac_source_index]|. // Returns OK and rewinds the state machine when there // is something to try, otherwise returns |error|. - int TryToFallbackPacUrl(int error); + int TryToFallbackPacSource(int error); // Gets the initial state (we skip fetching when the // ProxyResolver doesn't |expect_pac_bytes()|. State GetStartState() const; + NetLogStringParameter* CreateNetLogParameterAndDetermineURL( + const PacSource& pac_source, GURL* effective_pac_url); + // Returns the current PAC URL we are fetching/testing. - const PacURL& current_pac_url() const; + const PacSource& current_pac_source() const; void OnWaitTimerFired(); void DidCompleteInit(); @@ -117,11 +134,12 @@ class InitProxyResolver { ProxyResolver* resolver_; ProxyScriptFetcher* proxy_script_fetcher_; + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher_; CompletionCallbackImpl<InitProxyResolver> io_callback_; CompletionCallback* user_callback_; - size_t current_pac_url_index_; + size_t current_pac_source_index_; // Filled when the PAC script fetch completes. string16 pac_script_; @@ -130,7 +148,7 @@ class InitProxyResolver { // (i.e. fallback to direct connections are prohibited). bool pac_mandatory_; - UrlList pac_urls_; + PacSourceList pac_sources_; State next_state_; BoundNetLog net_log_; diff --git a/net/proxy/init_proxy_resolver_unittest.cc b/net/proxy/init_proxy_resolver_unittest.cc index b0d416d..628abee 100644 --- a/net/proxy/init_proxy_resolver_unittest.cc +++ b/net/proxy/init_proxy_resolver_unittest.cc @@ -11,6 +11,7 @@ #include "net/base/net_log_unittest.h" #include "net/base/test_completion_callback.h" #include "net/proxy/init_proxy_resolver.h" +#include "net/proxy/dhcp_proxy_script_fetcher.h" #include "net/proxy/proxy_config.h" #include "net/proxy/proxy_resolver.h" #include "net/proxy/proxy_script_fetcher.h" @@ -107,7 +108,7 @@ class RuleBasedProxyScriptFetcher : public ProxyScriptFetcher { virtual void Cancel() {} - virtual URLRequestContext* GetRequestContext() { return NULL; } + virtual URLRequestContext* GetRequestContext() const { return NULL; } private: const Rules* rules_; @@ -174,6 +175,7 @@ TEST(InitProxyResolverTest, CustomPacSucceeds) { Rules rules; RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_pac_url(GURL("http://custom/proxy.pac")); @@ -182,8 +184,10 @@ TEST(InitProxyResolverTest, CustomPacSucceeds) { TestCompletionCallback callback; CapturingNetLog log(CapturingNetLog::kUnbounded); - InitProxyResolver init(&resolver, &fetcher, &log); - EXPECT_EQ(OK, init.Init(config, base::TimeDelta(), NULL, &callback)); + ProxyConfig effective_config; + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, &log); + EXPECT_EQ(OK, init.Init( + config, base::TimeDelta(), &effective_config, &callback)); EXPECT_EQ(rule.text(), resolver.script_data()->utf16()); // Check the NetLog was filled correctly. @@ -203,6 +207,9 @@ TEST(InitProxyResolverTest, CustomPacSucceeds) { entries, 4, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT)); EXPECT_TRUE(LogContainsEndEvent( entries, 5, NetLog::TYPE_INIT_PROXY_RESOLVER)); + + EXPECT_TRUE(effective_config.has_pac_url()); + EXPECT_EQ(config.pac_url(), effective_config.pac_url()); } // Fail downloading the custom PAC script. @@ -210,6 +217,7 @@ TEST(InitProxyResolverTest, CustomPacFails1) { Rules rules; RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_pac_url(GURL("http://custom/proxy.pac")); @@ -218,9 +226,10 @@ TEST(InitProxyResolverTest, CustomPacFails1) { TestCompletionCallback callback; CapturingNetLog log(CapturingNetLog::kUnbounded); - InitProxyResolver init(&resolver, &fetcher, &log); + ProxyConfig effective_config; + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, &log); EXPECT_EQ(kFailedDownloading, - init.Init(config, base::TimeDelta(), NULL, &callback)); + init.Init(config, base::TimeDelta(), &effective_config, &callback)); EXPECT_EQ(NULL, resolver.script_data()); // Check the NetLog was filled correctly. @@ -236,6 +245,8 @@ TEST(InitProxyResolverTest, CustomPacFails1) { entries, 2, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT)); EXPECT_TRUE(LogContainsEndEvent( entries, 3, NetLog::TYPE_INIT_PROXY_RESOLVER)); + + EXPECT_FALSE(effective_config.has_pac_url()); } // Fail parsing the custom PAC script. @@ -243,6 +254,7 @@ TEST(InitProxyResolverTest, CustomPacFails2) { Rules rules; RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_pac_url(GURL("http://custom/proxy.pac")); @@ -250,7 +262,7 @@ TEST(InitProxyResolverTest, CustomPacFails2) { rules.AddFailParsingRule("http://custom/proxy.pac"); TestCompletionCallback callback; - InitProxyResolver init(&resolver, &fetcher, NULL); + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, NULL); EXPECT_EQ(kFailedParsing, init.Init(config, base::TimeDelta(), NULL, &callback)); EXPECT_EQ(NULL, resolver.script_data()); @@ -260,22 +272,24 @@ TEST(InitProxyResolverTest, CustomPacFails2) { TEST(InitProxyResolverTest, HasNullProxyScriptFetcher) { Rules rules; RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_pac_url(GURL("http://custom/proxy.pac")); TestCompletionCallback callback; - InitProxyResolver init(&resolver, NULL, NULL); + InitProxyResolver init(&resolver, NULL, &dhcp_fetcher, NULL); EXPECT_EQ(ERR_UNEXPECTED, init.Init(config, base::TimeDelta(), NULL, &callback)); EXPECT_EQ(NULL, resolver.script_data()); } -// Succeeds in choosing autodetect (wpad). +// Succeeds in choosing autodetect (WPAD DNS). TEST(InitProxyResolverTest, AutodetectSuccess) { Rules rules; RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_auto_detect(true); @@ -283,9 +297,14 @@ TEST(InitProxyResolverTest, AutodetectSuccess) { Rules::Rule rule = rules.AddSuccessRule("http://wpad/wpad.dat"); TestCompletionCallback callback; - InitProxyResolver init(&resolver, &fetcher, NULL); - EXPECT_EQ(OK, init.Init(config, base::TimeDelta(), NULL, &callback)); + ProxyConfig effective_config; + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, NULL); + EXPECT_EQ(OK, init.Init( + config, base::TimeDelta(), &effective_config, &callback)); EXPECT_EQ(rule.text(), resolver.script_data()->utf16()); + + EXPECT_TRUE(effective_config.has_pac_url()); + EXPECT_EQ(rule.url, effective_config.pac_url()); } // Fails at WPAD (downloading), but succeeds in choosing the custom PAC. @@ -293,6 +312,7 @@ TEST(InitProxyResolverTest, AutodetectFailCustomSuccess1) { Rules rules; RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_auto_detect(true); @@ -302,16 +322,23 @@ TEST(InitProxyResolverTest, AutodetectFailCustomSuccess1) { Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac"); TestCompletionCallback callback; - InitProxyResolver init(&resolver, &fetcher, NULL); - EXPECT_EQ(OK, init.Init(config, base::TimeDelta(), NULL, &callback)); + ProxyConfig effective_config; + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, NULL); + EXPECT_EQ(OK, init.Init( + config, base::TimeDelta(), &effective_config, &callback)); EXPECT_EQ(rule.text(), resolver.script_data()->utf16()); + + EXPECT_TRUE(effective_config.has_pac_url()); + EXPECT_EQ(rule.url, effective_config.pac_url()); } -// Fails at WPAD (parsing), but succeeds in choosing the custom PAC. +// Fails at WPAD (no DHCP config, DNS PAC fails parsing), but succeeds in +// choosing the custom PAC. TEST(InitProxyResolverTest, AutodetectFailCustomSuccess2) { Rules rules; RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_auto_detect(true); @@ -325,7 +352,7 @@ TEST(InitProxyResolverTest, AutodetectFailCustomSuccess2) { CapturingNetLog log(CapturingNetLog::kUnbounded); ProxyConfig effective_config; - InitProxyResolver init(&resolver, &fetcher, &log); + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, &log); EXPECT_EQ(OK, init.Init(config, base::TimeDelta(), &effective_config, &callback)); EXPECT_EQ(rule.text(), resolver.script_data()->utf16()); @@ -336,36 +363,48 @@ TEST(InitProxyResolverTest, AutodetectFailCustomSuccess2) { ProxyConfig::CreateFromCustomPacURL(GURL("http://custom/proxy.pac")))); // Check the NetLog was filled correctly. - // (Note that the Fetch and Set states are repeated since both WPAD and custom + // (Note that various states are repeated since both WPAD and custom // PAC scripts are tried). CapturingNetLog::EntryList entries; log.GetEntries(&entries); - EXPECT_EQ(11u, entries.size()); + EXPECT_EQ(14u, entries.size()); EXPECT_TRUE(LogContainsBeginEvent( entries, 0, NetLog::TYPE_INIT_PROXY_RESOLVER)); + // This is the DHCP phase, which fails fetching rather than parsing, so + // there is no pair of SET_PAC_SCRIPT events. EXPECT_TRUE(LogContainsBeginEvent( entries, 1, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT)); EXPECT_TRUE(LogContainsEndEvent( entries, 2, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsEvent( + entries, 3, + NetLog::TYPE_INIT_PROXY_RESOLVER_FALLING_BACK_TO_NEXT_PAC_SOURCE, + NetLog::PHASE_NONE)); + // This is the DNS phase, which attempts a fetch but fails. EXPECT_TRUE(LogContainsBeginEvent( - entries, 3, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT)); + entries, 4, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT)); EXPECT_TRUE(LogContainsEndEvent( - entries, 4, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT)); + entries, 5, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsBeginEvent( + entries, 6, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsEndEvent( + entries, 7, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT)); EXPECT_TRUE(LogContainsEvent( - entries, 5, - NetLog::TYPE_INIT_PROXY_RESOLVER_FALLING_BACK_TO_NEXT_PAC_URL, + entries, 8, + NetLog::TYPE_INIT_PROXY_RESOLVER_FALLING_BACK_TO_NEXT_PAC_SOURCE, NetLog::PHASE_NONE)); + // Finally, the custom PAC URL phase. EXPECT_TRUE(LogContainsBeginEvent( - entries, 6, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT)); + entries, 9, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT)); EXPECT_TRUE(LogContainsEndEvent( - entries, 7, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT)); + entries, 10, NetLog::TYPE_INIT_PROXY_RESOLVER_FETCH_PAC_SCRIPT)); EXPECT_TRUE(LogContainsBeginEvent( - entries, 8, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT)); + entries, 11, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT)); EXPECT_TRUE(LogContainsEndEvent( - entries, 9, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT)); + entries, 12, NetLog::TYPE_INIT_PROXY_RESOLVER_SET_PAC_SCRIPT)); EXPECT_TRUE(LogContainsEndEvent( - entries, 10, NetLog::TYPE_INIT_PROXY_RESOLVER)); + entries, 13, NetLog::TYPE_INIT_PROXY_RESOLVER)); } // Fails at WPAD (downloading), and fails at custom PAC (downloading). @@ -373,6 +412,7 @@ TEST(InitProxyResolverTest, AutodetectFailCustomFails1) { Rules rules; RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_auto_detect(true); @@ -382,7 +422,7 @@ TEST(InitProxyResolverTest, AutodetectFailCustomFails1) { rules.AddFailDownloadRule("http://custom/proxy.pac"); TestCompletionCallback callback; - InitProxyResolver init(&resolver, &fetcher, NULL); + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, NULL); EXPECT_EQ(kFailedDownloading, init.Init(config, base::TimeDelta(), NULL, &callback)); EXPECT_EQ(NULL, resolver.script_data()); @@ -393,6 +433,7 @@ TEST(InitProxyResolverTest, AutodetectFailCustomFails2) { Rules rules; RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_auto_detect(true); @@ -402,7 +443,7 @@ TEST(InitProxyResolverTest, AutodetectFailCustomFails2) { rules.AddFailParsingRule("http://custom/proxy.pac"); TestCompletionCallback callback; - InitProxyResolver init(&resolver, &fetcher, NULL); + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, NULL); EXPECT_EQ(kFailedParsing, init.Init(config, base::TimeDelta(), NULL, &callback)); EXPECT_EQ(NULL, resolver.script_data()); @@ -415,6 +456,7 @@ TEST(InitProxyResolverTest, AutodetectFailCustomSuccess2_NoFetch) { Rules rules; RuleBasedProxyResolver resolver(&rules, false /*expects_pac_bytes*/); RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_auto_detect(true); @@ -424,7 +466,7 @@ TEST(InitProxyResolverTest, AutodetectFailCustomSuccess2_NoFetch) { Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac"); TestCompletionCallback callback; - InitProxyResolver init(&resolver, &fetcher, NULL); + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, NULL); EXPECT_EQ(OK, init.Init(config, base::TimeDelta(), NULL, &callback)); EXPECT_EQ(rule.url, resolver.script_data()->url()); } @@ -436,6 +478,7 @@ TEST(InitProxyResolverTest, CustomPacFails1_WithPositiveDelay) { Rules rules; RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_pac_url(GURL("http://custom/proxy.pac")); @@ -444,7 +487,7 @@ TEST(InitProxyResolverTest, CustomPacFails1_WithPositiveDelay) { TestCompletionCallback callback; CapturingNetLog log(CapturingNetLog::kUnbounded); - InitProxyResolver init(&resolver, &fetcher, &log); + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, &log); EXPECT_EQ(ERR_IO_PENDING, init.Init(config, base::TimeDelta::FromMilliseconds(1), NULL, &callback)); @@ -478,6 +521,7 @@ TEST(InitProxyResolverTest, CustomPacFails1_WithNegativeDelay) { Rules rules; RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; ProxyConfig config; config.set_pac_url(GURL("http://custom/proxy.pac")); @@ -486,7 +530,7 @@ TEST(InitProxyResolverTest, CustomPacFails1_WithNegativeDelay) { TestCompletionCallback callback; CapturingNetLog log(CapturingNetLog::kUnbounded); - InitProxyResolver init(&resolver, &fetcher, &log); + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, &log); EXPECT_EQ(kFailedDownloading, init.Init(config, base::TimeDelta::FromSeconds(-5), NULL, &callback)); @@ -507,5 +551,87 @@ TEST(InitProxyResolverTest, CustomPacFails1_WithNegativeDelay) { entries, 3, NetLog::TYPE_INIT_PROXY_RESOLVER)); } +class SynchronousSuccessDhcpFetcher : public DhcpProxyScriptFetcher { + public: + explicit SynchronousSuccessDhcpFetcher(const string16& expected_text) + : gurl_("http://dhcppac/"), expected_text_(expected_text) { + } + + int Fetch(string16* utf16_text, CompletionCallback* callback) OVERRIDE { + *utf16_text = expected_text_; + return OK; + } + + void Cancel() OVERRIDE { + } + + const GURL& GetPacURL() const OVERRIDE { + return gurl_; + } + + const string16& expected_text() const { + return expected_text_; + } + + private: + GURL gurl_; + string16 expected_text_; +}; + +// All of the tests above that use InitProxyResolver have tested +// failure to fetch a PAC file via DHCP configuration, so we now test +// success at downloading and parsing, and then success at downloading, +// failure at parsing. + +TEST(InitProxyResolverTest, AutodetectDhcpSuccess) { + Rules rules; + RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); + RuleBasedProxyScriptFetcher fetcher(&rules); + SynchronousSuccessDhcpFetcher dhcp_fetcher( + WideToUTF16(L"http://bingo/!valid-script")); + + ProxyConfig config; + config.set_auto_detect(true); + + rules.AddSuccessRule("http://bingo/"); + rules.AddFailDownloadRule("http://wpad/wpad.dat"); + + TestCompletionCallback callback; + ProxyConfig effective_config; + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, NULL); + EXPECT_EQ(OK, init.Init( + config, base::TimeDelta(), &effective_config, &callback)); + EXPECT_EQ(dhcp_fetcher.expected_text(), + resolver.script_data()->utf16()); + + EXPECT_TRUE(effective_config.has_pac_url()); + EXPECT_EQ(GURL("http://dhcppac/"), effective_config.pac_url()); +} + +TEST(InitProxyResolverTest, AutodetectDhcpFailParse) { + Rules rules; + RuleBasedProxyResolver resolver(&rules, true /*expects_pac_bytes*/); + RuleBasedProxyScriptFetcher fetcher(&rules); + SynchronousSuccessDhcpFetcher dhcp_fetcher( + WideToUTF16(L"http://bingo/!invalid-script")); + + ProxyConfig config; + config.set_auto_detect(true); + + rules.AddFailParsingRule("http://bingo/"); + rules.AddFailDownloadRule("http://wpad/wpad.dat"); + + TestCompletionCallback callback; + ProxyConfig effective_config; + InitProxyResolver init(&resolver, &fetcher, &dhcp_fetcher, NULL); + // Since there is fallback to DNS-based WPAD, the final error will be that + // it failed downloading, not that it failed parsing. + EXPECT_EQ(kFailedDownloading, + init.Init(config, base::TimeDelta(), &effective_config, &callback)); + EXPECT_EQ(NULL, resolver.script_data()); + + EXPECT_FALSE(effective_config.has_pac_url()); +} + } // namespace } // namespace net diff --git a/net/proxy/mock_proxy_script_fetcher.cc b/net/proxy/mock_proxy_script_fetcher.cc new file mode 100644 index 0000000..3e9b601 --- /dev/null +++ b/net/proxy/mock_proxy_script_fetcher.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2011 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/mock_proxy_script_fetcher.h" + +#include "base/logging.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "net/base/net_errors.h" + +namespace net { + +MockProxyScriptFetcher::MockProxyScriptFetcher() + : pending_request_callback_(NULL), pending_request_text_(NULL) { +} + +// ProxyScriptFetcher implementation. +int MockProxyScriptFetcher::Fetch(const GURL& url, + string16* text, + 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_text_ = text; + return ERR_IO_PENDING; +} + +void MockProxyScriptFetcher::NotifyFetchCompletion( + int result, const std::string& ascii_text) { + DCHECK(has_pending_request()); + *pending_request_text_ = ASCIIToUTF16(ascii_text); + CompletionCallback* callback = pending_request_callback_; + pending_request_callback_ = NULL; + callback->Run(result); +} + +void MockProxyScriptFetcher::Cancel() { +} + +URLRequestContext* MockProxyScriptFetcher::GetRequestContext() const { + return NULL; +} + +const GURL& MockProxyScriptFetcher::pending_request_url() const { + return pending_request_url_; +} + +bool MockProxyScriptFetcher::has_pending_request() const { + return pending_request_callback_ != NULL; +} + +} // namespace net diff --git a/net/proxy/mock_proxy_script_fetcher.h b/net/proxy/mock_proxy_script_fetcher.h new file mode 100644 index 0000000..bd6ac42 --- /dev/null +++ b/net/proxy/mock_proxy_script_fetcher.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 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_MOCK_PROXY_SCRIPT_FETCHER_H_ +#define NET_PROXY_MOCK_PROXY_SCRIPT_FETCHER_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "googleurl/src/gurl.h" +#include "net/proxy/proxy_script_fetcher.h" + +#include <string> + +namespace net { + +class URLRequestContext; + +// A mock ProxyScriptFetcher. No result will be returned to the fetch client +// until we call NotifyFetchCompletion() to set the results. +class MockProxyScriptFetcher : public ProxyScriptFetcher { + public: + MockProxyScriptFetcher(); + + // ProxyScriptFetcher implementation. + virtual int Fetch(const GURL& url, + string16* text, + CompletionCallback* callback) OVERRIDE; + virtual void Cancel() OVERRIDE; + virtual URLRequestContext* GetRequestContext() const OVERRIDE; + + void NotifyFetchCompletion(int result, const std::string& ascii_text); + const GURL& pending_request_url() const; + bool has_pending_request() const; + + private: + GURL pending_request_url_; + CompletionCallback* pending_request_callback_; + string16* pending_request_text_; +}; + +} // namespace net + +#endif // NET_PROXY_MOCK_PROXY_SCRIPT_FETCHER_H_ diff --git a/net/proxy/proxy_script_fetcher.h b/net/proxy/proxy_script_fetcher.h index c8cda24..d488375 100644 --- a/net/proxy/proxy_script_fetcher.h +++ b/net/proxy/proxy_script_fetcher.h @@ -52,7 +52,7 @@ class ProxyScriptFetcher { // Returns the request context that this fetcher uses to issue downloads, // or NULL. - virtual URLRequestContext* GetRequestContext() = 0; + virtual URLRequestContext* GetRequestContext() const = 0; }; } // namespace net diff --git a/net/proxy/proxy_script_fetcher_impl.cc b/net/proxy/proxy_script_fetcher_impl.cc index aabe340..c276c30 100644 --- a/net/proxy/proxy_script_fetcher_impl.cc +++ b/net/proxy/proxy_script_fetcher_impl.cc @@ -171,7 +171,7 @@ void ProxyScriptFetcherImpl::Cancel() { ResetCurRequestState(); } -URLRequestContext* ProxyScriptFetcherImpl::GetRequestContext() { +URLRequestContext* ProxyScriptFetcherImpl::GetRequestContext() const { return url_request_context_; } diff --git a/net/proxy/proxy_script_fetcher_impl.h b/net/proxy/proxy_script_fetcher_impl.h index 419293f..8e9ca3b 100644 --- a/net/proxy/proxy_script_fetcher_impl.h +++ b/net/proxy/proxy_script_fetcher_impl.h @@ -45,9 +45,9 @@ class ProxyScriptFetcherImpl : public ProxyScriptFetcher, // ProxyScriptFetcher methods: virtual int Fetch(const GURL& url, string16* text, - CompletionCallback* callback); - virtual void Cancel(); - virtual URLRequestContext* GetRequestContext(); + CompletionCallback* callback) OVERRIDE; + virtual void Cancel() OVERRIDE; + virtual URLRequestContext* GetRequestContext() const OVERRIDE; // URLRequest::Delegate methods: virtual void OnAuthRequired(URLRequest* request, diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc index a134133..a16cb57 100644 --- a/net/proxy/proxy_service.cc +++ b/net/proxy/proxy_service.cc @@ -16,6 +16,7 @@ #include "net/base/net_errors.h" #include "net/base/net_log.h" #include "net/base/net_util.h" +#include "net/proxy/dhcp_proxy_script_fetcher.h" #include "net/proxy/init_proxy_resolver.h" #include "net/proxy/multi_threaded_proxy_resolver.h" #include "net/proxy/network_delegate_error_observer.h" @@ -409,11 +410,13 @@ ProxyService* ProxyService::CreateUsingV8ProxyResolver( ProxyConfigService* proxy_config_service, size_t num_pac_threads, ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher, HostResolver* host_resolver, NetLog* net_log, NetworkDelegate* network_delegate) { DCHECK(proxy_config_service); DCHECK(proxy_script_fetcher); + DCHECK(dhcp_proxy_script_fetcher); DCHECK(host_resolver); if (num_pac_threads == 0) @@ -433,8 +436,9 @@ ProxyService* ProxyService::CreateUsingV8ProxyResolver( ProxyService* proxy_service = new ProxyService(proxy_config_service, proxy_resolver, net_log); - // Configure PAC script downloads to be issued using |proxy_script_fetcher|. - proxy_service->SetProxyScriptFetcher(proxy_script_fetcher); + // Configure fetchers to use for PAC script downloads and auto-detect. + proxy_service->SetProxyScriptFetchers(proxy_script_fetcher, + dhcp_proxy_script_fetcher); return proxy_service; } @@ -774,11 +778,13 @@ int ProxyService::DidFinishResolvingProxy(ProxyInfo* result, return result_code; } -void ProxyService::SetProxyScriptFetcher( - ProxyScriptFetcher* proxy_script_fetcher) { +void ProxyService::SetProxyScriptFetchers( + ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher) { DCHECK(CalledOnValidThread()); State previous_state = ResetProxyConfig(false); proxy_script_fetcher_.reset(proxy_script_fetcher); + dhcp_proxy_script_fetcher_.reset(dhcp_proxy_script_fetcher); if (previous_state != STATE_NONE) ApplyProxyConfigIfAvailable(); } @@ -927,7 +933,9 @@ void ProxyService::InitializeUsingLastFetchedConfig() { current_state_ = STATE_WAITING_FOR_INIT_PROXY_RESOLVER; init_proxy_resolver_.reset( - new InitProxyResolver(resolver_.get(), proxy_script_fetcher_.get(), + new InitProxyResolver(resolver_.get(), + proxy_script_fetcher_.get(), + dhcp_proxy_script_fetcher_.get(), net_log_)); // If we changed networks recently, we should delay running proxy auto-config. diff --git a/net/proxy/proxy_service.h b/net/proxy/proxy_service.h index f7856e5..cee7ef6 100644 --- a/net/proxy/proxy_service.h +++ b/net/proxy/proxy_service.h @@ -25,6 +25,7 @@ class MessageLoop; namespace net { +class DhcpProxyScriptFetcher; class HostResolver; class InitProxyResolver; class NetworkDelegate; @@ -98,10 +99,12 @@ class ProxyService : public NetworkChangeNotifier::IPAddressObserver, // Call this method with a non-null |pac_request| to cancel the PAC request. void CancelPacRequest(PacRequest* pac_request); - // Sets the ProxyScriptFetcher dependency. This is needed if the ProxyResolver - // is of type ProxyResolverWithoutFetch. ProxyService takes ownership of - // |proxy_script_fetcher|. - void SetProxyScriptFetcher(ProxyScriptFetcher* proxy_script_fetcher); + // Sets the ProxyScriptFetcher and DhcpProxyScriptFetcher dependencies. This + // is needed if the ProxyResolver is of type ProxyResolverWithoutFetch. + // ProxyService takes ownership of both objects. + void SetProxyScriptFetchers( + ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher); ProxyScriptFetcher* GetProxyScriptFetcher() const; // Tells this ProxyService to start using a new ProxyConfigService to @@ -161,6 +164,10 @@ class ProxyService : public NetworkChangeNotifier::IPAddressObserver, // |proxy_script_fetcher| specifies the dependency to use for downloading // any PAC scripts. The resulting ProxyService will take ownership of it. // + // |dhcp_proxy_script_fetcher| specifies the dependency to use for attempting + // to retrieve the most appropriate PAC script configured in DHCP. The + // resulting ProxyService will take ownership of it. + // // |host_resolver| points to the host resolving dependency the PAC script // should use for any DNS queries. It must remain valid throughout the // lifetime of the ProxyService. @@ -174,6 +181,7 @@ class ProxyService : public NetworkChangeNotifier::IPAddressObserver, ProxyConfigService* proxy_config_service, size_t num_pac_threads, ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher, HostResolver* host_resolver, NetLog* net_log, NetworkDelegate* network_delegate); @@ -319,6 +327,11 @@ class ProxyService : public NetworkChangeNotifier::IPAddressObserver, // external PAC script fetching. scoped_ptr<ProxyScriptFetcher> proxy_script_fetcher_; + // The fetcher to use when attempting to download the most appropriate PAC + // script configured in DHCP, if any. Can be NULL if the ProxyResolver has + // no need for DHCP PAC script fetching. + scoped_ptr<DhcpProxyScriptFetcher> dhcp_proxy_script_fetcher_; + // Callback for when |init_proxy_resolver_| is done. CompletionCallbackImpl<ProxyService> init_proxy_resolver_callback_; diff --git a/net/proxy/proxy_service_unittest.cc b/net/proxy/proxy_service_unittest.cc index f5ae3d7..aac8bc9 100644 --- a/net/proxy/proxy_service_unittest.cc +++ b/net/proxy/proxy_service_unittest.cc @@ -15,7 +15,9 @@ #include "net/base/net_log.h" #include "net/base/net_log_unittest.h" #include "net/base/test_completion_callback.h" +#include "net/proxy/dhcp_proxy_script_fetcher.h" #include "net/proxy/mock_proxy_resolver.h" +#include "net/proxy/mock_proxy_script_fetcher.h" #include "net/proxy/proxy_config_service.h" #include "net/proxy/proxy_resolver.h" #include "net/proxy/proxy_script_fetcher.h" @@ -67,53 +69,6 @@ class MockProxyConfigService: public ProxyConfigService { } // namespace -// A mock ProxyScriptFetcher. No result will be returned to the fetch client -// until we call NotifyFetchCompletion() to set the results. -class MockProxyScriptFetcher : public ProxyScriptFetcher { - public: - MockProxyScriptFetcher() - : pending_request_callback_(NULL), pending_request_text_(NULL) { - } - - // ProxyScriptFetcher implementation. - virtual int Fetch(const GURL& url, - string16* text, - 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_text_ = text; - return ERR_IO_PENDING; - } - - void NotifyFetchCompletion(int result, const std::string& ascii_text) { - DCHECK(has_pending_request()); - *pending_request_text_ = ASCIIToUTF16(ascii_text); - CompletionCallback* callback = pending_request_callback_; - pending_request_callback_ = NULL; - callback->Run(result); - } - - virtual void Cancel() {} - - virtual URLRequestContext* GetRequestContext() { return NULL; } - - const GURL& pending_request_url() const { - return pending_request_url_; - } - - bool has_pending_request() const { - return pending_request_callback_ != NULL; - } - - private: - GURL pending_request_url_; - CompletionCallback* pending_request_callback_; - string16* pending_request_text_; -}; - TEST(ProxyServiceTest, Direct) { MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; ProxyService service(new MockProxyConfigService( @@ -449,7 +404,8 @@ TEST(ProxyServiceTest, ProxyResolverFailsParsingJavaScriptMandatoryPac) { ProxyService service(config_service, resolver, NULL); MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; - service.SetProxyScriptFetcher(fetcher); + DhcpProxyScriptFetcher* dhcp_fetcher = new DoNothingDhcpProxyScriptFetcher(); + service.SetProxyScriptFetchers(fetcher, dhcp_fetcher); // Start resolve request. GURL url("http://www.google.com/"); @@ -1183,7 +1139,8 @@ TEST(ProxyServiceTest, InitialPACScriptDownload) { ProxyService service(config_service, resolver, NULL); MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; - service.SetProxyScriptFetcher(fetcher); + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); // Start 3 requests. @@ -1262,7 +1219,8 @@ TEST(ProxyServiceTest, ChangeScriptFetcherWhilePACDownloadInProgress) { ProxyService service(config_service, resolver, NULL); MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; - service.SetProxyScriptFetcher(fetcher); + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); // Start 2 requests. @@ -1290,7 +1248,8 @@ TEST(ProxyServiceTest, ChangeScriptFetcherWhilePACDownloadInProgress) { // the initialization with the new fetcher. fetcher = new MockProxyScriptFetcher; - service.SetProxyScriptFetcher(fetcher); + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); // Nothing has been sent to the resolver yet. EXPECT_TRUE(resolver->pending_requests().empty()); @@ -1319,7 +1278,8 @@ TEST(ProxyServiceTest, CancelWhilePACFetching) { ProxyService service(config_service, resolver, NULL); MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; - service.SetProxyScriptFetcher(fetcher); + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); // Start 3 requests. ProxyInfo info1; @@ -1410,7 +1370,8 @@ TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac) { ProxyService service(config_service, resolver, NULL); MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; - service.SetProxyScriptFetcher(fetcher); + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); // Start 2 requests. @@ -1480,7 +1441,8 @@ TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac2) { ProxyService service(config_service, resolver, NULL); MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; - service.SetProxyScriptFetcher(fetcher); + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); // Start 2 requests. @@ -1555,7 +1517,8 @@ TEST(ProxyServiceTest, FallbackFromAutodetectToCustomToManual) { ProxyService service(config_service, resolver, NULL); MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; - service.SetProxyScriptFetcher(fetcher); + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); // Start 2 requests. @@ -1612,7 +1575,8 @@ TEST(ProxyServiceTest, BypassDoesntApplyToPac) { ProxyService service(config_service, resolver, NULL); MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; - service.SetProxyScriptFetcher(fetcher); + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); // Start 1 requests. @@ -1679,7 +1643,8 @@ TEST(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingFetch) { ProxyService service(config_service, resolver, NULL); MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; - service.SetProxyScriptFetcher(fetcher); + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); // Start 1 request. @@ -1810,7 +1775,8 @@ TEST(ProxyServiceTest, NetworkChangeTriggersPacRefetch) { ProxyService service(config_service, resolver, &log); MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; - service.SetProxyScriptFetcher(fetcher); + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); // Disable the "wait after IP address changes" hack, so this unit-test can // complete quickly. |