diff options
Diffstat (limited to 'chromium/net/proxy_resolution')
96 files changed, 29240 insertions, 0 deletions
diff --git a/chromium/net/proxy_resolution/OWNERS b/chromium/net/proxy_resolution/OWNERS new file mode 100644 index 00000000000..b03baf3a747 --- /dev/null +++ b/chromium/net/proxy_resolution/OWNERS @@ -0,0 +1,6 @@ +eroman@chromium.org +mmenke@chromium.org + +per-file *_v8*=jochen@chromium.org + +# COMPONENT: Internals>Network>Proxy diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.cc b/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.cc new file mode 100644 index 00000000000..e75d189c82d --- /dev/null +++ b/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.cc @@ -0,0 +1,296 @@ +// Copyright (c) 2012 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_resolution/dhcp_pac_file_adapter_fetcher_win.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/free_deleter.h" +#include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" +#include "base/task_runner.h" +#include "base/threading/scoped_blocking_call.h" +#include "base/time/time.h" +#include "net/base/net_errors.h" +#include "net/proxy_resolution/dhcpcsvc_init_win.h" +#include "net/proxy_resolution/pac_file_fetcher_impl.h" +#include "net/url_request/url_request_context.h" + +#include <windows.h> +#include <winsock2.h> +#include <dhcpcsdk.h> + +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, + scoped_refptr<base::TaskRunner> task_runner) + : task_runner_(task_runner), + state_(STATE_START), + result_(ERR_IO_PENDING), + url_request_context_(url_request_context) { + DCHECK(url_request_context_); +} + +DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + Cancel(); +} + +void DhcpProxyScriptAdapterFetcher::Fetch( + const std::string& adapter_name, const CompletionCallback& callback) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK_EQ(state_, STATE_START); + result_ = ERR_IO_PENDING; + pac_script_ = base::string16(); + state_ = STATE_WAIT_DHCP; + callback_ = callback; + + wait_timer_.Start(FROM_HERE, ImplGetTimeout(), + this, &DhcpProxyScriptAdapterFetcher::OnTimeout); + scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery()); + task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind( + &DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter, + dhcp_query.get(), + adapter_name), + base::Bind( + &DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone, + AsWeakPtr(), + dhcp_query)); +} + +void DhcpProxyScriptAdapterFetcher::Cancel() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + callback_.Reset(); + 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_CALLED_ON_VALID_THREAD(thread_checker_); + return state_ == STATE_FINISH; +} + +int DhcpProxyScriptAdapterFetcher::GetResult() const { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + return result_; +} + +base::string16 DhcpProxyScriptAdapterFetcher::GetPacScript() const { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + return pac_script_; +} + +GURL DhcpProxyScriptAdapterFetcher::GetPacURL() const { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + return pac_url_; +} + +DhcpProxyScriptAdapterFetcher::DhcpQuery::DhcpQuery() { +} + +void DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter( + const std::string& adapter_name) { + url_ = ImplGetPacURLFromDhcp(adapter_name); +} + +const std::string& DhcpProxyScriptAdapterFetcher::DhcpQuery::url() const { + return url_; +} + +std::string + DhcpProxyScriptAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp( + const std::string& adapter_name) { + return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name); +} + +DhcpProxyScriptAdapterFetcher::DhcpQuery::~DhcpQuery() { +} + +void DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone( + scoped_refptr<DhcpQuery> dhcp_query) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + // 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(dhcp_query->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_, + base::Bind(&DhcpProxyScriptAdapterFetcher::OnFetcherDone, + base::Unretained(this))); + } +} + +void DhcpProxyScriptAdapterFetcher::OnTimeout() { + DCHECK_EQ(state_, STATE_WAIT_DHCP); + result_ = ERR_TIMED_OUT; + TransitionToFinish(); +} + +void DhcpProxyScriptAdapterFetcher::OnFetcherDone(int result) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + 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; + CompletionCallback callback = callback_; + callback_.Reset(); + + // Be careful not to touch any member state after this, as the client + // may delete us during this callback. + callback.Run(result_); +} + +DhcpProxyScriptAdapterFetcher::State + DhcpProxyScriptAdapterFetcher::state() const { + return state_; +} + +ProxyScriptFetcher* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() { + return new ProxyScriptFetcherImpl(url_request_context_); +} + +DhcpProxyScriptAdapterFetcher::DhcpQuery* + DhcpProxyScriptAdapterFetcher::ImplCreateDhcpQuery() { + return new DhcpQuery(); +} + +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 }; + + 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; + std::unique_ptr<BYTE, base::FreeDeleter> result_buffer; + int retry_count = 0; + DWORD res = NO_ERROR; + do { + result_buffer.reset(static_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. + base::ScopedBlockingCall scoped_blocking_call( + base::BlockingType::MAY_BLOCK); + 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) { + VLOG(1) << "Error fetching PAC URL from DHCP: " << res; + } else if (wpad_params.nBytesData) { + return SanitizeDhcpApiString( + reinterpret_cast<const char*>(wpad_params.Data), + wpad_params.nBytesData); + } + + return ""; +} + +// static +std::string DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( + const char* data, size_t count_bytes) { + // The result should be ASCII, not wide character. Some DHCP + // servers appear to count the trailing NULL in nBytesData, others + // do not. A few (we've had one report, http://crbug.com/297810) + // do not NULL-terminate but may \n-terminate. + // + // Belt and suspenders and elastic waistband: First, ensure we + // NULL-terminate after nBytesData; this is the inner constructor + // with nBytesData as a parameter. Then, return only up to the + // first null in case of embedded NULLs; this is the outer + // constructor that takes the result of c_str() on the inner. If + // the server is giving us back a buffer with embedded NULLs, + // something is broken anyway. Finally, trim trailing whitespace. + std::string result(std::string(data, count_bytes).c_str()); + base::TrimWhitespaceASCII(result, base::TRIM_TRAILING, &result); + return result; +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h b/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h new file mode 100644 index 00000000000..64603e4310f --- /dev/null +++ b/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h @@ -0,0 +1,194 @@ +// Copyright (c) 2012 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_PAC_FILE_ADAPTER_FETCHER_WIN_H_ +#define NET_PROXY_DHCP_PAC_FILE_ADAPTER_FETCHER_WIN_H_ + +#include <stddef.h> + +#include <memory> +#include <string> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "base/threading/thread_checker.h" +#include "base/timer/timer.h" +#include "net/base/completion_callback.h" +#include "net/base/net_export.h" +#include "url/gurl.h" + +namespace base { +class TaskRunner; +} + +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 NET_EXPORT_PRIVATE DhcpProxyScriptAdapterFetcher + : public base::SupportsWeakPtr<DhcpProxyScriptAdapterFetcher> { + public: + // |url_request_context| must outlive DhcpProxyScriptAdapterFetcher. + // |task_runner| will be used to post tasks to a thread. + DhcpProxyScriptAdapterFetcher(URLRequestContext* url_request_context, + scoped_refptr<base::TaskRunner> task_runner); + 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, + const 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 base::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); + + // Sanitizes a string returned via the DHCP API. + static std::string SanitizeDhcpApiString(const char* data, + size_t count_bytes); + + protected: + // 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, + }; + + State state() const; + + // This inner class encapsulates work done on a worker pool thread. + // By using a separate object, we can keep the main object completely + // thread safe and let it be non-refcounted. + class NET_EXPORT_PRIVATE DhcpQuery + : public base::RefCountedThreadSafe<DhcpQuery> { + public: + DhcpQuery(); + + // This method should run on a worker pool thread, via PostTaskAndReply. + // After it has run, the |url()| method on this object will return the + // URL retrieved. + void GetPacURLForAdapter(const std::string& adapter_name); + + // Returns the URL retrieved for the given adapter, once the task has run. + const std::string& url() const; + + protected: + // Virtual method introduced to allow unit testing. + virtual std::string ImplGetPacURLFromDhcp(const std::string& adapter_name); + + friend class base::RefCountedThreadSafe<DhcpQuery>; + virtual ~DhcpQuery(); + + private: + // The URL retrieved for the given adapter. + std::string url_; + + DISALLOW_COPY_AND_ASSIGN(DhcpQuery); + }; + + // Virtual methods introduced to allow unit testing. + virtual ProxyScriptFetcher* ImplCreateScriptFetcher(); + virtual DhcpQuery* ImplCreateDhcpQuery(); + virtual base::TimeDelta ImplGetTimeout() const; + + private: + // Event/state transition handlers + void OnDhcpQueryDone(scoped_refptr<DhcpQuery> dhcp_query); + void OnTimeout(); + void OnFetcherDone(int result); + void TransitionToFinish(); + + // TaskRunner for posting tasks to a worker thread. + scoped_refptr<base::TaskRunner> task_runner_; + + // Current state of this state machine. + State state_; + + // A network error indicating result of operation. + int result_; + + // Empty string or the PAC script downloaded. + base::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_; + + // Fetcher to retrieve PAC files once URL is known. + std::unique_ptr<ProxyScriptFetcher> script_fetcher_; + + // Implements a timeout on the call to the Win32 DHCP API. + base::OneShotTimer wait_timer_; + + URLRequestContext* const url_request_context_; + + THREAD_CHECKER(thread_checker_); + + DISALLOW_IMPLICIT_CONSTRUCTORS(DhcpProxyScriptAdapterFetcher); +}; + +} // namespace net + +#endif // NET_PROXY_DHCP_PAC_FILE_ADAPTER_FETCHER_WIN_H_ diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win_unittest.cc b/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win_unittest.cc new file mode 100644 index 00000000000..0093b7787c2 --- /dev/null +++ b/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win_unittest.cc @@ -0,0 +1,340 @@ +// Copyright (c) 2012 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_resolution/dhcp_pac_file_adapter_fetcher_win.h" + +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/task_scheduler/post_task.h" +#include "base/test/test_timeouts.h" +#include "base/threading/thread_restrictions.h" +#include "base/timer/elapsed_timer.h" +#include "base/timer/timer.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/proxy_resolution/mock_pac_file_fetcher.h" +#include "net/proxy_resolution/pac_file_fetcher_impl.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "net/test/gtest_util.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using net::test::IsError; +using net::test::IsOk; + +namespace net { + +namespace { + +const char kPacUrl[] = "http://pacserver/script.pac"; + +// In net/proxy_resolution/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, + scoped_refptr<base::TaskRunner> task_runner) + : DhcpProxyScriptAdapterFetcher(context, task_runner), + dhcp_delay_(base::TimeDelta::FromMilliseconds(1)), + timeout_(TestTimeouts::action_timeout()), + configured_url_(kPacUrl), + fetcher_delay_ms_(1), + fetcher_result_(OK), + pac_script_("bingo") { + } + + void Cancel() override { + DhcpProxyScriptAdapterFetcher::Cancel(); + fetcher_ = NULL; + } + + 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(FROM_HERE, + base::TimeDelta::FromMilliseconds(fetcher_delay_ms_), + this, &MockDhcpProxyScriptAdapterFetcher::OnFetcherTimer); + } + return fetcher_; + } + + class DelayingDhcpQuery : public DhcpQuery { + public: + explicit DelayingDhcpQuery() + : DhcpQuery(), + test_finished_event_( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED) {} + + std::string ImplGetPacURLFromDhcp( + const std::string& adapter_name) override { + base::ElapsedTimer timer; + { + base::ScopedAllowBaseSyncPrimitivesForTesting + scoped_allow_base_sync_primitives; + test_finished_event_.TimedWait(dhcp_delay_); + } + return configured_url_; + } + + base::WaitableEvent test_finished_event_; + base::TimeDelta dhcp_delay_; + std::string configured_url_; + + private: + ~DelayingDhcpQuery() override {} + }; + + DhcpQuery* ImplCreateDhcpQuery() override { + dhcp_query_ = new DelayingDhcpQuery(); + dhcp_query_->dhcp_delay_ = dhcp_delay_; + dhcp_query_->configured_url_ = configured_url_; + return dhcp_query_.get(); + } + + // Use a shorter timeout so tests can finish more quickly. + base::TimeDelta ImplGetTimeout() const override { return timeout_; } + + 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(dhcp_query_.get()); + dhcp_query_->test_finished_event_.Signal(); + } + + base::TimeDelta dhcp_delay_; + base::TimeDelta timeout_; + std::string configured_url_; + int fetcher_delay_ms_; + int fetcher_result_; + std::string pac_script_; + MockProxyScriptFetcher* fetcher_; + base::OneShotTimer fetcher_timer_; + scoped_refptr<DelayingDhcpQuery> dhcp_query_; +}; + +class FetcherClient { + public: + FetcherClient() + : url_request_context_(new TestURLRequestContext()), + fetcher_(new MockDhcpProxyScriptAdapterFetcher( + url_request_context_.get(), + base::CreateSequencedTaskRunnerWithTraits( + {base::MayBlock(), + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}))) {} + + void WaitForResult(int expected_error) { + EXPECT_EQ(expected_error, callback_.WaitForResult()); + } + + void RunTest() { + fetcher_->Fetch("adapter name", callback_.callback()); + } + + void FinishTestAllowCleanup() { + fetcher_->FinishTest(); + base::RunLoop().RunUntilIdle(); + } + + TestCompletionCallback callback_; + std::unique_ptr<URLRequestContext> url_request_context_; + std::unique_ptr<MockDhcpProxyScriptAdapterFetcher> fetcher_; + base::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_THAT(client.fetcher_->GetResult(), IsError(ERR_PAC_NOT_IN_DHCP)); + EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); +} + +TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLInDhcp) { + FetcherClient client; + client.RunTest(); + client.WaitForResult(OK); + ASSERT_TRUE(client.fetcher_->DidFinish()); + EXPECT_THAT(client.fetcher_->GetResult(), IsOk()); + EXPECT_EQ(base::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_ = TestTimeouts::action_max_timeout(); + client.fetcher_->timeout_ = base::TimeDelta::FromMilliseconds(25); + + base::ElapsedTimer timer; + client.RunTest(); + // An error different from this would be received if the timeout didn't + // kick in. + client.WaitForResult(ERR_TIMED_OUT); + + ASSERT_TRUE(client.fetcher_->DidFinish()); + EXPECT_THAT(client.fetcher_->GetResult(), IsError(ERR_TIMED_OUT)); + EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); + EXPECT_EQ(GURL(), client.fetcher_->GetPacURL()); + client.FinishTestAllowCleanup(); +} + +TEST(DhcpProxyScriptAdapterFetcher, CancelWhileDhcp) { + FetcherClient client; + client.RunTest(); + client.fetcher_->Cancel(); + base::RunLoop().RunUntilIdle(); + ASSERT_FALSE(client.fetcher_->DidFinish()); + ASSERT_TRUE(client.fetcher_->WasCancelled()); + EXPECT_THAT(client.fetcher_->GetResult(), IsError(ERR_ABORTED)); + EXPECT_EQ(base::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(base::TimeDelta::FromMilliseconds(10)); + base::RunLoop().RunUntilIdle(); + } + client.fetcher_->Cancel(); + base::RunLoop().RunUntilIdle(); + ASSERT_FALSE(client.fetcher_->DidFinish()); + ASSERT_TRUE(client.fetcher_->WasCancelled()); + EXPECT_THAT(client.fetcher_->GetResult(), IsError(ERR_ABORTED)); + EXPECT_EQ(base::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_THAT(client.fetcher_->GetResult(), IsOk()); + EXPECT_EQ(base::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, + scoped_refptr<base::TaskRunner> task_runner) + : MockDhcpProxyScriptAdapterFetcher(context, task_runner), + 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) { + EmbeddedTestServer test_server; + test_server.ServeFilesFromSourceDirectory( + "net/data/proxy_script_fetcher_unittest"); + ASSERT_TRUE(test_server.Start()); + + GURL configured_url = test_server.GetURL("/downloadable.pac"); + + FetcherClient client; + TestURLRequestContext url_request_context; + client.fetcher_.reset(new MockDhcpRealFetchProxyScriptAdapterFetcher( + &url_request_context, + base::CreateTaskRunnerWithTraits( + {base::MayBlock(), + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}))); + client.fetcher_->configured_url_ = configured_url.spec(); + client.RunTest(); + client.WaitForResult(OK); + ASSERT_TRUE(client.fetcher_->DidFinish()); + EXPECT_THAT(client.fetcher_->GetResult(), IsOk()); + EXPECT_EQ(base::string16(L"-downloadable.pac-\n"), + client.fetcher_->GetPacScript()); + EXPECT_EQ(configured_url, + client.fetcher_->GetPacURL()); +} + +#define BASE_URL "http://corpserver/proxy.pac" + +TEST(DhcpProxyScriptAdapterFetcher, SanitizeDhcpApiString) { + const size_t kBaseUrlLen = strlen(BASE_URL); + + // Default case. + EXPECT_EQ(BASE_URL, + DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( + BASE_URL, kBaseUrlLen)); + + // Trailing \n and no null-termination. + EXPECT_EQ(BASE_URL, + DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( + BASE_URL "\nblablabla", kBaseUrlLen + 1)); + + // Embedded NULLs. + EXPECT_EQ(BASE_URL, + DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( + BASE_URL "\0foo\0blat", kBaseUrlLen + 9)); +} + +#undef BASE_URL + +} // namespace + +} // namespace net diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher.cc b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher.cc new file mode 100644 index 00000000000..eda798dedcb --- /dev/null +++ b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 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_resolution/dhcp_pac_file_fetcher.h" + +#include "net/base/net_errors.h" + +namespace net { + +std::string DhcpProxyScriptFetcher::GetFetcherName() const { + return std::string(); +} + +DhcpProxyScriptFetcher::DhcpProxyScriptFetcher() = default; + +DhcpProxyScriptFetcher::~DhcpProxyScriptFetcher() = default; + +DoNothingDhcpProxyScriptFetcher::DoNothingDhcpProxyScriptFetcher() = default; + +DoNothingDhcpProxyScriptFetcher::~DoNothingDhcpProxyScriptFetcher() = default; + +int DoNothingDhcpProxyScriptFetcher::Fetch( + base::string16* utf16_text, const CompletionCallback& callback) { + return ERR_NOT_IMPLEMENTED; +} + +void DoNothingDhcpProxyScriptFetcher::Cancel() {} + +void DoNothingDhcpProxyScriptFetcher::OnShutdown() {} + +const GURL& DoNothingDhcpProxyScriptFetcher::GetPacURL() const { + return gurl_; +} + +std::string DoNothingDhcpProxyScriptFetcher::GetFetcherName() const { + return "do nothing"; +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher.h b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher.h new file mode 100644 index 00000000000..afd9793e28f --- /dev/null +++ b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher.h @@ -0,0 +1,107 @@ +// Copyright (c) 2012 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_PAC_FILE_FETCHER_H_ +#define NET_PROXY_DHCP_PAC_FILE_FETCHER_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/strings/string16.h" +#include "net/base/completion_callback.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/pac_file_fetcher.h" +#include "url/gurl.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 NET_EXPORT_PRIVATE 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(base::string16* utf16_text, + const CompletionCallback& callback) = 0; + + // Aborts the in-progress fetch (if any). + virtual void Cancel() = 0; + + // Fails the in-progress fetch (if any) and future requests will fail + // immediately. Must be called before the URLRequestContext the fetcher was + // created with is torn down. + virtual void OnShutdown() = 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 NET_EXPORT_PRIVATE DoNothingDhcpProxyScriptFetcher + : public DhcpProxyScriptFetcher { + public: + DoNothingDhcpProxyScriptFetcher(); + ~DoNothingDhcpProxyScriptFetcher() override; + + int Fetch(base::string16* utf16_text, + const CompletionCallback& callback) override; + void Cancel() override; + void OnShutdown() override; + const GURL& GetPacURL() const override; + std::string GetFetcherName() const override; + + private: + GURL gurl_; + DISALLOW_COPY_AND_ASSIGN(DoNothingDhcpProxyScriptFetcher); +}; + +} // namespace net + +#endif // NET_PROXY_DHCP_PAC_FILE_FETCHER_H_ diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.cc b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.cc new file mode 100644 index 00000000000..255e013ca53 --- /dev/null +++ b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.cc @@ -0,0 +1,29 @@ +// 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_resolution/dhcp_pac_file_fetcher_factory.h" + +#include "net/base/net_errors.h" +#include "net/proxy_resolution/dhcp_pac_file_fetcher.h" + +#if defined(OS_WIN) +#include "net/proxy_resolution/dhcp_pac_file_fetcher_win.h" +#endif + +namespace net { + +DhcpProxyScriptFetcherFactory::DhcpProxyScriptFetcherFactory() = default; + +DhcpProxyScriptFetcherFactory::~DhcpProxyScriptFetcherFactory() = default; + +std::unique_ptr<DhcpProxyScriptFetcher> DhcpProxyScriptFetcherFactory::Create( + URLRequestContext* context) { +#if defined(OS_WIN) + return std::make_unique<DhcpProxyScriptFetcherWin>(context); +#else + return std::make_unique<DoNothingDhcpProxyScriptFetcher>(); +#endif +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.h b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.h new file mode 100644 index 00000000000..ab62b6b8670 --- /dev/null +++ b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.h @@ -0,0 +1,56 @@ +// Copyright (c) 2012 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_PAC_FILE_FETCHER_FACTORY_H_ +#define NET_PROXY_DHCP_PAC_FILE_FETCHER_FACTORY_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "net/base/completion_callback.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/dhcp_pac_file_fetcher.h" + +namespace net { + +class URLRequestContext; + +// 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 NET_EXPORT DhcpProxyScriptFetcherFactory { + public: + DhcpProxyScriptFetcherFactory(); + + virtual ~DhcpProxyScriptFetcherFactory(); + + // 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(). + virtual std::unique_ptr<DhcpProxyScriptFetcher> Create( + URLRequestContext* url_request_context); + + private: + DISALLOW_COPY_AND_ASSIGN(DhcpProxyScriptFetcherFactory); +}; + +} // namespace net + +#endif // NET_PROXY_DHCP_PAC_FILE_FETCHER_FACTORY_H_ diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory_unittest.cc b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory_unittest.cc new file mode 100644 index 00000000000..c8e73d3b360 --- /dev/null +++ b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory_unittest.cc @@ -0,0 +1,35 @@ +// Copyright (c) 2012 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_resolution/dhcp_pac_file_fetcher.h" +#include "net/proxy_resolution/dhcp_pac_file_fetcher_factory.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace { + +#if defined(OS_WIN) +TEST(DhcpProxyScriptFetcherFactoryTest, WindowsFetcherOnWindows) { + DhcpProxyScriptFetcherFactory factory; + TestURLRequestContext context; + std::unique_ptr<DhcpProxyScriptFetcher> fetcher(factory.Create(&context)); + ASSERT_TRUE(fetcher.get()); + EXPECT_EQ("win", fetcher->GetFetcherName()); +} + +#else // !defined(OS_WIN) + +TEST(DhcpProxyScriptFetcherFactoryTest, ReturnNullOnUnsupportedPlatforms) { + DhcpProxyScriptFetcherFactory factory; + TestURLRequestContext context; + std::unique_ptr<DhcpProxyScriptFetcher> fetcher(factory.Create(&context)); + ASSERT_TRUE(fetcher.get()); + EXPECT_EQ("do nothing", fetcher->GetFetcherName()); +} + +#endif // defined(OS_WIN) + +} // namespace +} // namespace net diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.cc b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.cc new file mode 100644 index 00000000000..f80f9ef1450 --- /dev/null +++ b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.cc @@ -0,0 +1,465 @@ +// Copyright (c) 2012 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_resolution/dhcp_pac_file_fetcher_win.h" + +#include <memory> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/containers/queue.h" +#include "base/memory/free_deleter.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "base/task_scheduler/post_task.h" +#include "base/threading/scoped_blocking_call.h" +#include "net/base/net_errors.h" +#include "net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h" + +#include <winsock2.h> +#include <iphlpapi.h> + +namespace { + +// Maximum number of DHCP lookup tasks running concurrently. This is chosen +// based on the following UMA data: +// - When OnWaitTimer fires, ~99.8% of users have 6 or fewer network +// adapters enabled for DHCP in total. +// - At the same measurement point, ~99.7% of users have 3 or fewer pending +// DHCP adapter lookups. +// - There is however a very long and thin tail of users who have +// systems reporting up to 100+ adapters (this must be some very weird +// OS bug (?), probably the cause of http://crbug.com/240034). +// +// Th value is chosen such that DHCP lookup tasks don't prevent other tasks from +// running even on systems that report a huge number of network adapters, while +// giving a good chance of getting back results for any responsive adapters. +constexpr int kMaxConcurrentDhcpLookupTasks = 12; + +// 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. +constexpr base::TimeDelta kMaxWaitAfterFirstResult = + base::TimeDelta::FromMilliseconds(400); + +// A TaskRunner that never schedules more than |kMaxConcurrentDhcpLookupTasks| +// tasks concurrently. +class TaskRunnerWithCap : public base::TaskRunner { + public: + TaskRunnerWithCap() = default; + + bool PostDelayedTask(const base::Location& from_here, + base::OnceClosure task, + base::TimeDelta delay) override { + // Delayed tasks are not supported. + DCHECK(delay.is_zero()); + + // Wrap the task in a callback that runs |task|, then tries to schedule a + // task from |pending_tasks_|. + base::OnceClosure wrapped_task = + base::BindOnce(&TaskRunnerWithCap::RunTaskAndSchedulePendingTask, this, + std::move(task)); + + { + base::AutoLock auto_lock(lock_); + + // If |kMaxConcurrentDhcpLookupTasks| tasks are scheduled, move the task + // to |pending_tasks_|. + DCHECK_LE(num_scheduled_tasks_, kMaxConcurrentDhcpLookupTasks); + if (num_scheduled_tasks_ == kMaxConcurrentDhcpLookupTasks) { + pending_tasks_.emplace(from_here, std::move(wrapped_task)); + return true; + } + + // If less than |kMaxConcurrentDhcpLookupTasks| tasks are scheduled, + // increment |num_scheduled_tasks_| and schedule the task. + ++num_scheduled_tasks_; + } + + task_runner_->PostTask(from_here, std::move(wrapped_task)); + return true; + } + + bool RunsTasksInCurrentSequence() const override { + return task_runner_->RunsTasksInCurrentSequence(); + } + + private: + struct LocationAndTask { + LocationAndTask() = default; + LocationAndTask(const base::Location& from_here, base::OnceClosure task) + : from_here(from_here), task(std::move(task)) {} + base::Location from_here; + base::OnceClosure task; + }; + + ~TaskRunnerWithCap() override = default; + + void RunTaskAndSchedulePendingTask(base::OnceClosure task) { + // Run |task|. + std::move(task).Run(); + + // If |pending_tasks_| is non-empty, schedule a task from it. Otherwise, + // decrement |num_scheduled_tasks_|. + LocationAndTask task_to_schedule; + + { + base::AutoLock auto_lock(lock_); + + DCHECK_GT(num_scheduled_tasks_, 0); + if (pending_tasks_.empty()) { + --num_scheduled_tasks_; + return; + } + + task_to_schedule = std::move(pending_tasks_.front()); + pending_tasks_.pop(); + } + + DCHECK(task_to_schedule.task); + task_runner_->PostTask(task_to_schedule.from_here, + std::move(task_to_schedule.task)); + } + + const scoped_refptr<base::TaskRunner> task_runner_ = + base::CreateTaskRunnerWithTraits( + {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN, + base::TaskPriority::USER_VISIBLE}); + + // Synchronizes access to members below. + base::Lock lock_; + + // Number of tasks that are currently scheduled. + int num_scheduled_tasks_ = 0; + + // Tasks that are waiting to be scheduled. + base::queue<LocationAndTask> pending_tasks_; + + DISALLOW_COPY_AND_ASSIGN(TaskRunnerWithCap); +}; + +} // namespace + +namespace net { + +DhcpProxyScriptFetcherWin::DhcpProxyScriptFetcherWin( + URLRequestContext* url_request_context) + : state_(STATE_START), + num_pending_fetchers_(0), + destination_string_(NULL), + url_request_context_(url_request_context), + task_runner_(base::MakeRefCounted<TaskRunnerWithCap>()) { + DCHECK(url_request_context_); +} + +DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + // Count as user-initiated if we are not yet in STATE_DONE. + Cancel(); +} + +int DhcpProxyScriptFetcherWin::Fetch(base::string16* utf16_text, + const CompletionCallback& callback) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + if (state_ != STATE_START && state_ != STATE_DONE) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + + if (!url_request_context_) + return ERR_CONTEXT_SHUT_DOWN; + + state_ = STATE_WAIT_ADAPTERS; + callback_ = callback; + destination_string_ = utf16_text; + + last_query_ = ImplCreateAdapterQuery(); + task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind( + &DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames, + last_query_.get()), + base::Bind(&DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone, + AsWeakPtr(), last_query_)); + + return ERR_IO_PENDING; +} + +void DhcpProxyScriptFetcherWin::Cancel() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + CancelImpl(); +} + +void DhcpProxyScriptFetcherWin::OnShutdown() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + // Back up callback, if there is one, as CancelImpl() will destroy it. + net::CompletionCallback callback = std::move(callback_); + + // Cancel current request, if there is one. + CancelImpl(); + + // Prevent future network requests. + url_request_context_ = nullptr; + + // Invoke callback with error, if present. + if (callback) + callback.Run(ERR_CONTEXT_SHUT_DOWN); +} + +void DhcpProxyScriptFetcherWin::CancelImpl() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (state_ != STATE_DONE) { + callback_.Reset(); + wait_timer_.Stop(); + state_ = STATE_DONE; + + for (FetcherVector::iterator it = fetchers_.begin(); + it != fetchers_.end(); + ++it) { + (*it)->Cancel(); + } + + fetchers_.clear(); + } +} + +void DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone( + scoped_refptr<AdapterQuery> query) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + // This can happen if this object is reused for multiple queries, + // and a previous query was cancelled before it completed. + if (query.get() != last_query_.get()) + return; + last_query_ = NULL; + + // Enable unit tests to wait for this to happen; in production this function + // call is a no-op. + ImplOnGetCandidateAdapterNamesDone(); + + // We may have been cancelled. + if (state_ != STATE_WAIT_ADAPTERS) + return; + + state_ = STATE_NO_RESULTS; + + const std::set<std::string>& adapter_names = query->adapter_names(); + + if (adapter_names.empty()) { + TransitionToDone(); + return; + } + + for (std::set<std::string>::const_iterator it = adapter_names.begin(); + it != adapter_names.end(); + ++it) { + std::unique_ptr<DhcpProxyScriptAdapterFetcher> fetcher( + ImplCreateAdapterFetcher()); + fetcher->Fetch( + *it, base::Bind(&DhcpProxyScriptFetcherWin::OnFetcherDone, + base::Unretained(this))); + fetchers_.push_back(std::move(fetcher)); + } + num_pending_fetchers_ = fetchers_.size(); +} + +std::string DhcpProxyScriptFetcherWin::GetFetcherName() const { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + return "win"; +} + +const GURL& DhcpProxyScriptFetcherWin::GetPacURL() const { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + 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(FROM_HERE, + ImplGetMaxWait(), 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); + + int result = ERR_PAC_NOT_IN_DHCP; // Default if no fetchers. + if (!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. + 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; + } + } + } + } + } + + CompletionCallback callback = callback_; + CancelImpl(); + DCHECK_EQ(state_, STATE_DONE); + DCHECK(fetchers_.empty()); + DCHECK(callback_.is_null()); // Invariant of data. + + // We may be deleted re-entrantly within this outcall. + callback.Run(result); +} + +int DhcpProxyScriptFetcherWin::num_pending_fetchers() const { + return num_pending_fetchers_; +} + +URLRequestContext* DhcpProxyScriptFetcherWin::url_request_context() const { + return url_request_context_; +} + +scoped_refptr<base::TaskRunner> DhcpProxyScriptFetcherWin::GetTaskRunner() { + return task_runner_; +} + +DhcpProxyScriptAdapterFetcher* + DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() { + return new DhcpProxyScriptAdapterFetcher(url_request_context_, task_runner_); +} + +DhcpProxyScriptFetcherWin::AdapterQuery* + DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() { + return new AdapterQuery(); +} + +base::TimeDelta DhcpProxyScriptFetcherWin::ImplGetMaxWait() { + return kMaxWaitAfterFirstResult; +} + +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; + std::unique_ptr<IP_ADAPTER_ADDRESSES, base::FreeDeleter> adapters; + ULONG error = ERROR_SUCCESS; + int num_tries = 0; + + do { + adapters.reset(static_cast<IP_ADAPTER_ADDRESSES*>(malloc(adapters_size))); + // Return only unicast addresses, and skip information we do not need. + base::ScopedBlockingCall scoped_blocking_call( + base::BlockingType::MAY_BLOCK); + 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; +} + +DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() { +} + +void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() { + ImplGetCandidateAdapterNames(&adapter_names_); +} + +const std::set<std::string>& + DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const { + return adapter_names_; +} + +bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames( + std::set<std::string>* adapter_names) { + return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names); +} + +DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() { +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.h b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.h new file mode 100644 index 00000000000..3cc16586f08 --- /dev/null +++ b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.h @@ -0,0 +1,186 @@ +// Copyright (c) 2012 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_PAC_FILE_FETCHER_WIN_H_ +#define NET_PROXY_DHCP_PAC_FILE_FETCHER_WIN_H_ + +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/dhcp_pac_file_fetcher.h" + +namespace base { +class TaskRunner; +} + +namespace net { + +class DhcpProxyScriptAdapterFetcher; +class URLRequestContext; + +// Windows-specific implementation. +class NET_EXPORT_PRIVATE DhcpProxyScriptFetcherWin + : public DhcpProxyScriptFetcher, + public base::SupportsWeakPtr<DhcpProxyScriptFetcherWin> { + 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); + ~DhcpProxyScriptFetcherWin() override; + + // DhcpProxyScriptFetcher implementation. + int Fetch(base::string16* utf16_text, + const CompletionCallback& callback) override; + void Cancel() override; + void OnShutdown() 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: + int num_pending_fetchers() const; + + URLRequestContext* url_request_context() const; + + scoped_refptr<base::TaskRunner> GetTaskRunner(); + + // This inner class encapsulate work done on a worker pool thread. + // The class calls GetCandidateAdapterNames, which can take a couple of + // hundred milliseconds. + class NET_EXPORT_PRIVATE AdapterQuery + : public base::RefCountedThreadSafe<AdapterQuery> { + public: + AdapterQuery(); + + // This is the method that runs on the worker pool thread. + void GetCandidateAdapterNames(); + + // This set is valid after GetCandidateAdapterNames has + // been run. Its lifetime is scoped by this object. + const std::set<std::string>& adapter_names() const; + + protected: + // Virtual method introduced to allow unit testing. + virtual bool ImplGetCandidateAdapterNames( + std::set<std::string>* adapter_names); + + friend class base::RefCountedThreadSafe<AdapterQuery>; + virtual ~AdapterQuery(); + + private: + // This is constructed on the originating thread, then used on the + // worker thread, then used again on the originating thread only when + // the task has completed on the worker thread. No locking required. + std::set<std::string> adapter_names_; + + DISALLOW_COPY_AND_ASSIGN(AdapterQuery); + }; + + // Virtual methods introduced to allow unit testing. + virtual DhcpProxyScriptAdapterFetcher* ImplCreateAdapterFetcher(); + virtual AdapterQuery* ImplCreateAdapterQuery(); + virtual base::TimeDelta ImplGetMaxWait(); + virtual void ImplOnGetCandidateAdapterNamesDone() {} + + private: + // Event/state transition handlers + void CancelImpl(); + void OnGetCandidateAdapterNamesDone(scoped_refptr<AdapterQuery> query); + void OnFetcherDone(int result); + void OnWaitTimer(); + void TransitionToDone(); + + // 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->WAIT_ADAPTERS when it starts a + // worker thread to get the list of adapters with DHCP enabled. + // It then goes from WAIT_ADAPTERS->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_WAIT_ADAPTERS, + 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. + using FetcherVector = + std::vector<std::unique_ptr<DhcpProxyScriptAdapterFetcher>>; + FetcherVector fetchers_; + + // 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 callback_; + + // Pointer to string we will write results to. Not valid in states + // START and DONE. + base::string16* destination_string_; + + // PAC URL retrieved from DHCP, if any. Valid only in state STATE_DONE. + GURL pac_url_; + + base::OneShotTimer wait_timer_; + + // Set to nullptr on cancellation. + URLRequestContext* url_request_context_; + + // NULL or the AdapterQuery currently in flight. + scoped_refptr<AdapterQuery> last_query_; + + // Time |Fetch()| was last called, 0 if never. + base::TimeTicks fetch_start_time_; + + // TaskRunner used for all DHCP lookup tasks. + const scoped_refptr<base::TaskRunner> task_runner_; + + THREAD_CHECKER(thread_checker_); + + DISALLOW_IMPLICIT_CONSTRUCTORS(DhcpProxyScriptFetcherWin); +}; + +} // namespace net + +#endif // NET_PROXY_DHCP_PAC_FILE_FETCHER_WIN_H_ diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win_unittest.cc b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win_unittest.cc new file mode 100644 index 00000000000..3490ef37921 --- /dev/null +++ b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win_unittest.cc @@ -0,0 +1,682 @@ +// Copyright (c) 2012 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_resolution/dhcp_pac_file_fetcher_win.h" + +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/rand_util.h" +#include "base/run_loop.h" +#include "base/test/test_timeouts.h" +#include "base/threading/platform_thread.h" +#include "base/timer/elapsed_timer.h" +#include "net/base/completion_callback.h" +#include "net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h" +#include "net/test/gtest_util.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using net::test::IsError; +using net::test::IsOk; + +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; + DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name); + } +} + +// Helper for RealFetch* tests below. +class RealFetchTester { + public: + RealFetchTester() + : context_(new TestURLRequestContext), + fetcher_(new DhcpProxyScriptFetcherWin(context_.get())), + finished_(false), + on_completion_is_error_(false) { + // Make sure the test ends. + timeout_.Start(FROM_HERE, + base::TimeDelta::FromSeconds(5), this, &RealFetchTester::OnTimeout); + } + + void RunTest() { + int result = fetcher_->Fetch( + &pac_text_, + base::Bind(&RealFetchTester::OnCompletion, base::Unretained(this))); + if (result != ERR_IO_PENDING) + finished_ = true; + } + + void RunTestWithCancel() { + RunTest(); + fetcher_->Cancel(); + } + + void RunTestWithDeferredCancel() { + // Put the cancellation into the queue before even running the + // test to avoid the chance of one of the adapter fetcher worker + // threads completing before cancellation. See http://crbug.com/86756. + cancel_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(0), + this, &RealFetchTester::OnCancelTimer); + RunTest(); + } + + void OnCompletion(int result) { + if (on_completion_is_error_) { + FAIL() << "Received completion for test in which this is error."; + } + finished_ = true; + } + + void OnTimeout() { + OnCompletion(0); + } + + void OnCancelTimer() { + fetcher_->Cancel(); + finished_ = true; + } + + void WaitUntilDone() { + while (!finished_) { + base::RunLoop().RunUntilIdle(); + } + base::RunLoop().RunUntilIdle(); + } + + // 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(base::TimeDelta::FromMilliseconds(30)); + } + + std::unique_ptr<URLRequestContext> context_; + std::unique_ptr<DhcpProxyScriptFetcherWin> fetcher_; + bool finished_; + base::string16 pac_text_; + base::OneShotTimer timeout_; + base::OneShotTimer 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(); + fetcher.fetcher_->GetPacURL().possibly_invalid_spec(); + + 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(); + base::RunLoop().RunUntilIdle(); + + // Attempt to avoid Valgrind leak reports in case worker thread is + // still running. + fetcher.FinishTestAllowCleanup(); +} + +// For RealFetchWithDeferredCancel, below. +class DelayingDhcpProxyScriptAdapterFetcher + : public DhcpProxyScriptAdapterFetcher { + public: + DelayingDhcpProxyScriptAdapterFetcher( + URLRequestContext* url_request_context, + scoped_refptr<base::TaskRunner> task_runner) + : DhcpProxyScriptAdapterFetcher(url_request_context, task_runner) { + } + + class DelayingDhcpQuery : public DhcpQuery { + public: + explicit DelayingDhcpQuery() : DhcpQuery() {} + + std::string ImplGetPacURLFromDhcp( + const std::string& adapter_name) override { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); + return DhcpQuery::ImplGetPacURLFromDhcp(adapter_name); + } + + private: + ~DelayingDhcpQuery() override {} + }; + + DhcpQuery* ImplCreateDhcpQuery() override { + return new DelayingDhcpQuery(); + } +}; + +// For RealFetchWithDeferredCancel, below. +class DelayingDhcpProxyScriptFetcherWin + : public DhcpProxyScriptFetcherWin { + public: + explicit DelayingDhcpProxyScriptFetcherWin( + URLRequestContext* context) + : DhcpProxyScriptFetcherWin(context) { + } + + DhcpProxyScriptAdapterFetcher* ImplCreateAdapterFetcher() override { + return new DelayingDhcpProxyScriptAdapterFetcher(url_request_context(), + GetTaskRunner()); + } +}; + +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_.get())); + 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(URLRequestContext* context, + scoped_refptr<base::TaskRunner> runner) + : DhcpProxyScriptAdapterFetcher(context, runner), + did_finish_(false), + result_(OK), + pac_script_(L"bingo"), + fetch_delay_ms_(1) { + } + + void Fetch(const std::string& adapter_name, + const CompletionCallback& callback) override { + callback_ = callback; + timer_.Start(FROM_HERE, 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_; + } + + base::string16 GetPacScript() const override { + return pac_script_; + } + + void OnTimer() { + callback_.Run(result_); + } + + void Configure(bool did_finish, + int result, + base::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_; + base::string16 pac_script_; + int fetch_delay_ms_; + CompletionCallback callback_; + base::OneShotTimer timer_; +}; + +class MockDhcpProxyScriptFetcherWin : public DhcpProxyScriptFetcherWin { + public: + class MockAdapterQuery : public AdapterQuery { + public: + MockAdapterQuery() { + } + + bool ImplGetCandidateAdapterNames( + std::set<std::string>* adapter_names) override { + adapter_names->insert( + mock_adapter_names_.begin(), mock_adapter_names_.end()); + return true; + } + + std::vector<std::string> mock_adapter_names_; + + private: + ~MockAdapterQuery() override {} + }; + + MockDhcpProxyScriptFetcherWin(URLRequestContext* context) + : DhcpProxyScriptFetcherWin(context), + num_fetchers_created_(0), + worker_finished_event_( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED) { + ResetTestState(); + } + + ~MockDhcpProxyScriptFetcherWin() override { ResetTestState(); } + + using DhcpProxyScriptFetcherWin::GetTaskRunner; + + // 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_query_->mock_adapter_names_.push_back(adapter_name); + adapter_fetchers_.push_back(fetcher); + } + + void ConfigureAndPushBackAdapter(const std::string& adapter_name, + bool did_finish, + int result, + base::string16 pac_script, + base::TimeDelta fetch_delay) { + std::unique_ptr<DummyDhcpProxyScriptAdapterFetcher> adapter_fetcher( + new DummyDhcpProxyScriptAdapterFetcher(url_request_context(), + GetTaskRunner())); + adapter_fetcher->Configure( + did_finish, result, pac_script, fetch_delay.InMilliseconds()); + PushBackAdapter(adapter_name, adapter_fetcher.release()); + } + + DhcpProxyScriptAdapterFetcher* ImplCreateAdapterFetcher() override { + ++num_fetchers_created_; + return adapter_fetchers_[next_adapter_fetcher_index_++]; + } + + AdapterQuery* ImplCreateAdapterQuery() override { + DCHECK(adapter_query_.get()); + return adapter_query_.get(); + } + + base::TimeDelta ImplGetMaxWait() override { + return max_wait_; + } + + void ImplOnGetCandidateAdapterNamesDone() override { + worker_finished_event_.Signal(); + } + + void ResetTestState() { + // Delete any adapter fetcher objects we didn't hand out. + std::vector<DhcpProxyScriptAdapterFetcher*>::const_iterator it + = adapter_fetchers_.begin(); + for (; it != adapter_fetchers_.end(); ++it) { + if (num_fetchers_created_-- <= 0) { + delete (*it); + } + } + + next_adapter_fetcher_index_ = 0; + num_fetchers_created_ = 0; + adapter_fetchers_.clear(); + adapter_query_ = new MockAdapterQuery(); + max_wait_ = TestTimeouts::tiny_timeout(); + } + + bool HasPendingFetchers() { + return num_pending_fetchers() > 0; + } + + int next_adapter_fetcher_index_; + + // Ownership gets transferred to the implementation class via + // ImplCreateAdapterFetcher, but any objects not handed out are + // deleted on destruction. + std::vector<DhcpProxyScriptAdapterFetcher*> adapter_fetchers_; + + scoped_refptr<MockAdapterQuery> adapter_query_; + + base::TimeDelta max_wait_; + int num_fetchers_created_; + base::WaitableEvent worker_finished_event_; +}; + +class FetcherClient { + public: + FetcherClient() + : context_(new TestURLRequestContext), + fetcher_(context_.get()), + finished_(false), + result_(ERR_UNEXPECTED) { + } + + void RunTest() { + int result = fetcher_.Fetch( + &pac_text_, + base::Bind(&FetcherClient::OnCompletion, base::Unretained(this))); + ASSERT_THAT(result, IsError(ERR_IO_PENDING)); + } + + int RunTestThatMayFailSync() { + int result = fetcher_.Fetch( + &pac_text_, + base::Bind(&FetcherClient::OnCompletion, base::Unretained(this))); + if (result != ERR_IO_PENDING) + result_ = result; + return result; + } + + void RunMessageLoopUntilComplete() { + while (!finished_) { + base::RunLoop().RunUntilIdle(); + } + base::RunLoop().RunUntilIdle(); + } + + void RunMessageLoopUntilWorkerDone() { + DCHECK(fetcher_.adapter_query_.get()); + while (!fetcher_.worker_finished_event_.TimedWait( + base::TimeDelta::FromMilliseconds(10))) { + base::RunLoop().RunUntilIdle(); + } + } + + void OnCompletion(int result) { + finished_ = true; + result_ = result; + } + + void ResetTestState() { + finished_ = false; + result_ = ERR_UNEXPECTED; + pac_text_ = L""; + fetcher_.ResetTestState(); + } + + scoped_refptr<base::TaskRunner> GetTaskRunner() { + return fetcher_.GetTaskRunner(); + } + + std::unique_ptr<URLRequestContext> context_; + MockDhcpProxyScriptFetcherWin fetcher_; + bool finished_; + int result_; + base::string16 pac_text_; +}; + +// We separate out each test's logic so that we can easily implement +// the ReuseFetcher test at the bottom. +void TestNormalCaseURLConfiguredOneAdapter(FetcherClient* client) { + TestURLRequestContext context; + std::unique_ptr<DummyDhcpProxyScriptAdapterFetcher> adapter_fetcher( + new DummyDhcpProxyScriptAdapterFetcher(&context, + client->GetTaskRunner())); + adapter_fetcher->Configure(true, OK, L"bingo", 1); + client->fetcher_.PushBackAdapter("a", adapter_fetcher.release()); + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_THAT(client->result_, IsOk()); + 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"", + base::TimeDelta::FromMilliseconds(1)); + client->fetcher_.ConfigureAndPushBackAdapter( + "second", true, OK, L"bingo", base::TimeDelta::FromMilliseconds(50)); + client->fetcher_.ConfigureAndPushBackAdapter( + "third", true, OK, L"rocko", base::TimeDelta::FromMilliseconds(1)); + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_THAT(client->result_, IsOk()); + 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"", + base::TimeDelta::FromMilliseconds(1)); + // This will time out. + client->fetcher_.ConfigureAndPushBackAdapter( + "second", false, ERR_IO_PENDING, L"bingo", + TestTimeouts::action_timeout()); + client->fetcher_.ConfigureAndPushBackAdapter( + "third", true, OK, L"rocko", base::TimeDelta::FromMilliseconds(1)); + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_THAT(client->result_, IsOk()); + 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"", + base::TimeDelta::FromMilliseconds(1)); + // This will time out. + client->fetcher_.ConfigureAndPushBackAdapter( + "second", false, ERR_IO_PENDING, L"bingo", + TestTimeouts::action_timeout()); + // 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"", + base::TimeDelta::FromMilliseconds(1)); + client->fetcher_.ConfigureAndPushBackAdapter( + "fourth", true, ERR_NOT_IMPLEMENTED, L"", + base::TimeDelta::FromMilliseconds(1)); + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_THAT(client->result_, IsError(ERR_PAC_STATUS_NOT_OK)); + 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"", + base::TimeDelta::FromMilliseconds(1)); + // This will time out. + client->fetcher_.ConfigureAndPushBackAdapter( + "second", false, ERR_IO_PENDING, L"bingo", + TestTimeouts::action_timeout()); + // 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"", + base::TimeDelta::FromMilliseconds(1)); + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_THAT(client->result_, IsError(ERR_PAC_NOT_IN_DHCP)); + ASSERT_EQ(L"", client->pac_text_); +} + +TEST(DhcpProxyScriptFetcherWin, FailureCaseNoURLConfigured) { + FetcherClient client; + TestFailureCaseNoURLConfigured(&client); +} + +void TestFailureCaseNoDhcpAdapters(FetcherClient* client) { + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_THAT(client->result_, IsError(ERR_PAC_NOT_IN_DHCP)); + ASSERT_EQ(L"", client->pac_text_); + ASSERT_EQ(0, client->fetcher_.num_fetchers_created_); +} + +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"", + base::TimeDelta::FromMilliseconds(1)); + client->fetcher_.ConfigureAndPushBackAdapter( + "2", true, OK, L"bingo", + base::TimeDelta::FromMilliseconds(1)); + client->fetcher_.ConfigureAndPushBackAdapter( + "3", true, OK, L"wrongo", TestTimeouts::action_max_timeout()); + + // Increase the timeout to ensure the short circuit mechanism has + // time to kick in before the timeout waiting for more adapters kicks in. + client->fetcher_.max_wait_ = TestTimeouts::action_timeout(); + + base::ElapsedTimer timer; + client->RunTest(); + client->RunMessageLoopUntilComplete(); + ASSERT_TRUE(client->fetcher_.HasPendingFetchers()); + // Assert that the time passed is definitely less than the wait timer + // timeout, to get a second signal that it was the shortcut mechanism + // (in OnFetcherDone) that kicked in, and not the timeout waiting for + // more adapters. + ASSERT_GT(client->fetcher_.max_wait_ - (client->fetcher_.max_wait_ / 10), + timer.Elapsed()); +} + +TEST(DhcpProxyScriptFetcherWin, ShortCircuitLessPreferredAdapters) { + FetcherClient client; + TestShortCircuitLessPreferredAdapters(&client); +} + +void TestImmediateCancel(FetcherClient* client) { + TestURLRequestContext context; + std::unique_ptr<DummyDhcpProxyScriptAdapterFetcher> adapter_fetcher( + new DummyDhcpProxyScriptAdapterFetcher(&context, + client->GetTaskRunner())); + adapter_fetcher->Configure(true, OK, L"bingo", 1); + client->fetcher_.PushBackAdapter("a", adapter_fetcher.release()); + client->RunTest(); + client->fetcher_.Cancel(); + client->RunMessageLoopUntilWorkerDone(); + ASSERT_EQ(0, client->fetcher_.num_fetchers_created_); +} + +// Regression test to check that when we cancel immediately, no +// adapter fetchers get created. +TEST(DhcpProxyScriptFetcherWin, ImmediateCancel) { + FetcherClient client; + TestImmediateCancel(&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); + test_functions.push_back(TestImmediateCancel); + + 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); +} + +TEST(DhcpProxyScriptFetcherWin, OnShutdown) { + FetcherClient client; + TestURLRequestContext context; + std::unique_ptr<DummyDhcpProxyScriptAdapterFetcher> adapter_fetcher( + new DummyDhcpProxyScriptAdapterFetcher(&context, client.GetTaskRunner())); + adapter_fetcher->Configure(true, OK, L"bingo", 1); + client.fetcher_.PushBackAdapter("a", adapter_fetcher.release()); + client.RunTest(); + + client.fetcher_.OnShutdown(); + EXPECT_TRUE(client.finished_); + EXPECT_THAT(client.result_, IsError(ERR_CONTEXT_SHUT_DOWN)); + + client.ResetTestState(); + EXPECT_THAT(client.RunTestThatMayFailSync(), IsError(ERR_CONTEXT_SHUT_DOWN)); + EXPECT_EQ(0u, context.url_requests()->size()); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/proxy_resolution/dhcpcsvc_init_win.cc b/chromium/net/proxy_resolution/dhcpcsvc_init_win.cc new file mode 100644 index 00000000000..4a6040f60be --- /dev/null +++ b/chromium/net/proxy_resolution/dhcpcsvc_init_win.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_resolution/dhcpcsvc_init_win.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" + +#include <windows.h> // Must be in front of other Windows header files. + +#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. + } +}; + +// Worker pool threads that use the DHCP API may still be running at shutdown. +// Leak instance and skip cleanup. +static base::LazyInstance<DhcpcsvcInitSingleton>::Leaky + g_dhcpcsvc_init_singleton = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace net { + +void EnsureDhcpcsvcInit() { + g_dhcpcsvc_init_singleton.Get(); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/dhcpcsvc_init_win.h b/chromium/net/proxy_resolution/dhcpcsvc_init_win.h new file mode 100644 index 00000000000..333cbd21f4e --- /dev/null +++ b/chromium/net/proxy_resolution/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_RESOLUTION_DHCPCSVC_INIT_WIN_H_ +#define NET_PROXY_RESOLUTION_DHCPCSVC_INIT_WIN_H_ + +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_RESOLUTION_DHCPCSVC_INIT_WIN_H_ diff --git a/chromium/net/proxy_resolution/mock_pac_file_fetcher.cc b/chromium/net/proxy_resolution/mock_pac_file_fetcher.cc new file mode 100644 index 00000000000..4c172c865db --- /dev/null +++ b/chromium/net/proxy_resolution/mock_pac_file_fetcher.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2012 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_resolution/mock_pac_file_fetcher.h" + +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/run_loop.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/net_errors.h" + +namespace net { + +MockProxyScriptFetcher::MockProxyScriptFetcher() + : pending_request_text_(NULL), + waiting_for_fetch_(false), + is_shutdown_(false) {} + +MockProxyScriptFetcher::~MockProxyScriptFetcher() = default; + +// ProxyScriptFetcher implementation. +int MockProxyScriptFetcher::Fetch(const GURL& url, base::string16* text, + const CompletionCallback& callback) { + DCHECK(!has_pending_request()); + + if (waiting_for_fetch_) + base::RunLoop::QuitCurrentWhenIdleDeprecated(); + + if (is_shutdown_) + return ERR_CONTEXT_SHUT_DOWN; + + // 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_ = base::ASCIIToUTF16(ascii_text); + base::ResetAndReturn(&pending_request_callback_).Run(result); +} + +void MockProxyScriptFetcher::Cancel() { + pending_request_callback_.Reset(); +} + +void MockProxyScriptFetcher::OnShutdown() { + is_shutdown_ = true; + if (pending_request_callback_) { + base::ResetAndReturn(&pending_request_callback_).Run(ERR_CONTEXT_SHUT_DOWN); + } +} + +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_.is_null(); +} + +void MockProxyScriptFetcher::WaitUntilFetch() { + DCHECK(!has_pending_request()); + waiting_for_fetch_ = true; + base::RunLoop().Run(); + waiting_for_fetch_ = false; +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/mock_pac_file_fetcher.h b/chromium/net/proxy_resolution/mock_pac_file_fetcher.h new file mode 100644 index 00000000000..218dc77ee42 --- /dev/null +++ b/chromium/net/proxy_resolution/mock_pac_file_fetcher.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 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_PAC_FILE_FETCHER_H_ +#define NET_PROXY_MOCK_PAC_FILE_FETCHER_H_ + +#include "base/compiler_specific.h" +#include "net/proxy_resolution/pac_file_fetcher.h" +#include "url/gurl.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(); + ~MockProxyScriptFetcher() override; + + // ProxyScriptFetcher implementation. + int Fetch(const GURL& url, + base::string16* text, + const CompletionCallback& callback) override; + void Cancel() override; + void OnShutdown() override; + URLRequestContext* GetRequestContext() const override; + + void NotifyFetchCompletion(int result, const std::string& ascii_text); + const GURL& pending_request_url() const; + bool has_pending_request() const; + + // Spins the message loop until this->Fetch() is invoked. + void WaitUntilFetch(); + + private: + GURL pending_request_url_; + CompletionCallback pending_request_callback_; + base::string16* pending_request_text_; + bool waiting_for_fetch_; + bool is_shutdown_; +}; + +} // namespace net + +#endif // NET_PROXY_MOCK_PAC_FILE_FETCHER_H_ diff --git a/chromium/net/proxy_resolution/mock_proxy_resolver.cc b/chromium/net/proxy_resolution/mock_proxy_resolver.cc new file mode 100644 index 00000000000..56ac53a29cf --- /dev/null +++ b/chromium/net/proxy_resolution/mock_proxy_resolver.cc @@ -0,0 +1,179 @@ +// 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_resolution/mock_proxy_resolver.h" + +#include <utility> + +#include "base/logging.h" + +namespace net { + +MockAsyncProxyResolver::RequestImpl::RequestImpl(std::unique_ptr<Job> job) + : job_(std::move(job)) { + DCHECK(job_); +} + +MockAsyncProxyResolver::RequestImpl::~RequestImpl() { + MockAsyncProxyResolver* resolver = job_->Resolver(); + // AddCancelledJob will check if request is already cancelled + resolver->AddCancelledJob(std::move(job_)); +} + +LoadState MockAsyncProxyResolver::RequestImpl::GetLoadState() { + return LOAD_STATE_RESOLVING_PROXY_FOR_URL; +} + +MockAsyncProxyResolver::Job::Job(MockAsyncProxyResolver* resolver, + const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback) + : resolver_(resolver), url_(url), results_(results), callback_(callback) {} + +MockAsyncProxyResolver::Job::~Job() = default; + +void MockAsyncProxyResolver::Job::CompleteNow(int rv) { + CompletionCallback callback = callback_; + + resolver_->RemovePendingJob(this); + + callback.Run(rv); +} + +MockAsyncProxyResolver::~MockAsyncProxyResolver() = default; + +int MockAsyncProxyResolver::GetProxyForURL( + const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& /*net_log*/) { + std::unique_ptr<Job> job(new Job(this, url, results, callback)); + + pending_jobs_.push_back(job.get()); + request->reset(new RequestImpl(std::move(job))); + + // Test code completes the request by calling job->CompleteNow(). + return ERR_IO_PENDING; +} + +void MockAsyncProxyResolver::AddCancelledJob(std::unique_ptr<Job> job) { + std::vector<Job*>::iterator it = + std::find(pending_jobs_.begin(), pending_jobs_.end(), job.get()); + // Because this is called always when RequestImpl is destructed, + // we need to check if it is still in pending jobs. + if (it != pending_jobs_.end()) { + cancelled_jobs_.push_back(std::move(job)); + pending_jobs_.erase(it); + } +} + +void MockAsyncProxyResolver::RemovePendingJob(Job* job) { + DCHECK(job); + std::vector<Job*>::iterator it = + std::find(pending_jobs_.begin(), pending_jobs_.end(), job); + DCHECK(it != pending_jobs_.end()); + pending_jobs_.erase(it); +} + +MockAsyncProxyResolver::MockAsyncProxyResolver() = default; + +MockAsyncProxyResolverFactory::Request::Request( + MockAsyncProxyResolverFactory* factory, + const scoped_refptr<ProxyResolverScriptData>& script_data, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback) + : factory_(factory), + script_data_(script_data), + resolver_(resolver), + callback_(callback) {} + +MockAsyncProxyResolverFactory::Request::~Request() = default; + +void MockAsyncProxyResolverFactory::Request::CompleteNow( + int rv, + std::unique_ptr<ProxyResolver> resolver) { + *resolver_ = std::move(resolver); + + // RemovePendingRequest may remove the last external reference to |this|. + scoped_refptr<MockAsyncProxyResolverFactory::Request> keep_alive(this); + factory_->RemovePendingRequest(this); + factory_ = nullptr; + callback_.Run(rv); +} + +void MockAsyncProxyResolverFactory::Request::CompleteNowWithForwarder( + int rv, + ProxyResolver* resolver) { + DCHECK(resolver); + CompleteNow(rv, std::make_unique<ForwardingProxyResolver>(resolver)); +} + +void MockAsyncProxyResolverFactory::Request::FactoryDestroyed() { + factory_ = nullptr; +} + +class MockAsyncProxyResolverFactory::Job + : public ProxyResolverFactory::Request { + public: + explicit Job( + const scoped_refptr<MockAsyncProxyResolverFactory::Request>& request) + : request_(request) {} + ~Job() override { + if (request_->factory_) { + request_->factory_->cancelled_requests_.push_back(request_); + request_->factory_->RemovePendingRequest(request_.get()); + } + } + + private: + scoped_refptr<MockAsyncProxyResolverFactory::Request> request_; +}; + +MockAsyncProxyResolverFactory::MockAsyncProxyResolverFactory( + bool resolvers_expect_pac_bytes) + : ProxyResolverFactory(resolvers_expect_pac_bytes) { +} + +int MockAsyncProxyResolverFactory::CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const net::CompletionCallback& callback, + std::unique_ptr<ProxyResolverFactory::Request>* request_handle) { + scoped_refptr<Request> request = + new Request(this, pac_script, resolver, callback); + pending_requests_.push_back(request); + + request_handle->reset(new Job(request)); + + // Test code completes the request by calling request->CompleteNow(). + return ERR_IO_PENDING; +} + +void MockAsyncProxyResolverFactory::RemovePendingRequest(Request* request) { + RequestsList::iterator it = + std::find(pending_requests_.begin(), pending_requests_.end(), request); + DCHECK(it != pending_requests_.end()); + pending_requests_.erase(it); +} + +MockAsyncProxyResolverFactory::~MockAsyncProxyResolverFactory() { + for (auto& request : pending_requests_) { + request->FactoryDestroyed(); + } +} + +ForwardingProxyResolver::ForwardingProxyResolver(ProxyResolver* impl) + : impl_(impl) { +} + +int ForwardingProxyResolver::GetProxyForURL(const GURL& query_url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) { + return impl_->GetProxyForURL(query_url, results, callback, request, net_log); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/mock_proxy_resolver.h b/chromium/net/proxy_resolution/mock_proxy_resolver.h new file mode 100644 index 00000000000..df8c6c1ff8c --- /dev/null +++ b/chromium/net/proxy_resolution/mock_proxy_resolver.h @@ -0,0 +1,166 @@ +// Copyright (c) 2012 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_RESOLUTION_MOCK_PROXY_RESOLVER_H_ +#define NET_PROXY_RESOLUTION_MOCK_PROXY_RESOLVER_H_ + +#include <memory> +#include <vector> + +#include "base/macros.h" +#include "net/base/net_errors.h" +#include "net/proxy_resolution/proxy_resolver.h" +#include "net/proxy_resolution/proxy_resolver_factory.h" +#include "url/gurl.h" + +namespace net { + +// Asynchronous mock proxy resolver. All requests complete asynchronously, +// user must call Job::CompleteNow() on a pending request to signal it. +class MockAsyncProxyResolver : public ProxyResolver { + public: + class Job { + public: + Job(MockAsyncProxyResolver* resolver, + const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback); + + const GURL& url() const { return url_; } + ProxyInfo* results() const { return results_; } + const CompletionCallback& callback() const { return callback_; } + MockAsyncProxyResolver* Resolver() const { return resolver_; }; + + void CompleteNow(int rv); + + ~Job(); + + private: + MockAsyncProxyResolver* resolver_; + const GURL url_; + ProxyInfo* results_; + CompletionCallback callback_; + }; + + class RequestImpl : public ProxyResolver::Request { + public: + explicit RequestImpl(std::unique_ptr<Job> job); + + ~RequestImpl() override; + + LoadState GetLoadState() override; + + private: + std::unique_ptr<Job> job_; + }; + + MockAsyncProxyResolver(); + ~MockAsyncProxyResolver() override; + + // ProxyResolver implementation. + int GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& /*net_log*/) override; + const std::vector<Job*>& pending_jobs() const { return pending_jobs_; } + + const std::vector<std::unique_ptr<Job>>& cancelled_jobs() const { + return cancelled_jobs_; + } + + void AddCancelledJob(std::unique_ptr<Job> job); + void RemovePendingJob(Job* job); + + private: + std::vector<Job*> pending_jobs_; + std::vector<std::unique_ptr<Job>> cancelled_jobs_; +}; + +// Asynchronous mock proxy resolver factory . All requests complete +// asynchronously; the user must call Request::CompleteNow() on a pending +// request to signal it. +class MockAsyncProxyResolverFactory : public ProxyResolverFactory { + public: + class Request; + using RequestsList = std::vector<scoped_refptr<Request>>; + + explicit MockAsyncProxyResolverFactory(bool resolvers_expect_pac_bytes); + ~MockAsyncProxyResolverFactory() override; + + int CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback, + std::unique_ptr<ProxyResolverFactory::Request>* request) override; + + const RequestsList& pending_requests() const { return pending_requests_; } + + const RequestsList& cancelled_requests() const { return cancelled_requests_; } + + void RemovePendingRequest(Request* request); + + private: + class Job; + RequestsList pending_requests_; + RequestsList cancelled_requests_; +}; + +class MockAsyncProxyResolverFactory::Request + : public base::RefCounted<Request> { + public: + Request(MockAsyncProxyResolverFactory* factory, + const scoped_refptr<ProxyResolverScriptData>& script_data, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback); + + const scoped_refptr<ProxyResolverScriptData>& script_data() const { + return script_data_; + } + + // Completes this request. A ForwardingProxyResolver that forwards to + // |resolver| will be returned to the requester. |resolver| must not be + // null and must remain as long as the resolver returned by this request + // remains in use. + void CompleteNowWithForwarder(int rv, ProxyResolver* resolver); + + void CompleteNow(int rv, std::unique_ptr<ProxyResolver> resolver); + + private: + friend class base::RefCounted<Request>; + friend class MockAsyncProxyResolverFactory; + friend class MockAsyncProxyResolverFactory::Job; + + ~Request(); + + void FactoryDestroyed(); + + MockAsyncProxyResolverFactory* factory_; + const scoped_refptr<ProxyResolverScriptData> script_data_; + std::unique_ptr<ProxyResolver>* resolver_; + CompletionCallback callback_; +}; + +// ForwardingProxyResolver forwards all requests to |impl|. |impl| must remain +// so long as this remains in use. +class ForwardingProxyResolver : public ProxyResolver { + public: + explicit ForwardingProxyResolver(ProxyResolver* impl); + + // ProxyResolver overrides. + int GetProxyForURL(const GURL& query_url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) override; + + private: + ProxyResolver* impl_; + + DISALLOW_COPY_AND_ASSIGN(ForwardingProxyResolver); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_MOCK_PROXY_RESOLVER_H_ diff --git a/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings.h b/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings.h new file mode 100644 index 00000000000..e64c422da35 --- /dev/null +++ b/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings.h @@ -0,0 +1,70 @@ +// Copyright 2015 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_RESOLUTION_MOJO_PROXY_RESOLVER_V8_TRACING_BINDINGS_H_ +#define NET_PROXY_RESOLUTION_MOJO_PROXY_RESOLVER_V8_TRACING_BINDINGS_H_ + +#include <utility> + +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_checker.h" +#include "net/dns/host_resolver_mojo.h" +#include "net/log/net_log_with_source.h" +#include "net/proxy_resolution/proxy_resolver_v8_tracing.h" + +namespace net { + +// An implementation of ProxyResolverV8Tracing::Bindings that forwards requests +// onto a Client mojo interface. Alert() and OnError() may be called from any +// thread; when they are called from another thread, the calls are proxied to +// the origin task runner. GetHostResolver() and GetNetLogWithSource() may only +// be +// called from the origin task runner. +template <typename Client> +class MojoProxyResolverV8TracingBindings + : public ProxyResolverV8Tracing::Bindings, + public HostResolverMojo::Impl { + public: + explicit MojoProxyResolverV8TracingBindings(Client* client) + : client_(client), host_resolver_(this) { + DCHECK(client_); + } + + // ProxyResolverV8Tracing::Bindings overrides. + void Alert(const base::string16& message) override { + DCHECK(thread_checker_.CalledOnValidThread()); + client_->Alert(base::UTF16ToUTF8(message)); + } + + void OnError(int line_number, const base::string16& message) override { + DCHECK(thread_checker_.CalledOnValidThread()); + client_->OnError(line_number, base::UTF16ToUTF8(message)); + } + + HostResolver* GetHostResolver() override { + DCHECK(thread_checker_.CalledOnValidThread()); + return &host_resolver_; + } + + NetLogWithSource GetNetLogWithSource() override { + DCHECK(thread_checker_.CalledOnValidThread()); + return NetLogWithSource(); + } + + private: + // HostResolverMojo::Impl override. + void ResolveDns(std::unique_ptr<HostResolver::RequestInfo> request_info, + interfaces::HostResolverRequestClientPtr client) { + DCHECK(thread_checker_.CalledOnValidThread()); + client_->ResolveDns(std::move(request_info), std::move(client)); + } + + base::ThreadChecker thread_checker_; + Client* client_; + HostResolverMojo host_resolver_; +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_MOJO_PROXY_RESOLVER_V8_TRACING_BINDINGS_H_ diff --git a/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings_unittest.cc b/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings_unittest.cc new file mode 100644 index 00000000000..ffd37b145c4 --- /dev/null +++ b/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings_unittest.cc @@ -0,0 +1,61 @@ +// Copyright 2015 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_resolution/mojo_proxy_resolver_v8_tracing_bindings.h" + +#include <string> +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +class MojoProxyResolverV8TracingBindingsTest : public testing::Test { + public: + MojoProxyResolverV8TracingBindingsTest() = default; + + void SetUp() override { + bindings_.reset(new MojoProxyResolverV8TracingBindings< + MojoProxyResolverV8TracingBindingsTest>(this)); + } + + void Alert(const std::string& message) { alerts_.push_back(message); } + + void OnError(int32_t line_number, const std::string& message) { + errors_.push_back(std::make_pair(line_number, message)); + } + + void ResolveDns(std::unique_ptr<HostResolver::RequestInfo> request_info, + interfaces::HostResolverRequestClientPtr client) {} + + protected: + std::unique_ptr<MojoProxyResolverV8TracingBindings< + MojoProxyResolverV8TracingBindingsTest>> + bindings_; + + std::vector<std::string> alerts_; + std::vector<std::pair<int, std::string>> errors_; + + private: + DISALLOW_COPY_AND_ASSIGN(MojoProxyResolverV8TracingBindingsTest); +}; + +TEST_F(MojoProxyResolverV8TracingBindingsTest, Basic) { + bindings_->Alert(base::ASCIIToUTF16("alert")); + bindings_->OnError(-1, base::ASCIIToUTF16("error")); + + EXPECT_TRUE(bindings_->GetHostResolver()); + EXPECT_FALSE(bindings_->GetNetLogWithSource().net_log()); + + ASSERT_EQ(1u, alerts_.size()); + EXPECT_EQ("alert", alerts_[0]); + ASSERT_EQ(1u, errors_.size()); + EXPECT_EQ(-1, errors_[0].first); + EXPECT_EQ("error", errors_[0].second); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/multi_threaded_proxy_resolver.cc b/chromium/net/proxy_resolution/multi_threaded_proxy_resolver.cc new file mode 100644 index 00000000000..01fbf393f7b --- /dev/null +++ b/chromium/net/proxy_resolution/multi_threaded_proxy_resolver.cc @@ -0,0 +1,625 @@ +// Copyright (c) 2012 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_resolution/multi_threaded_proxy_resolver.h" + +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback_helpers.h" +#include "base/containers/circular_deque.h" +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread.h" +#include "base/threading/thread_checker.h" +#include "base/threading/thread_restrictions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "net/base/net_errors.h" +#include "net/log/net_log.h" +#include "net/log/net_log_event_type.h" +#include "net/log/net_log_with_source.h" +#include "net/proxy_resolution/proxy_info.h" +#include "net/proxy_resolution/proxy_resolver.h" + +namespace net { +namespace { +class Job; + +// An "executor" is a job-runner for PAC requests. It encapsulates a worker +// thread and a synchronous ProxyResolver (which will be operated on said +// thread.) +class Executor : public base::RefCountedThreadSafe<Executor> { + public: + class Coordinator { + public: + virtual void OnExecutorReady(Executor* executor) = 0; + + protected: + virtual ~Coordinator() = default; + }; + + // |coordinator| must remain valid throughout our lifetime. It is used to + // signal when the executor is ready to receive work by calling + // |coordinator->OnExecutorReady()|. + // |thread_number| is an identifier used when naming the worker thread. + Executor(Coordinator* coordinator, int thread_number); + + // Submit a job to this executor. + void StartJob(Job* job); + + // Callback for when a job has completed running on the executor's thread. + void OnJobCompleted(Job* job); + + // Cleanup the executor. Cancels all outstanding work, and frees the thread + // and resolver. + void Destroy(); + + // Returns the outstanding job, or NULL. + Job* outstanding_job() const { return outstanding_job_.get(); } + + ProxyResolver* resolver() { return resolver_.get(); } + + int thread_number() const { return thread_number_; } + + void set_resolver(std::unique_ptr<ProxyResolver> resolver) { + resolver_ = std::move(resolver); + } + + void set_coordinator(Coordinator* coordinator) { + DCHECK(coordinator); + DCHECK(coordinator_); + coordinator_ = coordinator; + } + + private: + friend class base::RefCountedThreadSafe<Executor>; + ~Executor(); + + Coordinator* coordinator_; + const int thread_number_; + + // The currently active job for this executor (either a CreateProxyResolver or + // GetProxyForURL task). + scoped_refptr<Job> outstanding_job_; + + // The synchronous resolver implementation. + std::unique_ptr<ProxyResolver> resolver_; + + // The thread where |resolver_| is run on. + // Note that declaration ordering is important here. |thread_| needs to be + // destroyed *before* |resolver_|, in case |resolver_| is currently + // executing on |thread_|. + std::unique_ptr<base::Thread> thread_; +}; + +class MultiThreadedProxyResolver : public ProxyResolver, + public Executor::Coordinator { + public: + // Creates an asynchronous ProxyResolver that runs requests on up to + // |max_num_threads|. + // + // For each thread that is created, an accompanying synchronous ProxyResolver + // will be provisioned using |resolver_factory|. All methods on these + // ProxyResolvers will be called on the one thread. + MultiThreadedProxyResolver( + std::unique_ptr<ProxyResolverFactory> resolver_factory, + size_t max_num_threads, + const scoped_refptr<ProxyResolverScriptData>& script_data, + scoped_refptr<Executor> executor); + + ~MultiThreadedProxyResolver() override; + + // ProxyResolver implementation: + int GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) override; + + private: + class GetProxyForURLJob; + class RequestImpl; + // FIFO queue of pending jobs waiting to be started. + // TODO(eroman): Make this priority queue. + using PendingJobsQueue = base::circular_deque<scoped_refptr<Job>>; + using ExecutorList = std::vector<scoped_refptr<Executor>>; + + // Returns an idle worker thread which is ready to receive GetProxyForURL() + // requests. If all threads are occupied, returns NULL. + Executor* FindIdleExecutor(); + + // Creates a new worker thread, and appends it to |executors_|. + void AddNewExecutor(); + + // Starts the next job from |pending_jobs_| if possible. + void OnExecutorReady(Executor* executor) override; + + const std::unique_ptr<ProxyResolverFactory> resolver_factory_; + const size_t max_num_threads_; + PendingJobsQueue pending_jobs_; + ExecutorList executors_; + scoped_refptr<ProxyResolverScriptData> script_data_; + + THREAD_CHECKER(thread_checker_); +}; + +// Job --------------------------------------------- + +class Job : public base::RefCountedThreadSafe<Job> { + public: + // Identifies the subclass of Job (only being used for debugging purposes). + enum Type { + TYPE_GET_PROXY_FOR_URL, + TYPE_CREATE_RESOLVER, + }; + + Job(Type type, const CompletionCallback& callback) + : type_(type), + callback_(callback), + executor_(NULL), + was_cancelled_(false) { + } + + void set_executor(Executor* executor) { + executor_ = executor; + } + + // The "executor" is the job runner that is scheduling this job. If + // this job has not been submitted to an executor yet, this will be + // NULL (and we know it hasn't started yet). + Executor* executor() { + return executor_; + } + + // Mark the job as having been cancelled. + void Cancel() { + was_cancelled_ = true; + } + + // Returns true if Cancel() has been called. + bool was_cancelled() const { return was_cancelled_; } + + Type type() const { return type_; } + + // Returns true if this job still has a user callback. Some jobs + // do not have a user callback, because they were helper jobs + // scheduled internally (for example TYPE_CREATE_RESOLVER). + // + // Otherwise jobs that correspond with user-initiated work will + // have a non-null callback up until the callback is run. + bool has_user_callback() const { return !callback_.is_null(); } + + // This method is called when the job is inserted into a wait queue + // because no executors were ready to accept it. + virtual void WaitingForThread() {} + + // This method is called just before the job is posted to the work thread. + virtual void FinishedWaitingForThread() {} + + // This method is called on the worker thread to do the job's work. On + // completion, implementors are expected to call OnJobCompleted() on + // |origin_runner|. + virtual void Run( + scoped_refptr<base::SingleThreadTaskRunner> origin_runner) = 0; + + protected: + void OnJobCompleted() { + // |executor_| will be NULL if the executor has already been deleted. + if (executor_) + executor_->OnJobCompleted(this); + } + + void RunUserCallback(int result) { + DCHECK(has_user_callback()); + // Reset the callback so has_user_callback() will now return false. + base::ResetAndReturn(&callback_).Run(result); + } + + friend class base::RefCountedThreadSafe<Job>; + + virtual ~Job() = default; + + private: + const Type type_; + CompletionCallback callback_; + Executor* executor_; + bool was_cancelled_; +}; + +class MultiThreadedProxyResolver::RequestImpl : public ProxyResolver::Request { + public: + explicit RequestImpl(scoped_refptr<Job> job) : job_(std::move(job)) {} + + ~RequestImpl() override { job_->Cancel(); } + + LoadState GetLoadState() override { + return LOAD_STATE_RESOLVING_PROXY_FOR_URL; + } + + private: + scoped_refptr<Job> job_; +}; + +// CreateResolverJob ----------------------------------------------------------- + +// Runs on the worker thread to call ProxyResolverFactory::CreateProxyResolver. +class CreateResolverJob : public Job { + public: + CreateResolverJob(const scoped_refptr<ProxyResolverScriptData>& script_data, + ProxyResolverFactory* factory) + : Job(TYPE_CREATE_RESOLVER, CompletionCallback()), + script_data_(script_data), + factory_(factory) {} + + // Runs on the worker thread. + void Run(scoped_refptr<base::SingleThreadTaskRunner> origin_runner) override { + std::unique_ptr<ProxyResolverFactory::Request> request; + int rv = factory_->CreateProxyResolver(script_data_, &resolver_, + CompletionCallback(), &request); + + DCHECK_NE(rv, ERR_IO_PENDING); + origin_runner->PostTask( + FROM_HERE, base::Bind(&CreateResolverJob::RequestComplete, this, rv)); + } + + protected: + ~CreateResolverJob() override = default; + + private: + // Runs the completion callback on the origin thread. + void RequestComplete(int result_code) { + // The task may have been cancelled after it was started. + if (!was_cancelled()) { + DCHECK(executor()); + executor()->set_resolver(std::move(resolver_)); + } + OnJobCompleted(); + } + + const scoped_refptr<ProxyResolverScriptData> script_data_; + ProxyResolverFactory* factory_; + std::unique_ptr<ProxyResolver> resolver_; +}; + +// MultiThreadedProxyResolver::GetProxyForURLJob ------------------------------ + +class MultiThreadedProxyResolver::GetProxyForURLJob : public Job { + public: + // |url| -- the URL of the query. + // |results| -- the structure to fill with proxy resolve results. + GetProxyForURLJob(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + const NetLogWithSource& net_log) + : Job(TYPE_GET_PROXY_FOR_URL, callback), + results_(results), + net_log_(net_log), + url_(url), + was_waiting_for_thread_(false) { + DCHECK(!callback.is_null()); + } + + NetLogWithSource* net_log() { return &net_log_; } + + void WaitingForThread() override { + was_waiting_for_thread_ = true; + net_log_.BeginEvent(NetLogEventType::WAITING_FOR_PROXY_RESOLVER_THREAD); + } + + void FinishedWaitingForThread() override { + DCHECK(executor()); + + if (was_waiting_for_thread_) { + net_log_.EndEvent(NetLogEventType::WAITING_FOR_PROXY_RESOLVER_THREAD); + } + + net_log_.AddEvent( + NetLogEventType::SUBMITTED_TO_RESOLVER_THREAD, + NetLog::IntCallback("thread_number", executor()->thread_number())); + } + + // Runs on the worker thread. + void Run(scoped_refptr<base::SingleThreadTaskRunner> origin_runner) override { + ProxyResolver* resolver = executor()->resolver(); + DCHECK(resolver); + int rv = resolver->GetProxyForURL( + url_, &results_buf_, CompletionCallback(), NULL, net_log_); + DCHECK_NE(rv, ERR_IO_PENDING); + + origin_runner->PostTask( + FROM_HERE, base::Bind(&GetProxyForURLJob::QueryComplete, this, rv)); + } + + protected: + ~GetProxyForURLJob() override = default; + + private: + // Runs the completion callback on the origin thread. + void QueryComplete(int result_code) { + // The Job may have been cancelled after it was started. + if (!was_cancelled()) { + if (result_code >= OK) { // Note: unit-tests use values > 0. + results_->Use(results_buf_); + } + RunUserCallback(result_code); + } + OnJobCompleted(); + } + + // Must only be used on the "origin" thread. + ProxyInfo* results_; + + // Can be used on either "origin" or worker thread. + NetLogWithSource net_log_; + const GURL url_; + + // Usable from within DoQuery on the worker thread. + ProxyInfo results_buf_; + + bool was_waiting_for_thread_; +}; + +// Executor ---------------------------------------- + +Executor::Executor(Executor::Coordinator* coordinator, int thread_number) + : coordinator_(coordinator), thread_number_(thread_number) { + DCHECK(coordinator); + // Start up the thread. + thread_.reset(new base::Thread(base::StringPrintf("PAC thread #%d", + thread_number))); + CHECK(thread_->Start()); +} + +void Executor::StartJob(Job* job) { + DCHECK(!outstanding_job_.get()); + outstanding_job_ = job; + + // Run the job. Once it has completed (regardless of whether it was + // cancelled), it will invoke OnJobCompleted() on this thread. + job->set_executor(this); + job->FinishedWaitingForThread(); + thread_->task_runner()->PostTask( + FROM_HERE, + base::Bind(&Job::Run, job, base::ThreadTaskRunnerHandle::Get())); +} + +void Executor::OnJobCompleted(Job* job) { + DCHECK_EQ(job, outstanding_job_.get()); + outstanding_job_ = NULL; + coordinator_->OnExecutorReady(this); +} + +void Executor::Destroy() { + DCHECK(coordinator_); + + { + // See http://crbug.com/69710. + base::ThreadRestrictions::ScopedAllowIO allow_io; + + // Join the worker thread. + thread_.reset(); + } + + // Cancel any outstanding job. + if (outstanding_job_.get()) { + outstanding_job_->Cancel(); + // Orphan the job (since this executor may be deleted soon). + outstanding_job_->set_executor(NULL); + } + + // It is now safe to free the ProxyResolver, since all the tasks that + // were using it on the resolver thread have completed. + resolver_.reset(); + + // Null some stuff as a precaution. + coordinator_ = NULL; + outstanding_job_ = NULL; +} + +Executor::~Executor() { + // The important cleanup happens as part of Destroy(), which should always be + // called first. + DCHECK(!coordinator_) << "Destroy() was not called"; + DCHECK(!thread_.get()); + DCHECK(!resolver_.get()); + DCHECK(!outstanding_job_.get()); +} + +// MultiThreadedProxyResolver -------------------------------------------------- + +MultiThreadedProxyResolver::MultiThreadedProxyResolver( + std::unique_ptr<ProxyResolverFactory> resolver_factory, + size_t max_num_threads, + const scoped_refptr<ProxyResolverScriptData>& script_data, + scoped_refptr<Executor> executor) + : resolver_factory_(std::move(resolver_factory)), + max_num_threads_(max_num_threads), + script_data_(script_data) { + DCHECK(script_data_); + executor->set_coordinator(this); + executors_.push_back(executor); +} + +MultiThreadedProxyResolver::~MultiThreadedProxyResolver() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + // We will cancel all outstanding requests. + pending_jobs_.clear(); + + for (auto& executor : executors_) { + executor->Destroy(); + } +} + +int MultiThreadedProxyResolver::GetProxyForURL( + const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!callback.is_null()); + + scoped_refptr<GetProxyForURLJob> job( + new GetProxyForURLJob(url, results, callback, net_log)); + + // Completion will be notified through |callback|, unless the caller cancels + // the request using |request|. + if (request) + request->reset(new RequestImpl(job)); + + // If there is an executor that is ready to run this request, submit it! + Executor* executor = FindIdleExecutor(); + if (executor) { + DCHECK_EQ(0u, pending_jobs_.size()); + executor->StartJob(job.get()); + return ERR_IO_PENDING; + } + + // Otherwise queue this request. (We will schedule it to a thread once one + // becomes available). + job->WaitingForThread(); + pending_jobs_.push_back(job); + + // If we haven't already reached the thread limit, provision a new thread to + // drain the requests more quickly. + if (executors_.size() < max_num_threads_) + AddNewExecutor(); + + return ERR_IO_PENDING; +} + +Executor* MultiThreadedProxyResolver::FindIdleExecutor() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + for (ExecutorList::iterator it = executors_.begin(); + it != executors_.end(); ++it) { + Executor* executor = it->get(); + if (!executor->outstanding_job()) + return executor; + } + return NULL; +} + +void MultiThreadedProxyResolver::AddNewExecutor() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK_LT(executors_.size(), max_num_threads_); + // The "thread number" is used to give the thread a unique name. + int thread_number = executors_.size(); + Executor* executor = new Executor(this, thread_number); + executor->StartJob( + new CreateResolverJob(script_data_, resolver_factory_.get())); + executors_.push_back(base::WrapRefCounted(executor)); +} + +void MultiThreadedProxyResolver::OnExecutorReady(Executor* executor) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + while (!pending_jobs_.empty()) { + scoped_refptr<Job> job = pending_jobs_.front(); + pending_jobs_.pop_front(); + if (!job->was_cancelled()) { + executor->StartJob(job.get()); + return; + } + } +} + +} // namespace + +class MultiThreadedProxyResolverFactory::Job + : public ProxyResolverFactory::Request, + public Executor::Coordinator { + public: + Job(MultiThreadedProxyResolverFactory* factory, + const scoped_refptr<ProxyResolverScriptData>& script_data, + std::unique_ptr<ProxyResolver>* resolver, + std::unique_ptr<ProxyResolverFactory> resolver_factory, + size_t max_num_threads, + const CompletionCallback& callback) + : factory_(factory), + resolver_out_(resolver), + resolver_factory_(std::move(resolver_factory)), + max_num_threads_(max_num_threads), + script_data_(script_data), + executor_(new Executor(this, 0)), + callback_(callback) { + executor_->StartJob( + new CreateResolverJob(script_data_, resolver_factory_.get())); + } + + ~Job() override { + if (factory_) { + executor_->Destroy(); + factory_->RemoveJob(this); + } + } + + void FactoryDestroyed() { + executor_->Destroy(); + executor_ = nullptr; + factory_ = nullptr; + } + + private: + void OnExecutorReady(Executor* executor) override { + int error = OK; + if (executor->resolver()) { + resolver_out_->reset(new MultiThreadedProxyResolver( + std::move(resolver_factory_), max_num_threads_, + std::move(script_data_), executor_)); + } else { + error = ERR_PAC_SCRIPT_FAILED; + executor_->Destroy(); + } + factory_->RemoveJob(this); + factory_ = nullptr; + callback_.Run(error); + } + + MultiThreadedProxyResolverFactory* factory_; + std::unique_ptr<ProxyResolver>* const resolver_out_; + std::unique_ptr<ProxyResolverFactory> resolver_factory_; + const size_t max_num_threads_; + scoped_refptr<ProxyResolverScriptData> script_data_; + scoped_refptr<Executor> executor_; + const CompletionCallback callback_; +}; + +MultiThreadedProxyResolverFactory::MultiThreadedProxyResolverFactory( + size_t max_num_threads, + bool factory_expects_bytes) + : ProxyResolverFactory(factory_expects_bytes), + max_num_threads_(max_num_threads) { + DCHECK_GE(max_num_threads, 1u); +} + +MultiThreadedProxyResolverFactory::~MultiThreadedProxyResolverFactory() { + for (auto* job : jobs_) { + job->FactoryDestroyed(); + } +} + +int MultiThreadedProxyResolverFactory::CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback, + std::unique_ptr<Request>* request) { + std::unique_ptr<Job> job(new Job(this, pac_script, resolver, + CreateProxyResolverFactory(), + max_num_threads_, callback)); + jobs_.insert(job.get()); + *request = std::move(job); + return ERR_IO_PENDING; +} + +void MultiThreadedProxyResolverFactory::RemoveJob( + MultiThreadedProxyResolverFactory::Job* job) { + size_t erased = jobs_.erase(job); + DCHECK_EQ(1u, erased); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/multi_threaded_proxy_resolver.h b/chromium/net/proxy_resolution/multi_threaded_proxy_resolver.h new file mode 100644 index 00000000000..caf36faa7bb --- /dev/null +++ b/chromium/net/proxy_resolution/multi_threaded_proxy_resolver.h @@ -0,0 +1,81 @@ +// 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_RESOLUTION_MULTI_THREADED_PROXY_RESOLVER_H_ +#define NET_PROXY_RESOLUTION_MULTI_THREADED_PROXY_RESOLVER_H_ + +#include <stddef.h> + +#include <memory> +#include <set> + +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/proxy_resolver_factory.h" + +namespace net { +class ProxyResolver; + +// MultiThreadedProxyResolverFactory creates instances of a ProxyResolver +// implementation that runs synchronous ProxyResolver implementations on worker +// threads. +// +// Threads are created lazily on demand, up to a maximum total. The advantage +// of having a pool of threads, is faster performance. In particular, being +// able to keep servicing PAC requests even if one blocks its execution. +// +// During initialization (CreateProxyResolver), a single thread is spun up to +// test the script. If this succeeds, we cache the input script, and will re-use +// this to lazily provision any new threads as needed. +// +// For each new thread that we spawn in a particular MultiThreadedProxyResolver +// instance, a corresponding new ProxyResolver is created using the +// ProxyResolverFactory returned by CreateProxyResolverFactory(). +// +// Because we are creating multiple ProxyResolver instances, this means we +// are duplicating script contexts for what is ordinarily seen as being a +// single script. This can affect compatibility on some classes of PAC +// script: +// +// (a) Scripts whose initialization has external dependencies on network or +// time may end up successfully initializing on some threads, but not +// others. So depending on what thread services the request, the result +// may jump between several possibilities. +// +// (b) Scripts whose FindProxyForURL() depends on side-effects may now +// work differently. For example, a PAC script which was incrementing +// a global counter and using that to make a decision. In the +// multi-threaded model, each thread may have a different value for this +// counter, so it won't globally be seen as monotonically increasing! +class NET_EXPORT_PRIVATE MultiThreadedProxyResolverFactory + : public ProxyResolverFactory { + public: + MultiThreadedProxyResolverFactory(size_t max_num_threads, + bool factory_expects_bytes); + ~MultiThreadedProxyResolverFactory() override; + + int CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback, + std::unique_ptr<Request>* request) override; + + private: + class Job; + + // Invoked to create a ProxyResolverFactory instance to pass to a + // MultiThreadedProxyResolver instance. + virtual std::unique_ptr<ProxyResolverFactory> + CreateProxyResolverFactory() = 0; + + void RemoveJob(Job* job); + + const size_t max_num_threads_; + + std::set<Job*> jobs_; +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_MULTI_THREADED_PROXY_RESOLVER_H_ diff --git a/chromium/net/proxy_resolution/multi_threaded_proxy_resolver_unittest.cc b/chromium/net/proxy_resolution/multi_threaded_proxy_resolver_unittest.cc new file mode 100644 index 00000000000..53ce0aa15e2 --- /dev/null +++ b/chromium/net/proxy_resolution/multi_threaded_proxy_resolver_unittest.cc @@ -0,0 +1,784 @@ +// Copyright (c) 2012 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_resolution/multi_threaded_proxy_resolver.h" + +#include <utility> +#include <vector> + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/log/net_log_event_type.h" +#include "net/log/net_log_with_source.h" +#include "net/log/test_net_log.h" +#include "net/log/test_net_log_entry.h" +#include "net/log/test_net_log_util.h" +#include "net/proxy_resolution/mock_proxy_resolver.h" +#include "net/proxy_resolution/proxy_info.h" +#include "net/proxy_resolution/proxy_resolver_factory.h" +#include "net/test/gtest_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using net::test::IsError; +using net::test::IsOk; + +using base::ASCIIToUTF16; + +namespace net { + +namespace { + +// A synchronous mock ProxyResolver implementation, which can be used in +// conjunction with MultiThreadedProxyResolver. +// - returns a single-item proxy list with the query's host. +class MockProxyResolver : public ProxyResolver { + public: + MockProxyResolver() + : worker_loop_(base::MessageLoop::current()), request_count_(0) {} + + // ProxyResolver implementation. + int GetProxyForURL(const GURL& query_url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) override { + if (!resolve_latency_.is_zero()) + base::PlatformThread::Sleep(resolve_latency_); + + CheckIsOnWorkerThread(); + + EXPECT_TRUE(callback.is_null()); + EXPECT_TRUE(request == NULL); + + // Write something into |net_log| (doesn't really have any meaning.) + net_log.BeginEvent(NetLogEventType::PAC_JAVASCRIPT_ALERT); + + results->UseNamedProxy(query_url.host()); + + // Return a success code which represents the request's order. + return request_count_++; + } + + int request_count() const { return request_count_; } + + void SetResolveLatency(base::TimeDelta latency) { + resolve_latency_ = latency; + } + + private: + void CheckIsOnWorkerThread() { + EXPECT_EQ(base::MessageLoop::current(), worker_loop_); + } + + base::MessageLoop* worker_loop_; + int request_count_; + base::TimeDelta resolve_latency_; +}; + + +// A mock synchronous ProxyResolver which can be set to block upon reaching +// GetProxyForURL(). +// TODO(eroman): WaitUntilBlocked() *must* be called before calling Unblock(), +// otherwise there will be a race on |should_block_| since it is +// read without any synchronization. +class BlockableProxyResolver : public MockProxyResolver { + public: + BlockableProxyResolver() + : should_block_(false), + unblocked_(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::SIGNALED), + blocked_(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED) {} + + void Block() { + should_block_ = true; + unblocked_.Reset(); + } + + void Unblock() { + should_block_ = false; + blocked_.Reset(); + unblocked_.Signal(); + } + + void WaitUntilBlocked() { + blocked_.Wait(); + } + + int GetProxyForURL(const GURL& query_url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) override { + if (should_block_) { + blocked_.Signal(); + unblocked_.Wait(); + } + + return MockProxyResolver::GetProxyForURL( + query_url, results, callback, request, net_log); + } + + private: + bool should_block_; + base::WaitableEvent unblocked_; + base::WaitableEvent blocked_; +}; + +// This factory returns new instances of BlockableProxyResolver. +class BlockableProxyResolverFactory : public ProxyResolverFactory { + public: + BlockableProxyResolverFactory() : ProxyResolverFactory(false) {} + + ~BlockableProxyResolverFactory() override = default; + + int CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& script_data, + std::unique_ptr<ProxyResolver>* result, + const CompletionCallback& callback, + std::unique_ptr<Request>* request) override { + BlockableProxyResolver* resolver = new BlockableProxyResolver; + result->reset(resolver); + base::AutoLock lock(lock_); + resolvers_.push_back(resolver); + script_data_.push_back(script_data); + return OK; + } + + std::vector<BlockableProxyResolver*> resolvers() { + base::AutoLock lock(lock_); + return resolvers_; + } + + const std::vector<scoped_refptr<ProxyResolverScriptData>> script_data() { + base::AutoLock lock(lock_); + return script_data_; + } + + private: + std::vector<BlockableProxyResolver*> resolvers_; + std::vector<scoped_refptr<ProxyResolverScriptData>> script_data_; + base::Lock lock_; +}; + +class SingleShotMultiThreadedProxyResolverFactory + : public MultiThreadedProxyResolverFactory { + public: + SingleShotMultiThreadedProxyResolverFactory( + size_t max_num_threads, + std::unique_ptr<ProxyResolverFactory> factory) + : MultiThreadedProxyResolverFactory(max_num_threads, false), + factory_(std::move(factory)) {} + + std::unique_ptr<ProxyResolverFactory> CreateProxyResolverFactory() override { + DCHECK(factory_); + return std::move(factory_); + } + + private: + std::unique_ptr<ProxyResolverFactory> factory_; +}; + +class MultiThreadedProxyResolverTest : public testing::Test { + public: + void Init(size_t num_threads) { + std::unique_ptr<BlockableProxyResolverFactory> factory_owner( + new BlockableProxyResolverFactory); + factory_ = factory_owner.get(); + resolver_factory_.reset(new SingleShotMultiThreadedProxyResolverFactory( + num_threads, std::move(factory_owner))); + TestCompletionCallback ready_callback; + std::unique_ptr<ProxyResolverFactory::Request> request; + resolver_factory_->CreateProxyResolver( + ProxyResolverScriptData::FromUTF8("pac script bytes"), &resolver_, + ready_callback.callback(), &request); + EXPECT_TRUE(request); + ASSERT_THAT(ready_callback.WaitForResult(), IsOk()); + + // Verify that the script data reaches the synchronous resolver factory. + ASSERT_EQ(1u, factory_->script_data().size()); + EXPECT_EQ(ASCIIToUTF16("pac script bytes"), + factory_->script_data()[0]->utf16()); + } + + void ClearResolver() { resolver_.reset(); } + + BlockableProxyResolverFactory& factory() { + DCHECK(factory_); + return *factory_; + } + ProxyResolver& resolver() { + DCHECK(resolver_); + return *resolver_; + } + + private: + BlockableProxyResolverFactory* factory_ = nullptr; + std::unique_ptr<ProxyResolverFactory> factory_owner_; + std::unique_ptr<MultiThreadedProxyResolverFactory> resolver_factory_; + std::unique_ptr<ProxyResolver> resolver_; +}; + +TEST_F(MultiThreadedProxyResolverTest, SingleThread_Basic) { + const size_t kNumThreads = 1u; + ASSERT_NO_FATAL_FAILURE(Init(kNumThreads)); + + // Start request 0. + int rv; + TestCompletionCallback callback0; + BoundTestNetLog log0; + ProxyInfo results0; + rv = resolver().GetProxyForURL(GURL("http://request0"), &results0, + callback0.callback(), NULL, log0.bound()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Wait for request 0 to finish. + rv = callback0.WaitForResult(); + EXPECT_EQ(0, rv); + EXPECT_EQ("PROXY request0:80", results0.ToPacString()); + + // The mock proxy resolver should have written 1 log entry. And + // on completion, this should have been copied into |log0|. + // We also have 1 log entry that was emitted by the + // MultiThreadedProxyResolver. + TestNetLogEntry::List entries0; + log0.GetEntries(&entries0); + + ASSERT_EQ(2u, entries0.size()); + EXPECT_EQ(NetLogEventType::SUBMITTED_TO_RESOLVER_THREAD, entries0[0].type); + + // Start 3 more requests (request1 to request3). + + TestCompletionCallback callback1; + ProxyInfo results1; + rv = + resolver().GetProxyForURL(GURL("http://request1"), &results1, + callback1.callback(), NULL, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + TestCompletionCallback callback2; + ProxyInfo results2; + rv = + resolver().GetProxyForURL(GURL("http://request2"), &results2, + callback2.callback(), NULL, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + TestCompletionCallback callback3; + ProxyInfo results3; + rv = + resolver().GetProxyForURL(GURL("http://request3"), &results3, + callback3.callback(), NULL, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Wait for the requests to finish (they must finish in the order they were + // started, which is what we check for from their magic return value) + + rv = callback1.WaitForResult(); + EXPECT_EQ(1, rv); + EXPECT_EQ("PROXY request1:80", results1.ToPacString()); + + rv = callback2.WaitForResult(); + EXPECT_EQ(2, rv); + EXPECT_EQ("PROXY request2:80", results2.ToPacString()); + + rv = callback3.WaitForResult(); + EXPECT_EQ(3, rv); + EXPECT_EQ("PROXY request3:80", results3.ToPacString()); +} + +// Tests that the NetLog is updated to include the time the request was waiting +// to be scheduled to a thread. +TEST_F(MultiThreadedProxyResolverTest, + SingleThread_UpdatesNetLogWithThreadWait) { + const size_t kNumThreads = 1u; + ASSERT_NO_FATAL_FAILURE(Init(kNumThreads)); + + int rv; + + // Block the proxy resolver, so no request can complete. + factory().resolvers()[0]->Block(); + + // Start request 0. + std::unique_ptr<ProxyResolver::Request> request0; + TestCompletionCallback callback0; + ProxyInfo results0; + BoundTestNetLog log0; + rv = resolver().GetProxyForURL(GURL("http://request0"), &results0, + callback0.callback(), &request0, log0.bound()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Start 2 more requests (request1 and request2). + + TestCompletionCallback callback1; + ProxyInfo results1; + BoundTestNetLog log1; + rv = resolver().GetProxyForURL(GURL("http://request1"), &results1, + callback1.callback(), NULL, log1.bound()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + std::unique_ptr<ProxyResolver::Request> request2; + TestCompletionCallback callback2; + ProxyInfo results2; + BoundTestNetLog log2; + rv = resolver().GetProxyForURL(GURL("http://request2"), &results2, + callback2.callback(), &request2, log2.bound()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Unblock the worker thread so the requests can continue running. + factory().resolvers()[0]->WaitUntilBlocked(); + factory().resolvers()[0]->Unblock(); + + // Check that request 0 completed as expected. + // The NetLog has 1 entry that came from the MultiThreadedProxyResolver, and + // 1 entry from the mock proxy resolver. + EXPECT_EQ(0, callback0.WaitForResult()); + EXPECT_EQ("PROXY request0:80", results0.ToPacString()); + + TestNetLogEntry::List entries0; + log0.GetEntries(&entries0); + + ASSERT_EQ(2u, entries0.size()); + EXPECT_EQ(NetLogEventType::SUBMITTED_TO_RESOLVER_THREAD, entries0[0].type); + + // Check that request 1 completed as expected. + EXPECT_EQ(1, callback1.WaitForResult()); + EXPECT_EQ("PROXY request1:80", results1.ToPacString()); + + TestNetLogEntry::List entries1; + log1.GetEntries(&entries1); + + ASSERT_EQ(4u, entries1.size()); + EXPECT_TRUE(LogContainsBeginEvent( + entries1, 0, NetLogEventType::WAITING_FOR_PROXY_RESOLVER_THREAD)); + EXPECT_TRUE(LogContainsEndEvent( + entries1, 1, NetLogEventType::WAITING_FOR_PROXY_RESOLVER_THREAD)); + + // Check that request 2 completed as expected. + EXPECT_EQ(2, callback2.WaitForResult()); + EXPECT_EQ("PROXY request2:80", results2.ToPacString()); + + TestNetLogEntry::List entries2; + log2.GetEntries(&entries2); + + ASSERT_EQ(4u, entries2.size()); + EXPECT_TRUE(LogContainsBeginEvent( + entries2, 0, NetLogEventType::WAITING_FOR_PROXY_RESOLVER_THREAD)); + EXPECT_TRUE(LogContainsEndEvent( + entries2, 1, NetLogEventType::WAITING_FOR_PROXY_RESOLVER_THREAD)); +} + +// Cancel a request which is in progress, and then cancel a request which +// is pending. +TEST_F(MultiThreadedProxyResolverTest, SingleThread_CancelRequest) { + const size_t kNumThreads = 1u; + ASSERT_NO_FATAL_FAILURE(Init(kNumThreads)); + + int rv; + + // Block the proxy resolver, so no request can complete. + factory().resolvers()[0]->Block(); + + // Start request 0. + std::unique_ptr<ProxyResolver::Request> request0; + TestCompletionCallback callback0; + ProxyInfo results0; + rv = resolver().GetProxyForURL(GURL("http://request0"), &results0, + callback0.callback(), &request0, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Wait until requests 0 reaches the worker thread. + factory().resolvers()[0]->WaitUntilBlocked(); + + // Start 3 more requests (request1 : request3). + + TestCompletionCallback callback1; + ProxyInfo results1; + rv = + resolver().GetProxyForURL(GURL("http://request1"), &results1, + callback1.callback(), NULL, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + std::unique_ptr<ProxyResolver::Request> request2; + TestCompletionCallback callback2; + ProxyInfo results2; + rv = resolver().GetProxyForURL(GURL("http://request2"), &results2, + callback2.callback(), &request2, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + TestCompletionCallback callback3; + ProxyInfo results3; + rv = + resolver().GetProxyForURL(GURL("http://request3"), &results3, + callback3.callback(), NULL, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Cancel request0 (inprogress) and request2 (pending). + request0.reset(); + request2.reset(); + + // Unblock the worker thread so the requests can continue running. + factory().resolvers()[0]->Unblock(); + + // Wait for requests 1 and 3 to finish. + + rv = callback1.WaitForResult(); + EXPECT_EQ(1, rv); + EXPECT_EQ("PROXY request1:80", results1.ToPacString()); + + rv = callback3.WaitForResult(); + // Note that since request2 was cancelled before reaching the resolver, + // the request count is 2 and not 3 here. + EXPECT_EQ(2, rv); + EXPECT_EQ("PROXY request3:80", results3.ToPacString()); + + // Requests 0 and 2 which were cancelled, hence their completion callbacks + // were never summoned. + EXPECT_FALSE(callback0.have_result()); + EXPECT_FALSE(callback2.have_result()); +} + +// Test that deleting MultiThreadedProxyResolver while requests are +// outstanding cancels them (and doesn't leak anything). +TEST_F(MultiThreadedProxyResolverTest, SingleThread_CancelRequestByDeleting) { + const size_t kNumThreads = 1u; + ASSERT_NO_FATAL_FAILURE(Init(kNumThreads)); + + ASSERT_EQ(1u, factory().resolvers().size()); + + // Block the proxy resolver, so no request can complete. + factory().resolvers()[0]->Block(); + + int rv; + // Start 3 requests. + + TestCompletionCallback callback0; + ProxyInfo results0; + rv = + resolver().GetProxyForURL(GURL("http://request0"), &results0, + callback0.callback(), NULL, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + TestCompletionCallback callback1; + ProxyInfo results1; + rv = + resolver().GetProxyForURL(GURL("http://request1"), &results1, + callback1.callback(), NULL, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + TestCompletionCallback callback2; + ProxyInfo results2; + rv = + resolver().GetProxyForURL(GURL("http://request2"), &results2, + callback2.callback(), NULL, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Wait until request 0 reaches the worker thread. + factory().resolvers()[0]->WaitUntilBlocked(); + + // Add some latency, to improve the chance that when + // MultiThreadedProxyResolver is deleted below we are still running inside + // of the worker thread. The test will pass regardless, so this race doesn't + // cause flakiness. However the destruction during execution is a more + // interesting case to test. + factory().resolvers()[0]->SetResolveLatency( + base::TimeDelta::FromMilliseconds(100)); + + // Unblock the worker thread and delete the underlying + // MultiThreadedProxyResolver immediately. + factory().resolvers()[0]->Unblock(); + ClearResolver(); + + // Give any posted tasks a chance to run (in case there is badness). + base::RunLoop().RunUntilIdle(); + + // Check that none of the outstanding requests were completed. + EXPECT_FALSE(callback0.have_result()); + EXPECT_FALSE(callback1.have_result()); + EXPECT_FALSE(callback2.have_result()); +} + +// Tests setting the PAC script once, lazily creating new threads, and +// cancelling requests. +TEST_F(MultiThreadedProxyResolverTest, ThreeThreads_Basic) { + const size_t kNumThreads = 3u; + ASSERT_NO_FATAL_FAILURE(Init(kNumThreads)); + + // Verify that it reaches the synchronous resolver. + // One thread has been provisioned (i.e. one ProxyResolver was created). + ASSERT_EQ(1u, factory().resolvers().size()); + + const int kNumRequests = 8; + int rv; + TestCompletionCallback callback[kNumRequests]; + ProxyInfo results[kNumRequests]; + std::unique_ptr<ProxyResolver::Request> request[kNumRequests]; + + // Start request 0 -- this should run on thread 0 as there is nothing else + // going on right now. + rv = resolver().GetProxyForURL(GURL("http://request0"), &results[0], + callback[0].callback(), &request[0], + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Wait for request 0 to finish. + rv = callback[0].WaitForResult(); + EXPECT_EQ(0, rv); + EXPECT_EQ("PROXY request0:80", results[0].ToPacString()); + ASSERT_EQ(1u, factory().resolvers().size()); + EXPECT_EQ(1, factory().resolvers()[0]->request_count()); + + base::RunLoop().RunUntilIdle(); + + // We now block the first resolver to ensure a request is sent to the second + // thread. + factory().resolvers()[0]->Block(); + rv = resolver().GetProxyForURL(GURL("http://request1"), &results[1], + callback[1].callback(), &request[1], + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + factory().resolvers()[0]->WaitUntilBlocked(); + rv = resolver().GetProxyForURL(GURL("http://request2"), &results[2], + callback[2].callback(), &request[2], + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_EQ(0, callback[2].WaitForResult()); + ASSERT_EQ(2u, factory().resolvers().size()); + + // We now block the second resolver as well to ensure a request is sent to the + // third thread. + factory().resolvers()[1]->Block(); + rv = resolver().GetProxyForURL(GURL("http://request3"), &results[3], + callback[3].callback(), &request[3], + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + factory().resolvers()[1]->WaitUntilBlocked(); + rv = resolver().GetProxyForURL(GURL("http://request4"), &results[4], + callback[4].callback(), &request[4], + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_EQ(0, callback[4].WaitForResult()); + + // We should now have a total of 3 threads, each with its own ProxyResolver + // that will get initialized with the same data. + ASSERT_EQ(3u, factory().resolvers().size()); + + ASSERT_EQ(3u, factory().script_data().size()); + for (int i = 0; i < 3; ++i) { + EXPECT_EQ(ASCIIToUTF16("pac script bytes"), + factory().script_data()[i]->utf16()) + << "i=" << i; + } + + // Start and cancel two requests. Since the first two threads are still + // blocked, they'll both be serviced by the third thread. The first request + // will reach the resolver, but the second will still be queued when canceled. + // Start a third request so we can be sure the resolver has completed running + // the first request. + rv = resolver().GetProxyForURL(GURL("http://request5"), &results[5], + callback[5].callback(), &request[5], + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + rv = resolver().GetProxyForURL(GURL("http://request6"), &results[6], + callback[6].callback(), &request[6], + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + rv = resolver().GetProxyForURL(GURL("http://request7"), &results[7], + callback[7].callback(), &request[7], + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + request[5].reset(); + request[6].reset(); + + EXPECT_EQ(2, callback[7].WaitForResult()); + + // Check that the cancelled requests never invoked their callback. + EXPECT_FALSE(callback[5].have_result()); + EXPECT_FALSE(callback[6].have_result()); + + // Unblock the first two threads and wait for their requests to complete. + factory().resolvers()[0]->Unblock(); + factory().resolvers()[1]->Unblock(); + EXPECT_EQ(1, callback[1].WaitForResult()); + EXPECT_EQ(1, callback[3].WaitForResult()); + + EXPECT_EQ(2, factory().resolvers()[0]->request_count()); + EXPECT_EQ(2, factory().resolvers()[1]->request_count()); + EXPECT_EQ(3, factory().resolvers()[2]->request_count()); +} + +// Tests using two threads. The first request hangs the first thread. Checks +// that other requests are able to complete while this first request remains +// stalled. +TEST_F(MultiThreadedProxyResolverTest, OneThreadBlocked) { + const size_t kNumThreads = 2u; + ASSERT_NO_FATAL_FAILURE(Init(kNumThreads)); + + int rv; + + // One thread has been provisioned (i.e. one ProxyResolver was created). + ASSERT_EQ(1u, factory().resolvers().size()); + EXPECT_EQ(ASCIIToUTF16("pac script bytes"), + factory().script_data()[0]->utf16()); + + const int kNumRequests = 4; + TestCompletionCallback callback[kNumRequests]; + ProxyInfo results[kNumRequests]; + std::unique_ptr<ProxyResolver::Request> request[kNumRequests]; + + // Start a request that will block the first thread. + + factory().resolvers()[0]->Block(); + + rv = resolver().GetProxyForURL(GURL("http://request0"), &results[0], + callback[0].callback(), &request[0], + NetLogWithSource()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + factory().resolvers()[0]->WaitUntilBlocked(); + + // Start 3 more requests -- they should all be serviced by thread #2 + // since thread #1 is blocked. + + for (int i = 1; i < kNumRequests; ++i) { + rv = resolver().GetProxyForURL( + GURL(base::StringPrintf("http://request%d", i)), &results[i], + callback[i].callback(), &request[i], NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + } + + // Wait for the three requests to complete (they should complete in FIFO + // order). + for (int i = 1; i < kNumRequests; ++i) { + EXPECT_EQ(i - 1, callback[i].WaitForResult()); + } + + // Unblock the first thread. + factory().resolvers()[0]->Unblock(); + EXPECT_EQ(0, callback[0].WaitForResult()); + + // All in all, the first thread should have seen just 1 request. And the + // second thread 3 requests. + ASSERT_EQ(2u, factory().resolvers().size()); + EXPECT_EQ(1, factory().resolvers()[0]->request_count()); + EXPECT_EQ(3, factory().resolvers()[1]->request_count()); +} + +class FailingProxyResolverFactory : public ProxyResolverFactory { + public: + FailingProxyResolverFactory() : ProxyResolverFactory(false) {} + + // ProxyResolverFactory override. + int CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& script_data, + std::unique_ptr<ProxyResolver>* result, + const CompletionCallback& callback, + std::unique_ptr<Request>* request) override { + return ERR_PAC_SCRIPT_FAILED; + } +}; + +// Test that an error when creating the synchronous resolver causes the +// MultiThreadedProxyResolverFactory create request to fail with that error. +TEST_F(MultiThreadedProxyResolverTest, ProxyResolverFactoryError) { + const size_t kNumThreads = 1u; + SingleShotMultiThreadedProxyResolverFactory resolver_factory( + kNumThreads, std::make_unique<FailingProxyResolverFactory>()); + TestCompletionCallback ready_callback; + std::unique_ptr<ProxyResolverFactory::Request> request; + std::unique_ptr<ProxyResolver> resolver; + EXPECT_EQ(ERR_IO_PENDING, + resolver_factory.CreateProxyResolver( + ProxyResolverScriptData::FromUTF8("pac script bytes"), + &resolver, ready_callback.callback(), &request)); + EXPECT_TRUE(request); + EXPECT_THAT(ready_callback.WaitForResult(), IsError(ERR_PAC_SCRIPT_FAILED)); + EXPECT_FALSE(resolver); +} + +void Fail(int error) { + FAIL() << "Unexpected callback with error " << error; +} + +// Test that cancelling an in-progress create request works correctly. +TEST_F(MultiThreadedProxyResolverTest, CancelCreate) { + const size_t kNumThreads = 1u; + { + SingleShotMultiThreadedProxyResolverFactory resolver_factory( + kNumThreads, std::make_unique<BlockableProxyResolverFactory>()); + std::unique_ptr<ProxyResolverFactory::Request> request; + std::unique_ptr<ProxyResolver> resolver; + EXPECT_EQ(ERR_IO_PENDING, + resolver_factory.CreateProxyResolver( + ProxyResolverScriptData::FromUTF8("pac script bytes"), + &resolver, base::Bind(&Fail), &request)); + EXPECT_TRUE(request); + request.reset(); + } + // The factory destructor will block until the worker thread stops, but it may + // post tasks to the origin message loop which are still pending. Run them + // now to ensure it works as expected. + base::RunLoop().RunUntilIdle(); +} + +void DeleteRequest(const CompletionCallback& callback, + std::unique_ptr<ProxyResolverFactory::Request>* request, + int result) { + callback.Run(result); + request->reset(); +} + +// Test that delete the Request during the factory callback works correctly. +TEST_F(MultiThreadedProxyResolverTest, DeleteRequestInFactoryCallback) { + const size_t kNumThreads = 1u; + SingleShotMultiThreadedProxyResolverFactory resolver_factory( + kNumThreads, std::make_unique<BlockableProxyResolverFactory>()); + std::unique_ptr<ProxyResolverFactory::Request> request; + std::unique_ptr<ProxyResolver> resolver; + TestCompletionCallback callback; + EXPECT_EQ(ERR_IO_PENDING, + resolver_factory.CreateProxyResolver( + ProxyResolverScriptData::FromUTF8("pac script bytes"), + &resolver, base::Bind(&DeleteRequest, callback.callback(), + base::Unretained(&request)), + &request)); + EXPECT_TRUE(request); + EXPECT_THAT(callback.WaitForResult(), IsOk()); +} + +// Test that deleting the factory with a request in-progress works correctly. +TEST_F(MultiThreadedProxyResolverTest, DestroyFactoryWithRequestsInProgress) { + const size_t kNumThreads = 1u; + std::unique_ptr<ProxyResolverFactory::Request> request; + std::unique_ptr<ProxyResolver> resolver; + { + SingleShotMultiThreadedProxyResolverFactory resolver_factory( + kNumThreads, std::make_unique<BlockableProxyResolverFactory>()); + EXPECT_EQ(ERR_IO_PENDING, + resolver_factory.CreateProxyResolver( + ProxyResolverScriptData::FromUTF8("pac script bytes"), + &resolver, base::Bind(&Fail), &request)); + EXPECT_TRUE(request); + } + // The factory destructor will block until the worker thread stops, but it may + // post tasks to the origin message loop which are still pending. Run them + // now to ensure it works as expected. + base::RunLoop().RunUntilIdle(); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/proxy_resolution/network_delegate_error_observer.cc b/chromium/net/proxy_resolution/network_delegate_error_observer.cc new file mode 100644 index 00000000000..a5881617741 --- /dev/null +++ b/chromium/net/proxy_resolution/network_delegate_error_observer.cc @@ -0,0 +1,92 @@ +// 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_resolution/network_delegate_error_observer.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/single_thread_task_runner.h" +#include "net/base/net_errors.h" +#include "net/base/network_delegate.h" + +namespace net { + +// NetworkDelegateErrorObserver::Core ----------------------------------------- + +class NetworkDelegateErrorObserver::Core + : public base::RefCountedThreadSafe<NetworkDelegateErrorObserver::Core> { + public: + Core(NetworkDelegate* network_delegate, + base::SingleThreadTaskRunner* origin_runner); + + void NotifyPACScriptError(int line_number, const base::string16& error); + + void Shutdown(); + + private: + friend class base::RefCountedThreadSafe<NetworkDelegateErrorObserver::Core>; + + virtual ~Core(); + + NetworkDelegate* network_delegate_; + scoped_refptr<base::SingleThreadTaskRunner> origin_runner_; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +NetworkDelegateErrorObserver::Core::Core( + NetworkDelegate* network_delegate, + base::SingleThreadTaskRunner* origin_runner) + : network_delegate_(network_delegate), origin_runner_(origin_runner) { + DCHECK(origin_runner); +} + +NetworkDelegateErrorObserver::Core::~Core() = default; + +void NetworkDelegateErrorObserver::Core::NotifyPACScriptError( + int line_number, + const base::string16& error) { + if (!origin_runner_->BelongsToCurrentThread()) { + origin_runner_->PostTask(FROM_HERE, base::Bind(&Core::NotifyPACScriptError, + this, line_number, error)); + return; + } + if (network_delegate_) + network_delegate_->NotifyPACScriptError(line_number, error); +} + +void NetworkDelegateErrorObserver::Core::Shutdown() { + CHECK(origin_runner_->BelongsToCurrentThread()); + network_delegate_ = NULL; +} + +// NetworkDelegateErrorObserver ----------------------------------------------- + +NetworkDelegateErrorObserver::NetworkDelegateErrorObserver( + NetworkDelegate* network_delegate, + base::SingleThreadTaskRunner* origin_runner) + : core_(new Core(network_delegate, origin_runner)) { +} + +NetworkDelegateErrorObserver::~NetworkDelegateErrorObserver() { + core_->Shutdown(); +} + +// static +std::unique_ptr<ProxyResolverErrorObserver> +NetworkDelegateErrorObserver::Create( + NetworkDelegate* network_delegate, + const scoped_refptr<base::SingleThreadTaskRunner>& origin_runner) { + return std::make_unique<NetworkDelegateErrorObserver>(network_delegate, + origin_runner.get()); +} + +void NetworkDelegateErrorObserver::OnPACScriptError( + int line_number, + const base::string16& error) { + core_->NotifyPACScriptError(line_number, error); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/network_delegate_error_observer.h b/chromium/net/proxy_resolution/network_delegate_error_observer.h new file mode 100644 index 00000000000..1d8264a8160 --- /dev/null +++ b/chromium/net/proxy_resolution/network_delegate_error_observer.h @@ -0,0 +1,50 @@ +// 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_RESOLUTION_NETWORK_DELEGATE_ERROR_OBSERVER_H_ +#define NET_PROXY_RESOLUTION_NETWORK_DELEGATE_ERROR_OBSERVER_H_ + +#include <memory> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/proxy_resolver_error_observer.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace net { + +class NetworkDelegate; + +// An implementation of ProxyResolverErrorObserver that forwards PAC script +// errors to a NetworkDelegate object on the thread it lives on. +class NET_EXPORT_PRIVATE NetworkDelegateErrorObserver + : public ProxyResolverErrorObserver { + public: + NetworkDelegateErrorObserver(NetworkDelegate* network_delegate, + base::SingleThreadTaskRunner* origin_runner); + ~NetworkDelegateErrorObserver() override; + + static std::unique_ptr<ProxyResolverErrorObserver> Create( + NetworkDelegate* network_delegate, + const scoped_refptr<base::SingleThreadTaskRunner>& origin_runner); + + // ProxyResolverErrorObserver implementation. + void OnPACScriptError(int line_number, const base::string16& error) override; + + private: + class Core; + + scoped_refptr<Core> core_; + + DISALLOW_COPY_AND_ASSIGN(NetworkDelegateErrorObserver); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_NETWORK_DELEGATE_ERROR_OBSERVER_H_ diff --git a/chromium/net/proxy_resolution/network_delegate_error_observer_unittest.cc b/chromium/net/proxy_resolution/network_delegate_error_observer_unittest.cc new file mode 100644 index 00000000000..9e8941899d0 --- /dev/null +++ b/chromium/net/proxy_resolution/network_delegate_error_observer_unittest.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2012 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_resolution/network_delegate_error_observer.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread.h" +#include "base/threading/thread_task_runner_handle.h" +#include "net/base/net_errors.h" +#include "net/base/network_delegate_impl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class TestNetworkDelegate : public NetworkDelegateImpl { + public: + TestNetworkDelegate() : got_pac_error_(false) {} + ~TestNetworkDelegate() override = default; + + bool got_pac_error() const { return got_pac_error_; } + + private: + // NetworkDelegate implementation. + int OnBeforeURLRequest(URLRequest* request, + const CompletionCallback& callback, + GURL* new_url) override { + return OK; + } + int OnBeforeStartTransaction(URLRequest* request, + const CompletionCallback& callback, + HttpRequestHeaders* headers) override { + return OK; + } + void OnStartTransaction(URLRequest* request, + const HttpRequestHeaders& headers) override {} + int OnHeadersReceived( + URLRequest* request, + const CompletionCallback& callback, + const HttpResponseHeaders* original_response_headers, + scoped_refptr<HttpResponseHeaders>* override_response_headers, + GURL* allowed_unsafe_redirect_url) override { + return OK; + } + void OnBeforeRedirect(URLRequest* request, + const GURL& new_location) override {} + void OnResponseStarted(URLRequest* request, int net_error) override {} + void OnCompleted(URLRequest* request, bool started, int net_error) override {} + void OnURLRequestDestroyed(URLRequest* request) override {} + + void OnPACScriptError(int line_number, const base::string16& error) override { + got_pac_error_ = true; + } + AuthRequiredResponse OnAuthRequired(URLRequest* request, + const AuthChallengeInfo& auth_info, + const AuthCallback& callback, + AuthCredentials* credentials) override { + return AUTH_REQUIRED_RESPONSE_NO_ACTION; + } + bool OnCanGetCookies(const URLRequest& request, + const CookieList& cookie_list) override { + return true; + } + bool OnCanSetCookie(const URLRequest& request, + const net::CanonicalCookie& cookie, + CookieOptions* options) override { + return true; + } + bool OnCanAccessFile(const URLRequest& request, + const base::FilePath& original_path, + const base::FilePath& absolute_path) const override { + return true; + } + + bool got_pac_error_; +}; + +// Check that the OnPACScriptError method can be called from an arbitrary +// thread. +TEST(NetworkDelegateErrorObserverTest, CallOnThread) { + base::Thread thread("test_thread"); + thread.Start(); + TestNetworkDelegate network_delegate; + NetworkDelegateErrorObserver observer( + &network_delegate, base::ThreadTaskRunnerHandle::Get().get()); + thread.task_runner()->PostTask( + FROM_HERE, base::Bind(&NetworkDelegateErrorObserver::OnPACScriptError, + base::Unretained(&observer), 42, base::string16())); + thread.Stop(); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(network_delegate.got_pac_error()); +} + +// Check that passing a NULL network delegate works. +TEST(NetworkDelegateErrorObserverTest, NoDelegate) { + base::Thread thread("test_thread"); + thread.Start(); + NetworkDelegateErrorObserver observer( + NULL, base::ThreadTaskRunnerHandle::Get().get()); + thread.task_runner()->PostTask( + FROM_HERE, base::Bind(&NetworkDelegateErrorObserver::OnPACScriptError, + base::Unretained(&observer), 42, base::string16())); + thread.Stop(); + base::RunLoop().RunUntilIdle(); + // Shouldn't have crashed until here... +} + +} // namespace + +} // namespace net diff --git a/chromium/net/proxy_resolution/pac_file_data.cc b/chromium/net/proxy_resolution/pac_file_data.cc new file mode 100644 index 00000000000..c0f8cc1889b --- /dev/null +++ b/chromium/net/proxy_resolution/pac_file_data.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2012 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_resolution/pac_file_data.h" + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" + +namespace net { + +// static +scoped_refptr<ProxyResolverScriptData> ProxyResolverScriptData::FromUTF8( + const std::string& utf8) { + return new ProxyResolverScriptData(TYPE_SCRIPT_CONTENTS, GURL(), + base::UTF8ToUTF16(utf8)); +} + +// static +scoped_refptr<ProxyResolverScriptData> ProxyResolverScriptData::FromUTF16( + const base::string16& utf16) { + return new ProxyResolverScriptData(TYPE_SCRIPT_CONTENTS, GURL(), utf16); +} + +// static +scoped_refptr<ProxyResolverScriptData> ProxyResolverScriptData::FromURL( + const GURL& url) { + return new ProxyResolverScriptData(TYPE_SCRIPT_URL, url, base::string16()); +} + +// static +scoped_refptr<ProxyResolverScriptData> +ProxyResolverScriptData::ForAutoDetect() { + return new ProxyResolverScriptData(TYPE_AUTO_DETECT, GURL(), + base::string16()); +} + +const base::string16& ProxyResolverScriptData::utf16() const { + DCHECK_EQ(TYPE_SCRIPT_CONTENTS, type_); + return utf16_; +} + +const GURL& ProxyResolverScriptData::url() const { + DCHECK_EQ(TYPE_SCRIPT_URL, type_); + return url_; +} + +bool ProxyResolverScriptData::Equals( + const ProxyResolverScriptData* other) const { + if (type() != other->type()) + return false; + + switch (type()) { + case TYPE_SCRIPT_CONTENTS: + return utf16() == other->utf16(); + case TYPE_SCRIPT_URL: + return url() == other->url(); + case TYPE_AUTO_DETECT: + return true; + } + + return false; // Shouldn't be reached. +} + +ProxyResolverScriptData::ProxyResolverScriptData(Type type, + const GURL& url, + const base::string16& utf16) + : type_(type), url_(url), utf16_(utf16) {} + +ProxyResolverScriptData::~ProxyResolverScriptData() = default; + +} // namespace net diff --git a/chromium/net/proxy_resolution/pac_file_data.h b/chromium/net/proxy_resolution/pac_file_data.h new file mode 100644 index 00000000000..88dc1ef0b8d --- /dev/null +++ b/chromium/net/proxy_resolution/pac_file_data.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 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_PAC_FILE_DATA_H_ +#define NET_PROXY_PAC_FILE_DATA_H_ + +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "net/base/net_export.h" +#include "url/gurl.h" + +namespace net { + +// Reference-counted wrapper for passing around a PAC script specification. +// The PAC script can be either specified via a URL, a deferred URL for +// auto-detect, or the actual javascript program text. +// +// This is thread-safe so it can be used by multi-threaded implementations of +// ProxyResolver to share the data between threads. +class NET_EXPORT_PRIVATE ProxyResolverScriptData + : public base::RefCountedThreadSafe<ProxyResolverScriptData> { + public: + enum Type { + TYPE_SCRIPT_CONTENTS, + TYPE_SCRIPT_URL, + TYPE_AUTO_DETECT, + }; + + // Creates a script data given the UTF8 bytes of the content. + static scoped_refptr<ProxyResolverScriptData> FromUTF8( + const std::string& utf8); + + // Creates a script data given the UTF16 bytes of the content. + static scoped_refptr<ProxyResolverScriptData> FromUTF16( + const base::string16& utf16); + + // Creates a script data given a URL to the PAC script. + static scoped_refptr<ProxyResolverScriptData> FromURL(const GURL& url); + + // Creates a script data for using an automatically detected PAC URL. + static scoped_refptr<ProxyResolverScriptData> ForAutoDetect(); + + Type type() const { return type_; } + + // Returns the contents of the script as UTF16. + // (only valid for type() == TYPE_SCRIPT_CONTENTS). + const base::string16& utf16() const; + + // Returns the URL of the script. + // (only valid for type() == TYPE_SCRIPT_URL). + const GURL& url() const; + + // Returns true if |this| matches |other|. + bool Equals(const ProxyResolverScriptData* other) const; + + private: + friend class base::RefCountedThreadSafe<ProxyResolverScriptData>; + ProxyResolverScriptData(Type type, + const GURL& url, + const base::string16& utf16); + virtual ~ProxyResolverScriptData(); + + const Type type_; + const GURL url_; + const base::string16 utf16_; +}; + +} // namespace net + +#endif // NET_PROXY_PAC_FILE_DATA_H_ diff --git a/chromium/net/proxy_resolution/pac_file_decider.cc b/chromium/net/proxy_resolution/pac_file_decider.cc new file mode 100644 index 00000000000..29e8413a3e2 --- /dev/null +++ b/chromium/net/proxy_resolution/pac_file_decider.cc @@ -0,0 +1,502 @@ +// Copyright (c) 2012 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_resolution/pac_file_decider.h" + +#include <utility> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "net/base/net_errors.h" +#include "net/log/net_log_capture_mode.h" +#include "net/log/net_log_event_type.h" +#include "net/log/net_log_source_type.h" +#include "net/proxy_resolution/dhcp_pac_file_fetcher.h" +#include "net/proxy_resolution/dhcp_pac_file_fetcher_factory.h" +#include "net/proxy_resolution/pac_file_fetcher.h" +#include "net/url_request/url_request_context.h" + +namespace net { + +namespace { + +bool LooksLikePacScript(const base::string16& script) { + // Note: this is only an approximation! It may not always work correctly, + // however it is very likely that legitimate scripts have this exact string, + // since they must minimally define a function of this name. Conversely, a + // file not containing the string is not likely to be a PAC script. + // + // An exact test would have to load the script in a javascript evaluator. + return script.find(base::ASCIIToUTF16("FindProxyForURL")) != + base::string16::npos; +} + +} // anonymous namespace + +// This is the hard-coded location used by the DNS portion of web proxy +// auto-discovery. +// +// Note that we not use DNS devolution to find the WPAD host, since that could +// be dangerous should our top level domain registry become out of date. +// +// Instead we directly resolve "wpad", and let the operating system apply the +// DNS suffix search paths. This is the same approach taken by Firefox, and +// compatibility hasn't been an issue. +// +// For more details, also check out this comment: +// http://code.google.com/p/chromium/issues/detail?id=18575#c20 +namespace { +const char kWpadUrl[] = "http://wpad/wpad.dat"; +const int kQuickCheckDelayMs = 1000; +}; // namespace + +std::unique_ptr<base::Value> ProxyScriptDecider::PacSource::NetLogCallback( + const GURL* effective_pac_url, + NetLogCaptureMode /* capture_mode */) const { + std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + std::string source; + switch (type) { + case PacSource::WPAD_DHCP: + source = "WPAD DHCP"; + break; + case PacSource::WPAD_DNS: + source = "WPAD DNS: "; + source += effective_pac_url->possibly_invalid_spec(); + break; + case PacSource::CUSTOM: + source = "Custom PAC URL: "; + source += effective_pac_url->possibly_invalid_spec(); + break; + } + dict->SetString("source", source); + return std::move(dict); +} + +ProxyScriptDecider::ProxyScriptDecider( + ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher, + NetLog* net_log) + : proxy_script_fetcher_(proxy_script_fetcher), + dhcp_proxy_script_fetcher_(dhcp_proxy_script_fetcher), + current_pac_source_index_(0u), + pac_mandatory_(false), + next_state_(STATE_NONE), + net_log_(NetLogWithSource::Make(net_log, + NetLogSourceType::PROXY_SCRIPT_DECIDER)), + fetch_pac_bytes_(false), + quick_check_enabled_(true) {} + +ProxyScriptDecider::~ProxyScriptDecider() { + if (next_state_ != STATE_NONE) + Cancel(); +} + +int ProxyScriptDecider::Start(const ProxyConfig& config, + const base::TimeDelta wait_delay, + bool fetch_pac_bytes, + const CompletionCallback& callback) { + DCHECK_EQ(STATE_NONE, next_state_); + DCHECK(!callback.is_null()); + DCHECK(config.HasAutomaticSettings()); + + net_log_.BeginEvent(NetLogEventType::PROXY_SCRIPT_DECIDER); + + fetch_pac_bytes_ = fetch_pac_bytes; + + // Save the |wait_delay| as a non-negative value. + wait_delay_ = wait_delay; + if (wait_delay_ < base::TimeDelta()) + wait_delay_ = base::TimeDelta(); + + pac_mandatory_ = config.pac_mandatory(); + have_custom_pac_url_ = config.has_pac_url(); + + pac_sources_ = BuildPacSourcesFallbackList(config); + DCHECK(!pac_sources_.empty()); + + next_state_ = STATE_WAIT; + + int rv = DoLoop(OK); + if (rv == ERR_IO_PENDING) + callback_ = callback; + else + DidComplete(); + + return rv; +} + +void ProxyScriptDecider::OnShutdown() { + // Don't do anything if idle. + if (next_state_ == STATE_NONE) + return; + + CompletionCallback callback = std::move(callback_); + + // Just cancel any pending work. + Cancel(); + + if (callback) + callback.Run(ERR_CONTEXT_SHUT_DOWN); +} + +const ProxyConfig& ProxyScriptDecider::effective_config() const { + DCHECK_EQ(STATE_NONE, next_state_); + return effective_config_; +} + +const scoped_refptr<ProxyResolverScriptData>& ProxyScriptDecider::script_data() + const { + DCHECK_EQ(STATE_NONE, next_state_); + return script_data_; +} + +// Initialize the fallback rules. +// (1) WPAD (DHCP). +// (2) WPAD (DNS). +// (3) Custom PAC URL. +ProxyScriptDecider::PacSourceList +ProxyScriptDecider::BuildPacSourcesFallbackList( + const ProxyConfig& config) const { + PacSourceList pac_sources; + if (config.auto_detect()) { + pac_sources.push_back(PacSource(PacSource::WPAD_DHCP, GURL(kWpadUrl))); + pac_sources.push_back(PacSource(PacSource::WPAD_DNS, GURL(kWpadUrl))); + } + if (config.has_pac_url()) + pac_sources.push_back(PacSource(PacSource::CUSTOM, config.pac_url())); + return pac_sources; +} + +void ProxyScriptDecider::OnIOCompletion(int result) { + DCHECK_NE(STATE_NONE, next_state_); + int rv = DoLoop(result); + if (rv != ERR_IO_PENDING) { + DidComplete(); + DoCallback(rv); + } +} + +int ProxyScriptDecider::DoLoop(int result) { + DCHECK_NE(next_state_, STATE_NONE); + int rv = result; + do { + State state = next_state_; + next_state_ = STATE_NONE; + switch (state) { + case STATE_WAIT: + DCHECK_EQ(OK, rv); + rv = DoWait(); + break; + case STATE_WAIT_COMPLETE: + rv = DoWaitComplete(rv); + break; + case STATE_QUICK_CHECK: + DCHECK_EQ(OK, rv); + rv = DoQuickCheck(); + break; + case STATE_QUICK_CHECK_COMPLETE: + rv = DoQuickCheckComplete(rv); + break; + case STATE_FETCH_PAC_SCRIPT: + DCHECK_EQ(OK, rv); + rv = DoFetchPacScript(); + break; + case STATE_FETCH_PAC_SCRIPT_COMPLETE: + rv = DoFetchPacScriptComplete(rv); + break; + case STATE_VERIFY_PAC_SCRIPT: + DCHECK_EQ(OK, rv); + rv = DoVerifyPacScript(); + break; + case STATE_VERIFY_PAC_SCRIPT_COMPLETE: + rv = DoVerifyPacScriptComplete(rv); + break; + default: + NOTREACHED() << "bad state"; + rv = ERR_UNEXPECTED; + break; + } + } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); + return rv; +} + +void ProxyScriptDecider::DoCallback(int result) { + DCHECK_NE(ERR_IO_PENDING, result); + DCHECK(!callback_.is_null()); + callback_.Run(result); +} + +int ProxyScriptDecider::DoWait() { + next_state_ = STATE_WAIT_COMPLETE; + + // If no waiting is required, continue on to the next state. + if (wait_delay_.ToInternalValue() == 0) + return OK; + + // Otherwise wait the specified amount of time. + wait_timer_.Start(FROM_HERE, wait_delay_, this, + &ProxyScriptDecider::OnWaitTimerFired); + net_log_.BeginEvent(NetLogEventType::PROXY_SCRIPT_DECIDER_WAIT); + return ERR_IO_PENDING; +} + +int ProxyScriptDecider::DoWaitComplete(int result) { + DCHECK_EQ(OK, result); + if (wait_delay_.ToInternalValue() != 0) { + net_log_.EndEventWithNetErrorCode( + NetLogEventType::PROXY_SCRIPT_DECIDER_WAIT, result); + } + if (quick_check_enabled_ && current_pac_source().type == PacSource::WPAD_DNS) + next_state_ = STATE_QUICK_CHECK; + else + next_state_ = GetStartState(); + return OK; +} + +int ProxyScriptDecider::DoQuickCheck() { + DCHECK(quick_check_enabled_); + if (!proxy_script_fetcher_ || !proxy_script_fetcher_->GetRequestContext() || + !proxy_script_fetcher_->GetRequestContext()->host_resolver()) { + // If we have no resolver, skip QuickCheck altogether. + next_state_ = GetStartState(); + return OK; + } + + quick_check_start_time_ = base::Time::Now(); + std::string host = current_pac_source().url.host(); + HostResolver::RequestInfo reqinfo(HostPortPair(host, 80)); + reqinfo.set_host_resolver_flags(HOST_RESOLVER_SYSTEM_ONLY); + CompletionCallback callback = + base::Bind(&ProxyScriptDecider::OnIOCompletion, base::Unretained(this)); + + next_state_ = STATE_QUICK_CHECK_COMPLETE; + quick_check_timer_.Start( + FROM_HERE, base::TimeDelta::FromMilliseconds(kQuickCheckDelayMs), + base::Bind(callback, ERR_NAME_NOT_RESOLVED)); + + HostResolver* host_resolver = + proxy_script_fetcher_->GetRequestContext()->host_resolver(); + + // We use HIGHEST here because proxy decision blocks doing any other requests. + return host_resolver->Resolve(reqinfo, HIGHEST, &wpad_addresses_, callback, + &request_, net_log_); +} + +int ProxyScriptDecider::DoQuickCheckComplete(int result) { + DCHECK(quick_check_enabled_); + base::TimeDelta delta = base::Time::Now() - quick_check_start_time_; + if (result == OK) + UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckSuccess", delta); + else + UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckFailure", delta); + request_.reset(); + quick_check_timer_.Stop(); + if (result != OK) + return TryToFallbackPacSource(result); + next_state_ = GetStartState(); + return result; +} + +int ProxyScriptDecider::DoFetchPacScript() { + DCHECK(fetch_pac_bytes_); + + next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE; + + const PacSource& pac_source = current_pac_source(); + + GURL effective_pac_url; + DetermineURL(pac_source, &effective_pac_url); + + net_log_.BeginEvent( + NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT, + base::Bind(&PacSource::NetLogCallback, base::Unretained(&pac_source), + &effective_pac_url)); + + if (pac_source.type == PacSource::WPAD_DHCP) { + if (!dhcp_proxy_script_fetcher_) { + net_log_.AddEvent(NetLogEventType::PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER); + return ERR_UNEXPECTED; + } + + return dhcp_proxy_script_fetcher_->Fetch( + &pac_script_, base::Bind(&ProxyScriptDecider::OnIOCompletion, + base::Unretained(this))); + } + + if (!proxy_script_fetcher_) { + net_log_.AddEvent(NetLogEventType::PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER); + return ERR_UNEXPECTED; + } + + return proxy_script_fetcher_->Fetch( + effective_pac_url, &pac_script_, + base::Bind(&ProxyScriptDecider::OnIOCompletion, base::Unretained(this))); +} + +int ProxyScriptDecider::DoFetchPacScriptComplete(int result) { + DCHECK(fetch_pac_bytes_); + + net_log_.EndEventWithNetErrorCode( + NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT, result); + if (result != OK) + return TryToFallbackPacSource(result); + + next_state_ = STATE_VERIFY_PAC_SCRIPT; + return result; +} + +int ProxyScriptDecider::DoVerifyPacScript() { + next_state_ = STATE_VERIFY_PAC_SCRIPT_COMPLETE; + + // This is just a heuristic. Ideally we would try to parse the script. + if (fetch_pac_bytes_ && !LooksLikePacScript(pac_script_)) + return ERR_PAC_SCRIPT_FAILED; + + return OK; +} + +int ProxyScriptDecider::DoVerifyPacScriptComplete(int result) { + if (result != OK) + return TryToFallbackPacSource(result); + + const PacSource& pac_source = current_pac_source(); + + // Extract the current script data. + if (fetch_pac_bytes_) { + script_data_ = ProxyResolverScriptData::FromUTF16(pac_script_); + } else { + script_data_ = pac_source.type == PacSource::CUSTOM + ? ProxyResolverScriptData::FromURL(pac_source.url) + : ProxyResolverScriptData::ForAutoDetect(); + } + + // Let the caller know which automatic setting we ended up initializing the + // resolver for (there may have been multiple fallbacks to choose from.) + if (current_pac_source().type == PacSource::CUSTOM) { + effective_config_ = + ProxyConfig::CreateFromCustomPacURL(current_pac_source().url); + effective_config_.set_pac_mandatory(pac_mandatory_); + } else { + if (fetch_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 OK; +} + +int ProxyScriptDecider::TryToFallbackPacSource(int error) { + DCHECK_LT(error, 0); + + 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_source_index_; + + net_log_.AddEvent( + NetLogEventType::PROXY_SCRIPT_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE); + if (quick_check_enabled_ && current_pac_source().type == PacSource::WPAD_DNS) + next_state_ = STATE_QUICK_CHECK; + else + next_state_ = GetStartState(); + + return OK; +} + +ProxyScriptDecider::State ProxyScriptDecider::GetStartState() const { + return fetch_pac_bytes_ ? STATE_FETCH_PAC_SCRIPT : STATE_VERIFY_PAC_SCRIPT; +} + +void ProxyScriptDecider::DetermineURL(const PacSource& pac_source, + GURL* effective_pac_url) { + DCHECK(effective_pac_url); + + switch (pac_source.type) { + case PacSource::WPAD_DHCP: + break; + case PacSource::WPAD_DNS: + *effective_pac_url = GURL(kWpadUrl); + break; + case PacSource::CUSTOM: + *effective_pac_url = pac_source.url; + break; + } +} + +const ProxyScriptDecider::PacSource& ProxyScriptDecider::current_pac_source() + const { + DCHECK_LT(current_pac_source_index_, pac_sources_.size()); + return pac_sources_[current_pac_source_index_]; +} + +void ProxyScriptDecider::OnWaitTimerFired() { + OnIOCompletion(OK); +} + +void ProxyScriptDecider::DidComplete() { + net_log_.EndEvent(NetLogEventType::PROXY_SCRIPT_DECIDER); +} + +void ProxyScriptDecider::Cancel() { + DCHECK_NE(STATE_NONE, next_state_); + + net_log_.AddEvent(NetLogEventType::CANCELLED); + + switch (next_state_) { + case STATE_QUICK_CHECK_COMPLETE: + request_.reset(); + break; + case STATE_WAIT_COMPLETE: + wait_timer_.Stop(); + break; + case STATE_FETCH_PAC_SCRIPT_COMPLETE: + proxy_script_fetcher_->Cancel(); + break; + default: + break; + } + + next_state_ = STATE_NONE; + + // This is safe to call in any state. + if (dhcp_proxy_script_fetcher_) + dhcp_proxy_script_fetcher_->Cancel(); + + DCHECK(!request_); + + DidComplete(); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/pac_file_decider.h b/chromium/net/proxy_resolution/pac_file_decider.h new file mode 100644 index 00000000000..82ec9f1983d --- /dev/null +++ b/chromium/net/proxy_resolution/pac_file_decider.h @@ -0,0 +1,211 @@ +// Copyright (c) 2012 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_PAC_FILE_DECIDER_H_ +#define NET_PROXY_PAC_FILE_DECIDER_H_ + +#include <stddef.h> + +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "net/base/address_list.h" +#include "net/base/completion_callback.h" +#include "net/base/net_export.h" +#include "net/dns/host_resolver.h" +#include "net/log/net_log_with_source.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_resolver.h" +#include "url/gurl.h" + +namespace base { +class Value; +} + +namespace net { + +class DhcpProxyScriptFetcher; +class NetLog; +class NetLogCaptureMode; +class ProxyResolver; +class ProxyScriptFetcher; + +// ProxyScriptDecider is a helper class used by ProxyResolutionService to +// determine which PAC script to use given our proxy configuration. +// +// This involves trying to use PAC scripts in this order: +// +// (1) WPAD (DHCP) if auto-detect is on. +// (2) WPAD (DNS) if auto-detect is on. +// (3) Custom PAC script if a URL was given. +// +// If no PAC script was successfully selected, then it fails with either a +// network error, or PAC_SCRIPT_FAILED (indicating it did not pass our +// validation). +// +// On successful completion, the fetched PAC script data can be accessed using +// script_data(). +// +// Deleting ProxyScriptDecider while Init() is in progress, will +// cancel the request. +// +class NET_EXPORT_PRIVATE ProxyScriptDecider { + public: + // |proxy_script_fetcher|, |dhcp_proxy_script_fetcher| and + // |net_log| must remain valid for the lifespan of ProxyScriptDecider. + ProxyScriptDecider(ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher, + NetLog* net_log); + + // Aborts any in-progress request. + ~ProxyScriptDecider(); + + // Evaluates the effective proxy settings for |config|, and downloads the + // associated PAC script. + // If |wait_delay| is positive, the initialization will pause for this + // amount of time before getting started. + // On successful completion, the "effective" proxy settings we ended up + // deciding on will be available vial the effective_settings() accessor. + // Note that this may differ from |config| since we will have stripped any + // manual settings, and decided whether to use auto-detect or the custom PAC + // URL. Finally, if auto-detect was used we may now have resolved that to a + // specific script URL. + int Start(const ProxyConfig& config, + const base::TimeDelta wait_delay, + bool fetch_pac_bytes, + const CompletionCallback& callback); + + // Shuts down any in-progress DNS requests, and cancels any ScriptFetcher + // requests. Does not call OnShutdown on the [Dhcp]ProxyScriptFetcher. + void OnShutdown(); + + const ProxyConfig& effective_config() const; + + const scoped_refptr<ProxyResolverScriptData>& script_data() const; + + void set_quick_check_enabled(bool enabled) { quick_check_enabled_ = enabled; } + + bool quick_check_enabled() const { return quick_check_enabled_; } + + private: + // 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) {} + + // Returns a Value representing the PacSource. |effective_pac_url| must + // be non-NULL and point to the URL derived from information contained in + // |this|, if Type is not WPAD_DHCP. + std::unique_ptr<base::Value> NetLogCallback( + const GURL* effective_pac_url, + NetLogCaptureMode capture_mode) const; + + Type type; + GURL url; // Empty unless |type == PAC_SOURCE_CUSTOM|. + }; + + typedef std::vector<PacSource> PacSourceList; + + enum State { + STATE_NONE, + STATE_WAIT, + STATE_WAIT_COMPLETE, + STATE_QUICK_CHECK, + STATE_QUICK_CHECK_COMPLETE, + STATE_FETCH_PAC_SCRIPT, + STATE_FETCH_PAC_SCRIPT_COMPLETE, + STATE_VERIFY_PAC_SCRIPT, + STATE_VERIFY_PAC_SCRIPT_COMPLETE, + }; + + // Returns ordered list of PAC urls to try for |config|. + PacSourceList BuildPacSourcesFallbackList(const ProxyConfig& config) const; + + void OnIOCompletion(int result); + int DoLoop(int result); + void DoCallback(int result); + + int DoWait(); + int DoWaitComplete(int result); + + int DoQuickCheck(); + int DoQuickCheckComplete(int result); + + int DoFetchPacScript(); + int DoFetchPacScriptComplete(int result); + + int DoVerifyPacScript(); + int DoVerifyPacScriptComplete(int result); + + // Tries restarting using the next fallback PAC URL: + // |pac_sources_[++current_pac_source_index]|. + // Returns OK and rewinds the state machine when there + // is something to try, otherwise returns |error|. + int TryToFallbackPacSource(int error); + + // Gets the initial state (we skip fetching when the + // ProxyResolver doesn't |expect_pac_bytes()|. + State GetStartState() const; + + void DetermineURL(const PacSource& pac_source, GURL* effective_pac_url); + + // Returns the current PAC URL we are fetching/testing. + const PacSource& current_pac_source() const; + + void OnWaitTimerFired(); + void DidComplete(); + void Cancel(); + + ProxyScriptFetcher* proxy_script_fetcher_; + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher_; + + CompletionCallback callback_; + + size_t current_pac_source_index_; + + // Filled when the PAC script fetch completes. + base::string16 pac_script_; + + // Flag indicating whether the caller requested a mandatory pac script + // (i.e. fallback to direct connections are prohibited). + bool pac_mandatory_; + + // Whether we have an existing custom PAC URL. + bool have_custom_pac_url_; + + PacSourceList pac_sources_; + State next_state_; + + NetLogWithSource net_log_; + + bool fetch_pac_bytes_; + + base::TimeDelta wait_delay_; + base::OneShotTimer wait_timer_; + + // Whether to do DNS quick check + bool quick_check_enabled_; + + // Results. + ProxyConfig effective_config_; + scoped_refptr<ProxyResolverScriptData> script_data_; + + AddressList wpad_addresses_; + base::OneShotTimer quick_check_timer_; + std::unique_ptr<HostResolver::Request> request_; + base::Time quick_check_start_time_; + + DISALLOW_COPY_AND_ASSIGN(ProxyScriptDecider); +}; + +} // namespace net + +#endif // NET_PROXY_PAC_FILE_DECIDER_H_ diff --git a/chromium/net/proxy_resolution/pac_file_decider_unittest.cc b/chromium/net/proxy_resolution/pac_file_decider_unittest.cc new file mode 100644 index 00000000000..4d7a392e0b7 --- /dev/null +++ b/chromium/net/proxy_resolution/pac_file_decider_unittest.cc @@ -0,0 +1,809 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <vector> + +#include "base/bind.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/dns/mock_host_resolver.h" +#include "net/log/net_log_event_type.h" +#include "net/log/test_net_log.h" +#include "net/log/test_net_log_entry.h" +#include "net/log/test_net_log_util.h" +#include "net/proxy_resolution/dhcp_pac_file_fetcher.h" +#include "net/proxy_resolution/mock_pac_file_fetcher.h" +#include "net/proxy_resolution/pac_file_decider.h" +#include "net/proxy_resolution/pac_file_fetcher.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_resolver.h" +#include "net/test/gtest_util.h" +#include "net/url_request/url_request_context.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using net::test::IsError; +using net::test::IsOk; + +namespace net { +namespace { + +enum Error { + kFailedDownloading = -100, + kFailedParsing = ERR_PAC_SCRIPT_FAILED, +}; + +class Rules { + public: + struct Rule { + Rule(const GURL& url, int fetch_error, bool is_valid_script) + : url(url), + fetch_error(fetch_error), + is_valid_script(is_valid_script) {} + + base::string16 text() const { + if (is_valid_script) + return base::UTF8ToUTF16(url.spec() + "!FindProxyForURL"); + if (fetch_error == OK) + return base::UTF8ToUTF16(url.spec() + "!invalid-script"); + return base::string16(); + } + + GURL url; + int fetch_error; + bool is_valid_script; + }; + + Rule AddSuccessRule(const char* url) { + Rule rule(GURL(url), OK /*fetch_error*/, true); + rules_.push_back(rule); + return rule; + } + + void AddFailDownloadRule(const char* url) { + rules_.push_back( + Rule(GURL(url), kFailedDownloading /*fetch_error*/, false)); + } + + void AddFailParsingRule(const char* url) { + rules_.push_back(Rule(GURL(url), OK /*fetch_error*/, false)); + } + + const Rule& GetRuleByUrl(const GURL& url) const { + for (RuleList::const_iterator it = rules_.begin(); it != rules_.end(); + ++it) { + if (it->url == url) + return *it; + } + LOG(FATAL) << "Rule not found for " << url; + return rules_[0]; + } + + const Rule& GetRuleByText(const base::string16& text) const { + for (RuleList::const_iterator it = rules_.begin(); it != rules_.end(); + ++it) { + if (it->text() == text) + return *it; + } + LOG(FATAL) << "Rule not found for " << text; + return rules_[0]; + } + + private: + typedef std::vector<Rule> RuleList; + RuleList rules_; +}; + +class RuleBasedProxyScriptFetcher : public ProxyScriptFetcher { + public: + explicit RuleBasedProxyScriptFetcher(const Rules* rules) + : rules_(rules), request_context_(NULL) {} + + virtual void SetRequestContext(URLRequestContext* context) { + request_context_ = context; + } + + // ProxyScriptFetcher implementation. + int Fetch(const GURL& url, + base::string16* text, + const CompletionCallback& callback) override { + const Rules::Rule& rule = rules_->GetRuleByUrl(url); + int rv = rule.fetch_error; + EXPECT_NE(ERR_UNEXPECTED, rv); + if (rv == OK) + *text = rule.text(); + return rv; + } + + void Cancel() override {} + + void OnShutdown() override { request_context_ = nullptr; } + + URLRequestContext* GetRequestContext() const override { + return request_context_; + } + + private: + const Rules* rules_; + URLRequestContext* request_context_; +}; + +// A mock retriever, returns asynchronously when CompleteRequests() is called. +class MockDhcpProxyScriptFetcher : public DhcpProxyScriptFetcher { + public: + MockDhcpProxyScriptFetcher(); + ~MockDhcpProxyScriptFetcher() override; + + int Fetch(base::string16* utf16_text, + const CompletionCallback& callback) override; + void Cancel() override; + void OnShutdown() override; + const GURL& GetPacURL() const override; + + virtual void SetPacURL(const GURL& url); + + virtual void CompleteRequests(int result, const base::string16& script); + + private: + CompletionCallback callback_; + base::string16* utf16_text_; + GURL gurl_; + DISALLOW_COPY_AND_ASSIGN(MockDhcpProxyScriptFetcher); +}; + +MockDhcpProxyScriptFetcher::MockDhcpProxyScriptFetcher() = default; + +MockDhcpProxyScriptFetcher::~MockDhcpProxyScriptFetcher() = default; + +int MockDhcpProxyScriptFetcher::Fetch(base::string16* utf16_text, + const CompletionCallback& callback) { + utf16_text_ = utf16_text; + callback_ = callback; + return ERR_IO_PENDING; +} + +void MockDhcpProxyScriptFetcher::Cancel() {} + +void MockDhcpProxyScriptFetcher::OnShutdown() {} + +const GURL& MockDhcpProxyScriptFetcher::GetPacURL() const { + return gurl_; +} + +void MockDhcpProxyScriptFetcher::SetPacURL(const GURL& url) { + gurl_ = url; +} + +void MockDhcpProxyScriptFetcher::CompleteRequests( + int result, + const base::string16& script) { + *utf16_text_ = script; + callback_.Run(result); +} + +// Succeed using custom PAC script. +TEST(ProxyScriptDeciderTest, CustomPacSucceeds) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; + + ProxyConfig config; + config.set_pac_url(GURL("http://custom/proxy.pac")); + + Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + TestNetLog log; + ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, &log); + EXPECT_EQ( + OK, decider.Start(config, base::TimeDelta(), true, callback.callback())); + EXPECT_EQ(rule.text(), decider.script_data()->utf16()); + + // Check the NetLog was filled correctly. + TestNetLogEntry::List entries; + log.GetEntries(&entries); + + EXPECT_EQ(4u, entries.size()); + EXPECT_TRUE( + LogContainsBeginEvent(entries, 0, NetLogEventType::PROXY_SCRIPT_DECIDER)); + EXPECT_TRUE(LogContainsBeginEvent( + entries, 1, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsEndEvent( + entries, 2, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE( + LogContainsEndEvent(entries, 3, NetLogEventType::PROXY_SCRIPT_DECIDER)); + + EXPECT_TRUE(decider.effective_config().has_pac_url()); + EXPECT_EQ(config.pac_url(), decider.effective_config().pac_url()); +} + +// Fail downloading the custom PAC script. +TEST(ProxyScriptDeciderTest, CustomPacFails1) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; + + ProxyConfig config; + config.set_pac_url(GURL("http://custom/proxy.pac")); + + rules.AddFailDownloadRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + TestNetLog log; + ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, &log); + EXPECT_EQ(kFailedDownloading, decider.Start(config, base::TimeDelta(), true, + callback.callback())); + EXPECT_FALSE(decider.script_data()); + + // Check the NetLog was filled correctly. + TestNetLogEntry::List entries; + log.GetEntries(&entries); + + EXPECT_EQ(4u, entries.size()); + EXPECT_TRUE( + LogContainsBeginEvent(entries, 0, NetLogEventType::PROXY_SCRIPT_DECIDER)); + EXPECT_TRUE(LogContainsBeginEvent( + entries, 1, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsEndEvent( + entries, 2, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE( + LogContainsEndEvent(entries, 3, NetLogEventType::PROXY_SCRIPT_DECIDER)); + + EXPECT_FALSE(decider.effective_config().has_pac_url()); +} + +// Fail parsing the custom PAC script. +TEST(ProxyScriptDeciderTest, CustomPacFails2) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; + + ProxyConfig config; + config.set_pac_url(GURL("http://custom/proxy.pac")); + + rules.AddFailParsingRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL); + EXPECT_EQ(kFailedParsing, decider.Start(config, base::TimeDelta(), true, + callback.callback())); + EXPECT_FALSE(decider.script_data()); +} + +// Fail downloading the custom PAC script, because the fetcher was NULL. +TEST(ProxyScriptDeciderTest, HasNullProxyScriptFetcher) { + Rules rules; + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; + + ProxyConfig config; + config.set_pac_url(GURL("http://custom/proxy.pac")); + + TestCompletionCallback callback; + ProxyScriptDecider decider(NULL, &dhcp_fetcher, NULL); + EXPECT_EQ(ERR_UNEXPECTED, decider.Start(config, base::TimeDelta(), true, + callback.callback())); + EXPECT_FALSE(decider.script_data()); +} + +// Succeeds in choosing autodetect (WPAD DNS). +TEST(ProxyScriptDeciderTest, AutodetectSuccess) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; + + ProxyConfig config; + config.set_auto_detect(true); + + Rules::Rule rule = rules.AddSuccessRule("http://wpad/wpad.dat"); + + TestCompletionCallback callback; + ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL); + EXPECT_EQ( + OK, decider.Start(config, base::TimeDelta(), true, callback.callback())); + EXPECT_EQ(rule.text(), decider.script_data()->utf16()); + + EXPECT_TRUE(decider.effective_config().has_pac_url()); + EXPECT_EQ(rule.url, decider.effective_config().pac_url()); +} + +class ProxyScriptDeciderQuickCheckTest : public ::testing::Test { + public: + ProxyScriptDeciderQuickCheckTest() + : rule_(rules_.AddSuccessRule("http://wpad/wpad.dat")), + fetcher_(&rules_) {} + + void SetUp() override { + request_context_.set_host_resolver(&resolver_); + fetcher_.SetRequestContext(&request_context_); + config_.set_auto_detect(true); + decider_.reset(new ProxyScriptDecider(&fetcher_, &dhcp_fetcher_, NULL)); + } + + int StartDecider() { + return decider_->Start(config_, base::TimeDelta(), true, + callback_.callback()); + } + + protected: + MockHostResolver resolver_; + Rules rules_; + Rules::Rule rule_; + TestCompletionCallback callback_; + RuleBasedProxyScriptFetcher fetcher_; + ProxyConfig config_; + DoNothingDhcpProxyScriptFetcher dhcp_fetcher_; + std::unique_ptr<ProxyScriptDecider> decider_; + + private: + URLRequestContext request_context_; +}; + +// Fails if a synchronous DNS lookup success for wpad causes QuickCheck to fail. +TEST_F(ProxyScriptDeciderQuickCheckTest, SyncSuccess) { + resolver_.set_synchronous_mode(true); + resolver_.rules()->AddRule("wpad", "1.2.3.4"); + + EXPECT_THAT(StartDecider(), IsOk()); + EXPECT_EQ(rule_.text(), decider_->script_data()->utf16()); + + EXPECT_TRUE(decider_->effective_config().has_pac_url()); + EXPECT_EQ(rule_.url, decider_->effective_config().pac_url()); +} + +// Fails if an asynchronous DNS lookup success for wpad causes QuickCheck to +// fail. +TEST_F(ProxyScriptDeciderQuickCheckTest, AsyncSuccess) { + resolver_.set_ondemand_mode(true); + resolver_.rules()->AddRule("wpad", "1.2.3.4"); + + EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING)); + ASSERT_TRUE(resolver_.has_pending_requests()); + resolver_.ResolveAllPending(); + callback_.WaitForResult(); + EXPECT_FALSE(resolver_.has_pending_requests()); + EXPECT_EQ(rule_.text(), decider_->script_data()->utf16()); + EXPECT_TRUE(decider_->effective_config().has_pac_url()); + EXPECT_EQ(rule_.url, decider_->effective_config().pac_url()); +} + +// Fails if an asynchronous DNS lookup failure (i.e. an NXDOMAIN) still causes +// ProxyScriptDecider to yield a PAC URL. +TEST_F(ProxyScriptDeciderQuickCheckTest, AsyncFail) { + resolver_.set_ondemand_mode(true); + resolver_.rules()->AddSimulatedFailure("wpad"); + EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING)); + ASSERT_TRUE(resolver_.has_pending_requests()); + resolver_.ResolveAllPending(); + callback_.WaitForResult(); + EXPECT_FALSE(decider_->effective_config().has_pac_url()); +} + +// Fails if a DNS lookup timeout either causes ProxyScriptDecider to yield a PAC +// URL or causes ProxyScriptDecider not to cancel its pending resolution. +TEST_F(ProxyScriptDeciderQuickCheckTest, AsyncTimeout) { + resolver_.set_ondemand_mode(true); + EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING)); + ASSERT_TRUE(resolver_.has_pending_requests()); + callback_.WaitForResult(); + EXPECT_FALSE(resolver_.has_pending_requests()); + EXPECT_FALSE(decider_->effective_config().has_pac_url()); +} + +// Fails if DHCP check doesn't take place before QuickCheck. +TEST_F(ProxyScriptDeciderQuickCheckTest, QuickCheckInhibitsDhcp) { + MockDhcpProxyScriptFetcher dhcp_fetcher; + const char* kPac = "function FindProxyForURL(u,h) { return \"DIRECT\"; }"; + base::string16 pac_contents = base::UTF8ToUTF16(kPac); + GURL url("http://foobar/baz"); + dhcp_fetcher.SetPacURL(url); + decider_.reset(new ProxyScriptDecider(&fetcher_, &dhcp_fetcher, NULL)); + EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING)); + dhcp_fetcher.CompleteRequests(OK, pac_contents); + EXPECT_TRUE(decider_->effective_config().has_pac_url()); + EXPECT_EQ(decider_->effective_config().pac_url(), url); +} + +// Fails if QuickCheck still happens when disabled. To ensure QuickCheck is not +// happening, we add a synchronous failing resolver, which would ordinarily +// mean a QuickCheck failure, then ensure that our ProxyScriptFetcher is still +// asked to fetch. +TEST_F(ProxyScriptDeciderQuickCheckTest, QuickCheckDisabled) { + const char* kPac = "function FindProxyForURL(u,h) { return \"DIRECT\"; }"; + resolver_.set_synchronous_mode(true); + resolver_.rules()->AddSimulatedFailure("wpad"); + MockProxyScriptFetcher fetcher; + decider_.reset(new ProxyScriptDecider(&fetcher, &dhcp_fetcher_, NULL)); + EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING)); + EXPECT_TRUE(fetcher.has_pending_request()); + fetcher.NotifyFetchCompletion(OK, kPac); +} + +TEST_F(ProxyScriptDeciderQuickCheckTest, ExplicitPacUrl) { + const char* kCustomUrl = "http://custom/proxy.pac"; + config_.set_pac_url(GURL(kCustomUrl)); + Rules::Rule rule = rules_.AddSuccessRule(kCustomUrl); + resolver_.rules()->AddSimulatedFailure("wpad"); + resolver_.rules()->AddRule("custom", "1.2.3.4"); + EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING)); + callback_.WaitForResult(); + EXPECT_TRUE(decider_->effective_config().has_pac_url()); + EXPECT_EQ(rule.url, decider_->effective_config().pac_url()); +} + +TEST_F(ProxyScriptDeciderQuickCheckTest, ShutdownDuringResolve) { + resolver_.set_ondemand_mode(true); + + EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING)); + EXPECT_TRUE(resolver_.has_pending_requests()); + + decider_->OnShutdown(); + EXPECT_FALSE(resolver_.has_pending_requests()); + EXPECT_EQ(ERR_CONTEXT_SHUT_DOWN, callback_.WaitForResult()); +} + +// Regression test for http://crbug.com/409698. +// This test lets the state machine get into state QUICK_CHECK_COMPLETE, then +// destroys the decider, causing a cancel. +TEST_F(ProxyScriptDeciderQuickCheckTest, CancelPartway) { + resolver_.set_synchronous_mode(false); + resolver_.set_ondemand_mode(true); + EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING)); + decider_.reset(NULL); +} + +// Fails at WPAD (downloading), but succeeds in choosing the custom PAC. +TEST(ProxyScriptDeciderTest, AutodetectFailCustomSuccess1) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; + + ProxyConfig config; + config.set_auto_detect(true); + config.set_pac_url(GURL("http://custom/proxy.pac")); + + rules.AddFailDownloadRule("http://wpad/wpad.dat"); + Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL); + EXPECT_EQ( + OK, decider.Start(config, base::TimeDelta(), true, callback.callback())); + EXPECT_EQ(rule.text(), decider.script_data()->utf16()); + + EXPECT_TRUE(decider.effective_config().has_pac_url()); + EXPECT_EQ(rule.url, decider.effective_config().pac_url()); +} + +// Fails at WPAD (no DHCP config, DNS PAC fails parsing), but succeeds in +// choosing the custom PAC. +TEST(ProxyScriptDeciderTest, AutodetectFailCustomSuccess2) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; + + ProxyConfig config; + config.set_auto_detect(true); + config.set_pac_url(GURL("http://custom/proxy.pac")); + config.proxy_rules().ParseFromString("unused-manual-proxy:99"); + + rules.AddFailParsingRule("http://wpad/wpad.dat"); + Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + TestNetLog log; + + ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, &log); + EXPECT_EQ( + OK, decider.Start(config, base::TimeDelta(), true, callback.callback())); + EXPECT_EQ(rule.text(), decider.script_data()->utf16()); + + // Verify that the effective configuration no longer contains auto detect or + // any of the manual settings. + EXPECT_TRUE(decider.effective_config().Equals( + ProxyConfig::CreateFromCustomPacURL(GURL("http://custom/proxy.pac")))); + + // Check the NetLog was filled correctly. + // (Note that various states are repeated since both WPAD and custom + // PAC scripts are tried). + TestNetLogEntry::List entries; + log.GetEntries(&entries); + + EXPECT_EQ(10u, entries.size()); + EXPECT_TRUE( + LogContainsBeginEvent(entries, 0, NetLogEventType::PROXY_SCRIPT_DECIDER)); + // 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, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsEndEvent( + entries, 2, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsEvent( + entries, 3, + NetLogEventType::PROXY_SCRIPT_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE, + NetLogEventPhase::NONE)); + // This is the DNS phase, which attempts a fetch but fails. + EXPECT_TRUE(LogContainsBeginEvent( + entries, 4, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsEndEvent( + entries, 5, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsEvent( + entries, 6, + NetLogEventType::PROXY_SCRIPT_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE, + NetLogEventPhase::NONE)); + // Finally, the custom PAC URL phase. + EXPECT_TRUE(LogContainsBeginEvent( + entries, 7, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsEndEvent( + entries, 8, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE( + LogContainsEndEvent(entries, 9, NetLogEventType::PROXY_SCRIPT_DECIDER)); +} + +// Fails at WPAD (downloading), and fails at custom PAC (downloading). +TEST(ProxyScriptDeciderTest, AutodetectFailCustomFails1) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; + + ProxyConfig config; + config.set_auto_detect(true); + config.set_pac_url(GURL("http://custom/proxy.pac")); + + rules.AddFailDownloadRule("http://wpad/wpad.dat"); + rules.AddFailDownloadRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL); + EXPECT_EQ(kFailedDownloading, decider.Start(config, base::TimeDelta(), true, + callback.callback())); + EXPECT_FALSE(decider.script_data()); +} + +// Fails at WPAD (downloading), and fails at custom PAC (parsing). +TEST(ProxyScriptDeciderTest, AutodetectFailCustomFails2) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; + + ProxyConfig config; + config.set_auto_detect(true); + config.set_pac_url(GURL("http://custom/proxy.pac")); + + rules.AddFailDownloadRule("http://wpad/wpad.dat"); + rules.AddFailParsingRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL); + EXPECT_EQ(kFailedParsing, decider.Start(config, base::TimeDelta(), true, + callback.callback())); + EXPECT_FALSE(decider.script_data()); +} + +// This is a copy-paste of CustomPacFails1, with the exception that we give it +// a 1 millisecond delay. This means it will now complete asynchronously. +// Moreover, we test the NetLog to make sure it logged the pause. +TEST(ProxyScriptDeciderTest, CustomPacFails1_WithPositiveDelay) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; + + ProxyConfig config; + config.set_pac_url(GURL("http://custom/proxy.pac")); + + rules.AddFailDownloadRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + TestNetLog log; + ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, &log); + EXPECT_EQ(ERR_IO_PENDING, + decider.Start(config, base::TimeDelta::FromMilliseconds(1), true, + callback.callback())); + + EXPECT_EQ(kFailedDownloading, callback.WaitForResult()); + EXPECT_FALSE(decider.script_data()); + + // Check the NetLog was filled correctly. + TestNetLogEntry::List entries; + log.GetEntries(&entries); + + EXPECT_EQ(6u, entries.size()); + EXPECT_TRUE( + LogContainsBeginEvent(entries, 0, NetLogEventType::PROXY_SCRIPT_DECIDER)); + EXPECT_TRUE(LogContainsBeginEvent( + entries, 1, NetLogEventType::PROXY_SCRIPT_DECIDER_WAIT)); + EXPECT_TRUE(LogContainsEndEvent(entries, 2, + NetLogEventType::PROXY_SCRIPT_DECIDER_WAIT)); + EXPECT_TRUE(LogContainsBeginEvent( + entries, 3, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsEndEvent( + entries, 4, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE( + LogContainsEndEvent(entries, 5, NetLogEventType::PROXY_SCRIPT_DECIDER)); +} + +// This is a copy-paste of CustomPacFails1, with the exception that we give it +// a -5 second delay instead of a 0 ms delay. This change should have no effect +// so the rest of the test is unchanged. +TEST(ProxyScriptDeciderTest, CustomPacFails1_WithNegativeDelay) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + DoNothingDhcpProxyScriptFetcher dhcp_fetcher; + + ProxyConfig config; + config.set_pac_url(GURL("http://custom/proxy.pac")); + + rules.AddFailDownloadRule("http://custom/proxy.pac"); + + TestCompletionCallback callback; + TestNetLog log; + ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, &log); + EXPECT_EQ(kFailedDownloading, + decider.Start(config, base::TimeDelta::FromSeconds(-5), true, + callback.callback())); + EXPECT_FALSE(decider.script_data()); + + // Check the NetLog was filled correctly. + TestNetLogEntry::List entries; + log.GetEntries(&entries); + + EXPECT_EQ(4u, entries.size()); + EXPECT_TRUE( + LogContainsBeginEvent(entries, 0, NetLogEventType::PROXY_SCRIPT_DECIDER)); + EXPECT_TRUE(LogContainsBeginEvent( + entries, 1, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE(LogContainsEndEvent( + entries, 2, NetLogEventType::PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)); + EXPECT_TRUE( + LogContainsEndEvent(entries, 3, NetLogEventType::PROXY_SCRIPT_DECIDER)); +} + +class SynchronousSuccessDhcpFetcher : public DhcpProxyScriptFetcher { + public: + explicit SynchronousSuccessDhcpFetcher(const base::string16& expected_text) + : gurl_("http://dhcppac/"), expected_text_(expected_text) {} + + int Fetch(base::string16* utf16_text, + const CompletionCallback& callback) override { + *utf16_text = expected_text_; + return OK; + } + + void Cancel() override {} + + void OnShutdown() override {} + + const GURL& GetPacURL() const override { return gurl_; } + + const base::string16& expected_text() const { return expected_text_; } + + private: + GURL gurl_; + base::string16 expected_text_; + + DISALLOW_COPY_AND_ASSIGN(SynchronousSuccessDhcpFetcher); +}; + +// All of the tests above that use ProxyScriptDecider 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(ProxyScriptDeciderTest, AutodetectDhcpSuccess) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + SynchronousSuccessDhcpFetcher dhcp_fetcher( + base::WideToUTF16(L"http://bingo/!FindProxyForURL")); + + ProxyConfig config; + config.set_auto_detect(true); + + rules.AddSuccessRule("http://bingo/"); + rules.AddFailDownloadRule("http://wpad/wpad.dat"); + + TestCompletionCallback callback; + ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL); + EXPECT_EQ( + OK, decider.Start(config, base::TimeDelta(), true, callback.callback())); + EXPECT_EQ(dhcp_fetcher.expected_text(), decider.script_data()->utf16()); + + EXPECT_TRUE(decider.effective_config().has_pac_url()); + EXPECT_EQ(GURL("http://dhcppac/"), decider.effective_config().pac_url()); +} + +TEST(ProxyScriptDeciderTest, AutodetectDhcpFailParse) { + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + SynchronousSuccessDhcpFetcher dhcp_fetcher( + base::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; + ProxyScriptDecider decider(&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, decider.Start(config, base::TimeDelta(), true, + callback.callback())); + EXPECT_FALSE(decider.script_data()); + + EXPECT_FALSE(decider.effective_config().has_pac_url()); +} + +class AsyncFailDhcpFetcher + : public DhcpProxyScriptFetcher, + public base::SupportsWeakPtr<AsyncFailDhcpFetcher> { + public: + AsyncFailDhcpFetcher() = default; + ~AsyncFailDhcpFetcher() override = default; + + int Fetch(base::string16* utf16_text, + const CompletionCallback& callback) override { + callback_ = callback; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&AsyncFailDhcpFetcher::CallbackWithFailure, AsWeakPtr())); + return ERR_IO_PENDING; + } + + void Cancel() override { callback_.Reset(); } + + void OnShutdown() override {} + + const GURL& GetPacURL() const override { return dummy_gurl_; } + + void CallbackWithFailure() { + if (!callback_.is_null()) + callback_.Run(ERR_PAC_NOT_IN_DHCP); + } + + private: + GURL dummy_gurl_; + CompletionCallback callback_; +}; + +TEST(ProxyScriptDeciderTest, DhcpCancelledByDestructor) { + // This regression test would crash before + // http://codereview.chromium.org/7044058/ + // Thus, we don't care much about actual results (hence no EXPECT or ASSERT + // macros below), just that it doesn't crash. + Rules rules; + RuleBasedProxyScriptFetcher fetcher(&rules); + + std::unique_ptr<AsyncFailDhcpFetcher> dhcp_fetcher( + new AsyncFailDhcpFetcher()); + + ProxyConfig config; + config.set_auto_detect(true); + rules.AddFailDownloadRule("http://wpad/wpad.dat"); + + TestCompletionCallback callback; + + // Scope so ProxyScriptDecider gets destroyed early. + { + ProxyScriptDecider decider(&fetcher, dhcp_fetcher.get(), NULL); + decider.Start(config, base::TimeDelta(), true, callback.callback()); + } + + // Run the message loop to let the DHCP fetch complete and post the results + // back. Before the fix linked to above, this would try to invoke on + // the callback object provided by ProxyScriptDecider after it was + // no longer valid. + base::RunLoop().RunUntilIdle(); +} + +} // namespace +} // namespace net diff --git a/chromium/net/proxy_resolution/pac_file_fetcher.h b/chromium/net/proxy_resolution/pac_file_fetcher.h new file mode 100644 index 00000000000..d19ed597031 --- /dev/null +++ b/chromium/net/proxy_resolution/pac_file_fetcher.h @@ -0,0 +1,67 @@ +// 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. + +// ProxyScriptFetcher is an async interface for fetching a proxy auto config +// script. It is specific to fetching a PAC script; enforces timeout, max-size, +// status code. + +#ifndef NET_PROXY_PAC_FILE_FETCHER_H_ +#define NET_PROXY_PAC_FILE_FETCHER_H_ + +#include "base/strings/string16.h" +#include "net/base/completion_callback.h" +#include "net/base/net_export.h" + +class GURL; + +namespace net { + +class URLRequestContext; + +// Interface for downloading a PAC script. Implementations can enforce +// timeouts, maximum size constraints, content encoding, etc.. +class NET_EXPORT_PRIVATE ProxyScriptFetcher { + public: + // Destruction should cancel any outstanding requests. + virtual ~ProxyScriptFetcher() {} + + // Downloads the given PAC URL, 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_TIMED_OUT -- the fetch took too long to complete. + // ERR_FILE_TOO_BIG -- the response's body was too large. + // ERR_PAC_STATUS_NOT_OK -- non-200 HTTP status code. + // ERR_NOT_IMPLEMENTED -- the response 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(const GURL& url, + base::string16* utf16_text, + const CompletionCallback& callback) = 0; + + // Aborts the in-progress fetch (if any). + virtual void Cancel() = 0; + + // Returns the request context that this fetcher uses to issue downloads, + // or NULL. + virtual URLRequestContext* GetRequestContext() const = 0; + + // Fails the in-progress fetch (if any) and future requests will fail + // immediately. GetRequestContext() will always return nullptr after this is + // called. Must be called before the URLRequestContext the fetcher was + // created with is torn down. + virtual void OnShutdown() = 0; +}; + +} // namespace net + +#endif // NET_PROXY_PAC_FILE_FETCHER_H_ diff --git a/chromium/net/proxy_resolution/pac_file_fetcher_impl.cc b/chromium/net/proxy_resolution/pac_file_fetcher_impl.cc new file mode 100644 index 00000000000..d0d107e6aa2 --- /dev/null +++ b/chromium/net/proxy_resolution/pac_file_fetcher_impl.cc @@ -0,0 +1,402 @@ +// Copyright (c) 2012 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_resolution/pac_file_fetcher_impl.h" + +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/metrics/histogram_macros.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_task_runner_handle.h" +#include "net/base/data_url.h" +#include "net/base/io_buffer.h" +#include "net/base/load_flags.h" +#include "net/base/net_errors.h" +#include "net/base/net_string_util.h" +#include "net/base/request_priority.h" +#include "net/cert/cert_status_flags.h" +#include "net/http/http_response_headers.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "net/url_request/url_request_context.h" + +// TODO(eroman): +// - Support auth-prompts (http://crbug.com/77366) + +namespace net { + +namespace { + +// The maximum size (in bytes) allowed for a PAC script. Responses exceeding +// this will fail with ERR_FILE_TOO_BIG. +const int kDefaultMaxResponseBytes = 1048576; // 1 megabyte + +// The maximum duration (in milliseconds) allowed for fetching the PAC script. +// Responses exceeding this will fail with ERR_TIMED_OUT. +// +// This timeout applies to both scripts fetched in the course of WPAD, as well +// as explicitly configured ones. +// +// If the default timeout is too high, auto-detect can stall for a long time, +// and if it is too low then slow loading scripts may be skipped. +// +// 30 seconds is a compromise between those competing goals. This value also +// appears to match Microsoft Edge (based on testing). +constexpr base::TimeDelta kDefaultMaxDuration = + base::TimeDelta::FromSeconds(30); + +// Returns true if |mime_type| is one of the known PAC mime type. +bool IsPacMimeType(const std::string& mime_type) { + static const char* const kSupportedPacMimeTypes[] = { + "application/x-ns-proxy-autoconfig", "application/x-javascript-config", + }; + for (size_t i = 0; i < arraysize(kSupportedPacMimeTypes); ++i) { + if (base::LowerCaseEqualsASCII(mime_type, kSupportedPacMimeTypes[i])) + return true; + } + return false; +} + +// Converts |bytes| (which is encoded by |charset|) to UTF16, saving the resul +// to |*utf16|. +// If |charset| is empty, then we don't know what it was and guess. +void ConvertResponseToUTF16(const std::string& charset, + const std::string& bytes, + base::string16* utf16) { + const char* codepage; + + if (charset.empty()) { + // Assume ISO-8859-1 if no charset was specified. + codepage = kCharsetLatin1; + } else { + // Otherwise trust the charset that was provided. + codepage = charset.c_str(); + } + + // Be generous in the conversion -- if any characters lie outside of |charset| + // (i.e. invalid), then substitute them with U+FFFD rather than failing. + ConvertToUTF16WithSubstitutions(bytes, codepage, utf16); +} + +} // namespace + +ProxyScriptFetcherImpl::ProxyScriptFetcherImpl( + URLRequestContext* url_request_context) + : url_request_context_(url_request_context), + buf_(new IOBuffer(kBufSize)), + next_id_(0), + cur_request_id_(0), + result_code_(OK), + result_text_(NULL), + max_response_bytes_(kDefaultMaxResponseBytes), + max_duration_(kDefaultMaxDuration), + weak_factory_(this) { + DCHECK(url_request_context); +} + +ProxyScriptFetcherImpl::~ProxyScriptFetcherImpl() { + // The URLRequest's destructor will cancel the outstanding request, and + // ensure that the delegate (this) is not called again. +} + +base::TimeDelta ProxyScriptFetcherImpl::SetTimeoutConstraint( + base::TimeDelta timeout) { + base::TimeDelta prev = max_duration_; + max_duration_ = timeout; + return prev; +} + +size_t ProxyScriptFetcherImpl::SetSizeConstraint(size_t size_bytes) { + size_t prev = max_response_bytes_; + max_response_bytes_ = size_bytes; + return prev; +} + +void ProxyScriptFetcherImpl::OnResponseCompleted(URLRequest* request, + int net_error) { + DCHECK_EQ(request, cur_request_.get()); + + // Use |result_code_| as the request's error if we have already set it to + // something specific. + if (result_code_ == OK && net_error != OK) + result_code_ = net_error; + + FetchCompleted(); +} + +int ProxyScriptFetcherImpl::Fetch(const GURL& url, + base::string16* text, + const CompletionCallback& callback) { + // It is invalid to call Fetch() while a request is already in progress. + DCHECK(!cur_request_.get()); + DCHECK(!callback.is_null()); + DCHECK(text); + + if (!url_request_context_) + return ERR_CONTEXT_SHUT_DOWN; + + // Handle base-64 encoded data-urls that contain custom PAC scripts. + if (url.SchemeIs("data")) { + std::string mime_type; + std::string charset; + std::string data; + if (!DataURL::Parse(url, &mime_type, &charset, &data)) + return ERR_FAILED; + + ConvertResponseToUTF16(charset, data, text); + return OK; + } + + DCHECK(fetch_start_time_.is_null()); + fetch_start_time_ = base::TimeTicks::Now(); + + net::NetworkTrafficAnnotationTag traffic_annotation = + net::DefineNetworkTrafficAnnotation("proxy_script_fetcher", R"( + semantics { + sender: "Proxy Service" + description: + "Fetches candidate URLs for proxy auto-config (PAC) scripts. This " + "may be carried out as part of the web proxy auto-discovery " + "protocol, or because an explicit PAC script is specified by the " + "proxy settings. The source of these URLs may be user-specified " + "(when part of proxy settings), or may be provided by the network " + "(DNS or DHCP based discovery). Note that a user may not be using " + "a proxy, but determining that (i.e. auto-detect) may cause these " + "fetches." + trigger: + "PAC URLs may be fetched on initial start, every time the network " + "changes, whenever the proxy settings change, or periodically on a " + "timer to check for changes." + data: "None." + destination: OTHER + } + policy { + cookies_allowed: YES + cookies_store: "user" + setting: + "This feature cannot be disabled by settings. This request is only " + "made if the effective proxy settings include either auto-detect, " + "or specify a PAC script." + policy_exception_justification: "Not implemented." + })"); + // Use highest priority, so if socket pools are being used for other types of + // requests, PAC requests are aren't blocked on them. + cur_request_ = url_request_context_->CreateRequest(url, MAXIMUM_PRIORITY, + this, traffic_annotation); + cur_request_->set_is_pac_request(true); + + // Make sure that the PAC script is downloaded using a direct connection, + // to avoid circular dependencies (fetching is a part of proxy resolution). + // Also disable the use of the disk cache. The cache is disabled so that if + // the user switches networks we don't potentially use the cached response + // from old network when we should in fact be re-fetching on the new network. + // If the PAC script is hosted on an HTTPS server we bypass revocation + // checking in order to avoid a circular dependency when attempting to fetch + // the OCSP response or CRL. We could make the revocation check go direct but + // the proxy might be the only way to the outside world. IGNORE_LIMITS is + // used to avoid blocking proxy resolution on other network requests. + cur_request_->SetLoadFlags(LOAD_BYPASS_PROXY | LOAD_DISABLE_CACHE | + LOAD_DISABLE_CERT_REVOCATION_CHECKING | + LOAD_IGNORE_LIMITS); + + // Save the caller's info for notification on completion. + callback_ = callback; + result_text_ = text; + + bytes_read_so_far_.clear(); + + // Post a task to timeout this request if it takes too long. + cur_request_id_ = ++next_id_; + + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::Bind(&ProxyScriptFetcherImpl::OnTimeout, weak_factory_.GetWeakPtr(), + cur_request_id_), + max_duration_); + + // Start the request. + cur_request_->Start(); + return ERR_IO_PENDING; +} + +void ProxyScriptFetcherImpl::Cancel() { + // ResetCurRequestState will free the URLRequest, which will cause + // cancellation. + ResetCurRequestState(); +} + +URLRequestContext* ProxyScriptFetcherImpl::GetRequestContext() const { + return url_request_context_; +} + +void ProxyScriptFetcherImpl::OnShutdown() { + url_request_context_ = nullptr; + + if (cur_request_) { + result_code_ = ERR_CONTEXT_SHUT_DOWN; + FetchCompleted(); + } +} + +void ProxyScriptFetcherImpl::OnAuthRequired(URLRequest* request, + AuthChallengeInfo* auth_info) { + DCHECK_EQ(request, cur_request_.get()); + // TODO(eroman): http://crbug.com/77366 + LOG(WARNING) << "Auth required to fetch PAC script, aborting."; + result_code_ = ERR_NOT_IMPLEMENTED; + request->CancelAuth(); +} + +void ProxyScriptFetcherImpl::OnSSLCertificateError(URLRequest* request, + const SSLInfo& ssl_info, + bool fatal) { + DCHECK_EQ(request, cur_request_.get()); + // Revocation check failures are not fatal. + if (IsCertStatusMinorError(ssl_info.cert_status)) { + request->ContinueDespiteLastError(); + return; + } + LOG(WARNING) << "SSL certificate error when fetching PAC script, aborting."; + // Certificate errors are in same space as net errors. + result_code_ = MapCertStatusToNetError(ssl_info.cert_status); + request->Cancel(); +} + +void ProxyScriptFetcherImpl::OnResponseStarted(URLRequest* request, + int net_error) { + DCHECK_EQ(request, cur_request_.get()); + DCHECK_NE(ERR_IO_PENDING, net_error); + + if (net_error != OK) { + OnResponseCompleted(request, net_error); + return; + } + + // Require HTTP responses to have a success status code. + if (request->url().SchemeIsHTTPOrHTTPS()) { + // NOTE about status codes: We are like Firefox 3 in this respect. + // {IE 7, Safari 3, Opera 9.5} do not care about the status code. + if (request->GetResponseCode() != 200) { + VLOG(1) << "Fetched PAC script had (bad) status line: " + << request->response_headers()->GetStatusLine(); + result_code_ = ERR_PAC_STATUS_NOT_OK; + request->Cancel(); + return; + } + + // NOTE about mime types: We do not enforce mime types on PAC files. + // This is for compatibility with {IE 7, Firefox 3, Opera 9.5}. We will + // however log mismatches to help with debugging. + std::string mime_type; + cur_request_->GetMimeType(&mime_type); + if (!IsPacMimeType(mime_type)) { + VLOG(1) << "Fetched PAC script does not have a proper mime type: " + << mime_type; + } + } + + ReadBody(request); +} + +void ProxyScriptFetcherImpl::OnReadCompleted(URLRequest* request, + int num_bytes) { + DCHECK_NE(ERR_IO_PENDING, num_bytes); + + DCHECK_EQ(request, cur_request_.get()); + if (ConsumeBytesRead(request, num_bytes)) { + // Keep reading. + ReadBody(request); + } +} + +void ProxyScriptFetcherImpl::ReadBody(URLRequest* request) { + // Read as many bytes as are available synchronously. + while (true) { + int num_bytes = request->Read(buf_.get(), kBufSize); + if (num_bytes == ERR_IO_PENDING) + return; + + if (num_bytes < 0) { + OnResponseCompleted(request, num_bytes); + return; + } + + if (!ConsumeBytesRead(request, num_bytes)) + return; + } +} + +bool ProxyScriptFetcherImpl::ConsumeBytesRead(URLRequest* request, + int num_bytes) { + if (fetch_time_to_first_byte_.is_null()) + fetch_time_to_first_byte_ = base::TimeTicks::Now(); + + if (num_bytes <= 0) { + // Error while reading, or EOF. + OnResponseCompleted(request, num_bytes); + return false; + } + + // Enforce maximum size bound. + if (num_bytes + bytes_read_so_far_.size() > + static_cast<size_t>(max_response_bytes_)) { + result_code_ = ERR_FILE_TOO_BIG; + request->Cancel(); + return false; + } + + bytes_read_so_far_.append(buf_->data(), num_bytes); + return true; +} + +void ProxyScriptFetcherImpl::FetchCompleted() { + if (result_code_ == OK) { + // Calculate duration of time for proxy script fetch to complete. + DCHECK(!fetch_start_time_.is_null()); + DCHECK(!fetch_time_to_first_byte_.is_null()); + UMA_HISTOGRAM_MEDIUM_TIMES("Net.ProxyScriptFetcher.SuccessDuration", + base::TimeTicks::Now() - fetch_start_time_); + UMA_HISTOGRAM_MEDIUM_TIMES("Net.ProxyScriptFetcher.FirstByteDuration", + fetch_time_to_first_byte_ - fetch_start_time_); + + // The caller expects the response to be encoded as UTF16. + std::string charset; + cur_request_->GetCharset(&charset); + ConvertResponseToUTF16(charset, bytes_read_so_far_, result_text_); + } else { + // On error, the caller expects empty string for bytes. + result_text_->clear(); + } + + int result_code = result_code_; + CompletionCallback callback = callback_; + + ResetCurRequestState(); + + callback.Run(result_code); +} + +void ProxyScriptFetcherImpl::ResetCurRequestState() { + cur_request_.reset(); + cur_request_id_ = 0; + callback_.Reset(); + result_code_ = OK; + result_text_ = NULL; + fetch_start_time_ = base::TimeTicks(); + fetch_time_to_first_byte_ = base::TimeTicks(); +} + +void ProxyScriptFetcherImpl::OnTimeout(int id) { + // Timeout tasks may outlive the URLRequest they reference. Make sure it + // is still applicable. + if (cur_request_id_ != id) + return; + + DCHECK(cur_request_.get()); + result_code_ = ERR_TIMED_OUT; + FetchCompleted(); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/pac_file_fetcher_impl.h b/chromium/net/proxy_resolution/pac_file_fetcher_impl.h new file mode 100644 index 00000000000..3cea326af60 --- /dev/null +++ b/chromium/net/proxy_resolution/pac_file_fetcher_impl.h @@ -0,0 +1,139 @@ +// Copyright (c) 2012 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_PAC_FILE_FETCHER_IMPL_H_ +#define NET_PROXY_PAC_FILE_FETCHER_IMPL_H_ + +#include <stddef.h> + +#include <memory> +#include <string> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "base/time/time.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/pac_file_fetcher.h" +#include "net/url_request/url_request.h" + +class GURL; + +namespace net { + +class URLRequestContext; + +// Implementation of ProxyScriptFetcher that downloads scripts using the +// specified request context. +class NET_EXPORT ProxyScriptFetcherImpl : public ProxyScriptFetcher, + public URLRequest::Delegate { + public: + // Creates a ProxyScriptFetcher that issues requests through + // |url_request_context|. |url_request_context| must remain valid for the + // lifetime of ProxyScriptFetcherImpl. + // Note that while a request is in progress, we will 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(). + explicit ProxyScriptFetcherImpl(URLRequestContext* url_request_context); + + ~ProxyScriptFetcherImpl() override; + + // Used by unit-tests to modify the default limits. + base::TimeDelta SetTimeoutConstraint(base::TimeDelta timeout); + size_t SetSizeConstraint(size_t size_bytes); + + void OnResponseCompleted(URLRequest* request, int net_error); + + // ProxyScriptFetcher methods: + int Fetch(const GURL& url, + base::string16* text, + const CompletionCallback& callback) override; + void Cancel() override; + URLRequestContext* GetRequestContext() const override; + void OnShutdown() override; + + // URLRequest::Delegate methods: + void OnAuthRequired(URLRequest* request, + AuthChallengeInfo* auth_info) override; + void OnSSLCertificateError(URLRequest* request, + const SSLInfo& ssl_info, + bool is_hsts_ok) override; + void OnResponseStarted(URLRequest* request, int net_error) override; + void OnReadCompleted(URLRequest* request, int num_bytes) override; + + private: + enum { kBufSize = 4096 }; + + // Read more bytes from the response. + void ReadBody(URLRequest* request); + + // Handles a response from Read(). Returns true if we should continue trying + // to read. |num_bytes| is 0 for EOF, and < 0 on errors. + bool ConsumeBytesRead(URLRequest* request, int num_bytes); + + // Called once the request has completed to notify the caller of + // |response_code_| and |response_text_|. + void FetchCompleted(); + + // Clear out the state for the current request. + void ResetCurRequestState(); + + // Callback for time-out task of request with id |id|. + void OnTimeout(int id); + + // The context used for making network requests. Set to nullptr by + // OnShutdown. + URLRequestContext* url_request_context_; + + // Buffer that URLRequest writes into. + scoped_refptr<IOBuffer> buf_; + + // The next ID to use for |cur_request_| (monotonically increasing). + int next_id_; + + // The current (in progress) request, or NULL. + std::unique_ptr<URLRequest> cur_request_; + + // State for current request (only valid when |cur_request_| is not NULL): + + // Unique ID for the current request. + int cur_request_id_; + + // Callback to invoke on completion of the fetch. + CompletionCallback callback_; + + // Holds the error condition that was hit on the current request, or OK. + int result_code_; + + // Holds the bytes read so far. Will not exceed |max_response_bytes|. + std::string bytes_read_so_far_; + + // This buffer is owned by the owner of |callback|, and will be filled with + // UTF16 response on completion. + base::string16* result_text_; + + // The maximum number of bytes to allow in responses. + size_t max_response_bytes_; + + // The maximum amount of time to wait for download to complete. + base::TimeDelta max_duration_; + + // The time that the fetch started. + base::TimeTicks fetch_start_time_; + + // The time that the first byte was received. + base::TimeTicks fetch_time_to_first_byte_; + + // Factory for creating the time-out task. This takes care of revoking + // outstanding tasks when |this| is deleted. + base::WeakPtrFactory<ProxyScriptFetcherImpl> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ProxyScriptFetcherImpl); +}; + +} // namespace net + +#endif // NET_PROXY_PAC_FILE_FETCHER_IMPL_H_ diff --git a/chromium/net/proxy_resolution/pac_file_fetcher_impl_unittest.cc b/chromium/net/proxy_resolution/pac_file_fetcher_impl_unittest.cc new file mode 100644 index 00000000000..a2c030e58a5 --- /dev/null +++ b/chromium/net/proxy_resolution/pac_file_fetcher_impl_unittest.cc @@ -0,0 +1,601 @@ +// Copyright (c) 2012 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_resolution/pac_file_fetcher_impl.h" + +#include <string> +#include <utility> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "net/base/filename_util.h" +#include "net/base/load_flags.h" +#include "net/base/network_delegate_impl.h" +#include "net/base/test_completion_callback.h" +#include "net/cert/ct_policy_enforcer.h" +#include "net/cert/mock_cert_verifier.h" +#include "net/cert/multi_log_ct_verifier.h" +#include "net/disk_cache/disk_cache.h" +#include "net/dns/mock_host_resolver.h" +#include "net/http/http_cache.h" +#include "net/http/http_network_session.h" +#include "net/http/http_server_properties_impl.h" +#include "net/http/http_transaction_factory.h" +#include "net/http/transport_security_state.h" +#include "net/net_features.h" +#include "net/socket/client_socket_pool_manager.h" +#include "net/socket/transport_client_socket_pool.h" +#include "net/ssl/ssl_config_service_defaults.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "net/test/embedded_test_server/simple_connection_listener.h" +#include "net/test/gtest_util.h" +#include "net/url_request/url_request_context_storage.h" +#include "net/url_request/url_request_file_job.h" +#include "net/url_request/url_request_job_factory_impl.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +#if !BUILDFLAG(DISABLE_FILE_SUPPORT) +#include "net/url_request/file_protocol_handler.h" +#endif + +using net::test::IsError; +using net::test::IsOk; + +using base::ASCIIToUTF16; + +// TODO(eroman): +// - Test canceling an outstanding request. +// - Test deleting ProxyScriptFetcher while a request is in progress. + +namespace net { + +namespace { + +const base::FilePath::CharType kDocRoot[] = + FILE_PATH_LITERAL("net/data/proxy_script_fetcher_unittest"); + +struct FetchResult { + int code; + base::string16 text; +}; + +// A non-mock URL request which can access http:// and file:// urls, in the case +// the tests were built with file support. +class RequestContext : public URLRequestContext { + public: + RequestContext() : storage_(this) { + ProxyConfig no_proxy; + storage_.set_host_resolver( + std::unique_ptr<HostResolver>(new MockHostResolver)); + storage_.set_cert_verifier(std::make_unique<MockCertVerifier>()); + storage_.set_transport_security_state( + std::make_unique<TransportSecurityState>()); + storage_.set_cert_transparency_verifier( + std::make_unique<MultiLogCTVerifier>()); + storage_.set_ct_policy_enforcer(std::make_unique<CTPolicyEnforcer>()); + storage_.set_proxy_resolution_service(ProxyResolutionService::CreateFixed(no_proxy)); + storage_.set_ssl_config_service(new SSLConfigServiceDefaults); + storage_.set_http_server_properties( + std::unique_ptr<HttpServerProperties>(new HttpServerPropertiesImpl())); + + HttpNetworkSession::Context session_context; + session_context.host_resolver = host_resolver(); + session_context.cert_verifier = cert_verifier(); + session_context.transport_security_state = transport_security_state(); + session_context.cert_transparency_verifier = cert_transparency_verifier(); + session_context.ct_policy_enforcer = ct_policy_enforcer(); + session_context.proxy_resolution_service = proxy_resolution_service(); + session_context.ssl_config_service = ssl_config_service(); + session_context.http_server_properties = http_server_properties(); + storage_.set_http_network_session(std::make_unique<HttpNetworkSession>( + HttpNetworkSession::Params(), session_context)); + storage_.set_http_transaction_factory(std::make_unique<HttpCache>( + storage_.http_network_session(), HttpCache::DefaultBackend::InMemory(0), + false)); + std::unique_ptr<URLRequestJobFactoryImpl> job_factory = + std::make_unique<URLRequestJobFactoryImpl>(); +#if !BUILDFLAG(DISABLE_FILE_SUPPORT) + job_factory->SetProtocolHandler("file", + std::make_unique<FileProtocolHandler>( + base::ThreadTaskRunnerHandle::Get())); +#endif + storage_.set_job_factory(std::move(job_factory)); + } + + ~RequestContext() override { AssertNoURLRequests(); } + + private: + URLRequestContextStorage storage_; +}; + +#if !BUILDFLAG(DISABLE_FILE_SUPPORT) +// Get a file:// url relative to net/data/proxy/proxy_script_fetcher_unittest. +GURL GetTestFileUrl(const std::string& relpath) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.AppendASCII("net"); + path = path.AppendASCII("data"); + path = path.AppendASCII("proxy_script_fetcher_unittest"); + GURL base_url = FilePathToFileURL(path); + return GURL(base_url.spec() + "/" + relpath); +} +#endif // !BUILDFLAG(DISABLE_FILE_SUPPORT) + +// Really simple NetworkDelegate so we can allow local file access on ChromeOS +// without introducing layering violations. Also causes a test failure if a +// request is seen that doesn't set a load flag to bypass revocation checking. + +class BasicNetworkDelegate : public NetworkDelegateImpl { + public: + BasicNetworkDelegate() = default; + ~BasicNetworkDelegate() override = default; + + private: + int OnBeforeURLRequest(URLRequest* request, + const CompletionCallback& callback, + GURL* new_url) override { + EXPECT_TRUE(request->load_flags() & LOAD_DISABLE_CERT_REVOCATION_CHECKING); + return OK; + } + + int OnBeforeStartTransaction(URLRequest* request, + const CompletionCallback& callback, + HttpRequestHeaders* headers) override { + return OK; + } + + void OnStartTransaction(URLRequest* request, + const HttpRequestHeaders& headers) override {} + + int OnHeadersReceived( + URLRequest* request, + const CompletionCallback& callback, + const HttpResponseHeaders* original_response_headers, + scoped_refptr<HttpResponseHeaders>* override_response_headers, + GURL* allowed_unsafe_redirect_url) override { + return OK; + } + + void OnBeforeRedirect(URLRequest* request, + const GURL& new_location) override {} + + void OnResponseStarted(URLRequest* request, int net_error) override {} + + void OnCompleted(URLRequest* request, bool started, int net_error) override {} + + void OnURLRequestDestroyed(URLRequest* request) override {} + + void OnPACScriptError(int line_number, const base::string16& error) override { + } + + NetworkDelegate::AuthRequiredResponse OnAuthRequired( + URLRequest* request, + const AuthChallengeInfo& auth_info, + const AuthCallback& callback, + AuthCredentials* credentials) override { + return NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION; + } + + bool OnCanGetCookies(const URLRequest& request, + const CookieList& cookie_list) override { + return true; + } + + bool OnCanSetCookie(const URLRequest& request, + const net::CanonicalCookie& cookie, + CookieOptions* options) override { + return true; + } + + bool OnCanAccessFile(const URLRequest& request, + const base::FilePath& original_path, + const base::FilePath& absolute_path) const override { + return true; + } + + DISALLOW_COPY_AND_ASSIGN(BasicNetworkDelegate); +}; + +class ProxyScriptFetcherImplTest : public PlatformTest { + public: + ProxyScriptFetcherImplTest() { + test_server_.AddDefaultHandlers(base::FilePath(kDocRoot)); + context_.set_network_delegate(&network_delegate_); + } + + protected: + EmbeddedTestServer test_server_; + BasicNetworkDelegate network_delegate_; + RequestContext context_; +}; + +#if !BUILDFLAG(DISABLE_FILE_SUPPORT) +TEST_F(ProxyScriptFetcherImplTest, FileUrl) { + ProxyScriptFetcherImpl pac_fetcher(&context_); + + { // Fetch a non-existent file. + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(GetTestFileUrl("does-not-exist"), &text, + callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsError(ERR_FILE_NOT_FOUND)); + EXPECT_TRUE(text.empty()); + } + { // Fetch a file that exists. + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(GetTestFileUrl("pac.txt"), &text, + callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(ASCIIToUTF16("-pac.txt-\n"), text); + } +} +#endif // !BUILDFLAG(DISABLE_FILE_SUPPORT) + +// Note that all mime types are allowed for PAC file, to be consistent +// with other browsers. +TEST_F(ProxyScriptFetcherImplTest, HttpMimeType) { + ASSERT_TRUE(test_server_.Start()); + + ProxyScriptFetcherImpl pac_fetcher(&context_); + + { // Fetch a PAC with mime type "text/plain" + GURL url(test_server_.GetURL("/pac.txt")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(ASCIIToUTF16("-pac.txt-\n"), text); + } + { // Fetch a PAC with mime type "text/html" + GURL url(test_server_.GetURL("/pac.html")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(ASCIIToUTF16("-pac.html-\n"), text); + } + { // Fetch a PAC with mime type "application/x-ns-proxy-autoconfig" + GURL url(test_server_.GetURL("/pac.nsproxy")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text); + } +} + +TEST_F(ProxyScriptFetcherImplTest, HttpStatusCode) { + ASSERT_TRUE(test_server_.Start()); + + ProxyScriptFetcherImpl pac_fetcher(&context_); + + { // Fetch a PAC which gives a 500 -- FAIL + GURL url(test_server_.GetURL("/500.pac")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsError(ERR_PAC_STATUS_NOT_OK)); + EXPECT_TRUE(text.empty()); + } + { // Fetch a PAC which gives a 404 -- FAIL + GURL url(test_server_.GetURL("/404.pac")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsError(ERR_PAC_STATUS_NOT_OK)); + EXPECT_TRUE(text.empty()); + } +} + +TEST_F(ProxyScriptFetcherImplTest, ContentDisposition) { + ASSERT_TRUE(test_server_.Start()); + + ProxyScriptFetcherImpl pac_fetcher(&context_); + + // Fetch PAC scripts via HTTP with a Content-Disposition header -- should + // have no effect. + GURL url(test_server_.GetURL("/downloadable.pac")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(ASCIIToUTF16("-downloadable.pac-\n"), text); +} + +// Verifies that PAC scripts are not being cached. +TEST_F(ProxyScriptFetcherImplTest, NoCache) { + ASSERT_TRUE(test_server_.Start()); + + ProxyScriptFetcherImpl pac_fetcher(&context_); + + // Fetch a PAC script whose HTTP headers make it cacheable for 1 hour. + GURL url(test_server_.GetURL("/cacheable_1hr.pac")); + { + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(ASCIIToUTF16("-cacheable_1hr.pac-\n"), text); + } + + // Kill the HTTP server. + ASSERT_TRUE(test_server_.ShutdownAndWaitUntilComplete()); + + // Try to fetch the file again. Since the server is not running anymore, the + // call should fail, thus indicating that the file was not fetched from the + // local cache. + { + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + + // Expect any error. The exact error varies by platform. + EXPECT_NE(OK, callback.WaitForResult()); + } +} + +TEST_F(ProxyScriptFetcherImplTest, TooLarge) { + ASSERT_TRUE(test_server_.Start()); + + ProxyScriptFetcherImpl pac_fetcher(&context_); + + // Set the maximum response size to 50 bytes. + int prev_size = pac_fetcher.SetSizeConstraint(50); + + // These two URLs are the same file, but are http:// vs file:// + GURL urls[] = { + test_server_.GetURL("/large-pac.nsproxy"), +#if !BUILDFLAG(DISABLE_FILE_SUPPORT) + GetTestFileUrl("large-pac.nsproxy") +#endif + }; + + // Try fetching URLs that are 101 bytes large. We should abort the request + // after 50 bytes have been read, and fail with a too large error. + for (size_t i = 0; i < arraysize(urls); ++i) { + const GURL& url = urls[i]; + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsError(ERR_FILE_TOO_BIG)); + EXPECT_TRUE(text.empty()); + } + + // Restore the original size bound. + pac_fetcher.SetSizeConstraint(prev_size); + + { // Make sure we can still fetch regular URLs. + GURL url(test_server_.GetURL("/pac.nsproxy")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text); + } +} + +// The ProxyScriptFetcher should be able to handle responses with an empty body. +TEST_F(ProxyScriptFetcherImplTest, Empty) { + ASSERT_TRUE(test_server_.Start()); + + ProxyScriptFetcherImpl pac_fetcher(&context_); + + GURL url(test_server_.GetURL("/empty")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(0u, text.size()); +} + +TEST_F(ProxyScriptFetcherImplTest, Hang) { + ASSERT_TRUE(test_server_.Start()); + + ProxyScriptFetcherImpl pac_fetcher(&context_); + + // Set the timeout period to 0.5 seconds. + base::TimeDelta prev_timeout = + pac_fetcher.SetTimeoutConstraint(base::TimeDelta::FromMilliseconds(500)); + + // Try fetching a URL which takes 1.2 seconds. We should abort the request + // after 500 ms, and fail with a timeout error. + { + GURL url(test_server_.GetURL("/slow/proxy.pac?1.2")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsError(ERR_TIMED_OUT)); + EXPECT_TRUE(text.empty()); + } + + // Restore the original timeout period. + pac_fetcher.SetTimeoutConstraint(prev_timeout); + + { // Make sure we can still fetch regular URLs. + GURL url(test_server_.GetURL("/pac.nsproxy")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text); + } +} + +// The ProxyScriptFetcher should decode any content-codings +// (like gzip, bzip, etc.), and apply any charset conversions to yield +// UTF8. +TEST_F(ProxyScriptFetcherImplTest, Encodings) { + ASSERT_TRUE(test_server_.Start()); + + ProxyScriptFetcherImpl pac_fetcher(&context_); + + // Test a response that is gzip-encoded -- should get inflated. + { + GURL url(test_server_.GetURL("/gzipped_pac")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(ASCIIToUTF16("This data was gzipped.\n"), text); + } + + // Test a response that was served as UTF-16 (BE). It should + // be converted to UTF8. + { + GURL url(test_server_.GetURL("/utf16be_pac")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(ASCIIToUTF16("This was encoded as UTF-16BE.\n"), text); + } +} + +TEST_F(ProxyScriptFetcherImplTest, DataURLs) { + ProxyScriptFetcherImpl pac_fetcher(&context_); + + const char kEncodedUrl[] = + "data:application/x-ns-proxy-autoconfig;base64,ZnVuY3Rpb24gRmluZFByb3h5R" + "m9yVVJMKHVybCwgaG9zdCkgewogIGlmIChob3N0ID09ICdmb29iYXIuY29tJykKICAgIHJl" + "dHVybiAnUFJPWFkgYmxhY2tob2xlOjgwJzsKICByZXR1cm4gJ0RJUkVDVCc7Cn0="; + const char kPacScript[] = + "function FindProxyForURL(url, host) {\n" + " if (host == 'foobar.com')\n" + " return 'PROXY blackhole:80';\n" + " return 'DIRECT';\n" + "}"; + + // Test fetching a "data:"-url containing a base64 encoded PAC script. + { + GURL url(kEncodedUrl); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsOk()); + EXPECT_EQ(ASCIIToUTF16(kPacScript), text); + } + + const char kEncodedUrlBroken[] = + "data:application/x-ns-proxy-autoconfig;base64,ZnVuY3Rpb24gRmluZFByb3h5R"; + + // Test a broken "data:"-url containing a base64 encoded PAC script. + { + GURL url(kEncodedUrlBroken); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_FAILED)); + } +} + +// Makes sure that a request gets through when the socket pool is full, so +// ProxyScriptFetcherImpl can use the same URLRequestContext as everything else. +TEST_F(ProxyScriptFetcherImplTest, Priority) { + // Enough requests to exceed the per-pool limit, which is also enough to + // exceed the per-group limit. + int num_requests = 10 + ClientSocketPoolManager::max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL); + + net::test_server::SimpleConnectionListener connection_listener( + num_requests, net::test_server::SimpleConnectionListener:: + FAIL_ON_ADDITIONAL_CONNECTIONS); + test_server_.SetConnectionListener(&connection_listener); + ASSERT_TRUE(test_server_.Start()); + + std::vector<std::unique_ptr<ProxyScriptFetcherImpl>> pac_fetchers; + + TestCompletionCallback callback; + base::string16 text; + for (int i = 0; i < num_requests; i++) { + std::unique_ptr<ProxyScriptFetcherImpl> pac_fetcher = + std::make_unique<ProxyScriptFetcherImpl>(&context_); + GURL url(test_server_.GetURL("/hung")); + // Fine to use the same string and callback for all of these, as they should + // all hang. + int result = pac_fetcher->Fetch(url, &text, callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + pac_fetchers.push_back(std::move(pac_fetcher)); + } + + connection_listener.WaitForConnections(); + // None of the callbacks should have been invoked - all jobs should still be + // hung. + EXPECT_FALSE(callback.have_result()); + + // Need to shut down the server before |connection_listener| is destroyed. + EXPECT_TRUE(test_server_.ShutdownAndWaitUntilComplete()); +} + +TEST_F(ProxyScriptFetcherImplTest, OnShutdown) { + ASSERT_TRUE(test_server_.Start()); + + ProxyScriptFetcherImpl pac_fetcher(&context_); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(test_server_.GetURL("/hung"), &text, + callback.callback()); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_EQ(1u, context_.url_requests()->size()); + + pac_fetcher.OnShutdown(); + EXPECT_EQ(0u, context_.url_requests()->size()); + EXPECT_THAT(callback.WaitForResult(), IsError(ERR_CONTEXT_SHUT_DOWN)); + + // Make sure there's no asynchronous completion notification. + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0u, context_.url_requests()->size()); + EXPECT_FALSE(callback.have_result()); + + result = pac_fetcher.Fetch(test_server_.GetURL("/hung"), &text, + callback.callback()); + EXPECT_THAT(result, IsError(ERR_CONTEXT_SHUT_DOWN)); +} + +TEST_F(ProxyScriptFetcherImplTest, OnShutdownWithNoLiveRequest) { + ASSERT_TRUE(test_server_.Start()); + + ProxyScriptFetcherImpl pac_fetcher(&context_); + pac_fetcher.OnShutdown(); + + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher.Fetch(test_server_.GetURL("/hung"), &text, + callback.callback()); + EXPECT_THAT(result, IsError(ERR_CONTEXT_SHUT_DOWN)); + EXPECT_EQ(0u, context_.url_requests()->size()); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/proxy_resolution/pac_js_library.h b/chromium/net/proxy_resolution/pac_js_library.h new file mode 100644 index 00000000000..c99c348352f --- /dev/null +++ b/chromium/net/proxy_resolution/pac_js_library.h @@ -0,0 +1,282 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_PROXY_RESOLUTION_PAC_JS_LIBRARY_H_ +#define NET_PROXY_RESOLUTION_PAC_JS_LIBRARY_H_ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Akhil Arora <akhil.arora@sun.com> + * Tomi Leppikangas <Tomi.Leppikangas@oulu.fi> + * Darin Fisher <darin@meer.net> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// The following code was formatted from: +// 'mozilla/netwerk/base/src/nsProxyAutoConfig.js' (1.55) +// +// Using the command: +// $ cat nsProxyAutoConfig.js | +// awk '/var pacUtils/,/EOF/' | +// sed -e 's/^\s*$/""/g' | +// sed -e 's/"\s*[+]\s*$/"/g' | +// sed -e 's/"$/" \\/g' | +// sed -e 's/\/(ipaddr);/\/.exec(ipaddr);/g' | +// grep -v '^var pacUtils =' +// +// isPlainHost() was removed. +#define PAC_JS_LIBRARY \ + "function dnsDomainIs(host, domain) {\n" \ + " return (host.length >= domain.length &&\n" \ + " host.substring(host.length - domain.length) == domain);\n" \ + "}\n" \ + "" \ + "function dnsDomainLevels(host) {\n" \ + " return host.split('.').length-1;\n" \ + "}\n" \ + "" \ + "function convert_addr(ipchars) {\n" \ + " var bytes = ipchars.split('.');\n" \ + " var result = ((bytes[0] & 0xff) << 24) |\n" \ + " ((bytes[1] & 0xff) << 16) |\n" \ + " ((bytes[2] & 0xff) << 8) |\n" \ + " (bytes[3] & 0xff);\n" \ + " return result;\n" \ + "}\n" \ + "" \ + "function isInNet(ipaddr, pattern, maskstr) {\n" \ + " var test = " \ + "/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n" \ + " if (test == null) {\n" \ + " ipaddr = dnsResolve(ipaddr);\n" \ + " if (ipaddr == null)\n" \ + " return false;\n" \ + " } else if (test[1] > 255 || test[2] > 255 || \n" \ + " test[3] > 255 || test[4] > 255) {\n" \ + " return false; // not an IP address\n" \ + " }\n" \ + " var host = convert_addr(ipaddr);\n" \ + " var pat = convert_addr(pattern);\n" \ + " var mask = convert_addr(maskstr);\n" \ + " return ((host & mask) == (pat & mask));\n" \ + " \n" \ + "}\n" \ + "" \ + "function isResolvable(host) {\n" \ + " var ip = dnsResolve(host);\n" \ + " return (ip != null);\n" \ + "}\n" \ + "" \ + "function localHostOrDomainIs(host, hostdom) {\n" \ + " return (host == hostdom) ||\n" \ + " (hostdom.lastIndexOf(host + '.', 0) == 0);\n" \ + "}\n" \ + "" \ + "function shExpMatch(url, pattern) {\n" \ + " pattern = pattern.replace(/\\./g, '\\\\.');\n" \ + " pattern = pattern.replace(/\\*/g, '.*');\n" \ + " pattern = pattern.replace(/\\?/g, '.');\n" \ + " var newRe = new RegExp('^'+pattern+'$');\n" \ + " return newRe.test(url);\n" \ + "}\n" \ + "" \ + "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n" \ + "" \ + "var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, " \ + "AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n" \ + "" \ + "function weekdayRange() {\n" \ + " function getDay(weekday) {\n" \ + " if (weekday in wdays) {\n" \ + " return wdays[weekday];\n" \ + " }\n" \ + " return -1;\n" \ + " }\n" \ + " var date = new Date();\n" \ + " var argc = arguments.length;\n" \ + " var wday;\n" \ + " if (argc < 1)\n" \ + " return false;\n" \ + " if (arguments[argc - 1] == 'GMT') {\n" \ + " argc--;\n" \ + " wday = date.getUTCDay();\n" \ + " } else {\n" \ + " wday = date.getDay();\n" \ + " }\n" \ + " var wd1 = getDay(arguments[0]);\n" \ + " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" \ + " return (wd1 == -1 || wd2 == -1) ? false\n" \ + " : (wd1 <= wday && wday <= wd2);\n" \ + "}\n" \ + "" \ + "function dateRange() {\n" \ + " function getMonth(name) {\n" \ + " if (name in months) {\n" \ + " return months[name];\n" \ + " }\n" \ + " return -1;\n" \ + " }\n" \ + " var date = new Date();\n" \ + " var argc = arguments.length;\n" \ + " if (argc < 1) {\n" \ + " return false;\n" \ + " }\n" \ + " var isGMT = (arguments[argc - 1] == 'GMT');\n" \ + "\n" \ + " if (isGMT) {\n" \ + " argc--;\n" \ + " }\n" \ + " // function will work even without explict handling of this case\n" \ + " if (argc == 1) {\n" \ + " var tmp = parseInt(arguments[0]);\n" \ + " if (isNaN(tmp)) {\n" \ + " return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n" \ + "getMonth(arguments[0]));\n" \ + " } else if (tmp < 32) {\n" \ + " return ((isGMT ? date.getUTCDate() : date.getDate()) == " \ + "tmp);\n" \ + " } else { \n" \ + " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) " \ + "==\n" \ + "tmp);\n" \ + " }\n" \ + " }\n" \ + " var year = date.getFullYear();\n" \ + " var date1, date2;\n" \ + " date1 = new Date(year, 0, 1, 0, 0, 0);\n" \ + " date2 = new Date(year, 11, 31, 23, 59, 59);\n" \ + " var adjustMonth = false;\n" \ + " for (var i = 0; i < (argc >> 1); i++) {\n" \ + " var tmp = parseInt(arguments[i]);\n" \ + " if (isNaN(tmp)) {\n" \ + " var mon = getMonth(arguments[i]);\n" \ + " date1.setMonth(mon);\n" \ + " } else if (tmp < 32) {\n" \ + " adjustMonth = (argc <= 2);\n" \ + " date1.setDate(tmp);\n" \ + " } else {\n" \ + " date1.setFullYear(tmp);\n" \ + " }\n" \ + " }\n" \ + " for (var i = (argc >> 1); i < argc; i++) {\n" \ + " var tmp = parseInt(arguments[i]);\n" \ + " if (isNaN(tmp)) {\n" \ + " var mon = getMonth(arguments[i]);\n" \ + " date2.setMonth(mon);\n" \ + " } else if (tmp < 32) {\n" \ + " date2.setDate(tmp);\n" \ + " } else {\n" \ + " date2.setFullYear(tmp);\n" \ + " }\n" \ + " }\n" \ + " if (adjustMonth) {\n" \ + " date1.setMonth(date.getMonth());\n" \ + " date2.setMonth(date.getMonth());\n" \ + " }\n" \ + " if (isGMT) {\n" \ + " var tmp = date;\n" \ + " tmp.setFullYear(date.getUTCFullYear());\n" \ + " tmp.setMonth(date.getUTCMonth());\n" \ + " tmp.setDate(date.getUTCDate());\n" \ + " tmp.setHours(date.getUTCHours());\n" \ + " tmp.setMinutes(date.getUTCMinutes());\n" \ + " tmp.setSeconds(date.getUTCSeconds());\n" \ + " date = tmp;\n" \ + " }\n" \ + " return ((date1 <= date) && (date <= date2));\n" \ + "}\n" \ + "" \ + "function timeRange() {\n" \ + " var argc = arguments.length;\n" \ + " var date = new Date();\n" \ + " var isGMT= false;\n" \ + "\n" \ + " if (argc < 1) {\n" \ + " return false;\n" \ + " }\n" \ + " if (arguments[argc - 1] == 'GMT') {\n" \ + " isGMT = true;\n" \ + " argc--;\n" \ + " }\n" \ + "\n" \ + " var hour = isGMT ? date.getUTCHours() : date.getHours();\n" \ + " var date1, date2;\n" \ + " date1 = new Date();\n" \ + " date2 = new Date();\n" \ + "\n" \ + " if (argc == 1) {\n" \ + " return (hour == arguments[0]);\n" \ + " } else if (argc == 2) {\n" \ + " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n" \ + " } else {\n" \ + " switch (argc) {\n" \ + " case 6:\n" \ + " date1.setSeconds(arguments[2]);\n" \ + " date2.setSeconds(arguments[5]);\n" \ + " case 4:\n" \ + " var middle = argc >> 1;\n" \ + " date1.setHours(arguments[0]);\n" \ + " date1.setMinutes(arguments[1]);\n" \ + " date2.setHours(arguments[middle]);\n" \ + " date2.setMinutes(arguments[middle + 1]);\n" \ + " if (middle == 2) {\n" \ + " date2.setSeconds(59);\n" \ + " }\n" \ + " break;\n" \ + " default:\n" \ + " throw 'timeRange: bad number of arguments'\n" \ + " }\n" \ + " }\n" \ + "\n" \ + " if (isGMT) {\n" \ + " date.setFullYear(date.getUTCFullYear());\n" \ + " date.setMonth(date.getUTCMonth());\n" \ + " date.setDate(date.getUTCDate());\n" \ + " date.setHours(date.getUTCHours());\n" \ + " date.setMinutes(date.getUTCMinutes());\n" \ + " date.setSeconds(date.getUTCSeconds());\n" \ + " }\n" \ + " return ((date1 <= date) && (date <= date2));\n" \ + "}\n" + +// This is a Microsoft extension to PAC for IPv6, see: +// http://blogs.msdn.com/b/wndp/archive/2006/07/13/ipv6-pac-extensions-v0-9.aspx +#define PAC_JS_LIBRARY_EX \ + "function isResolvableEx(host) {\n" \ + " var ipList = dnsResolveEx(host);\n" \ + " return (ipList != '');\n" \ + "}\n" + +#endif // NET_PROXY_RESOLUTION_PAC_JS_LIBRARY_H_ diff --git a/chromium/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc b/chromium/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc new file mode 100644 index 00000000000..100e818b702 --- /dev/null +++ b/chromium/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc @@ -0,0 +1,24 @@ +// Copyright 2016 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 <stddef.h> +#include <stdint.h> + +#include "net/proxy_resolution/proxy_bypass_rules.h" + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // Don't waste time parsing if the input is too large + // (https://crbug.com/813619). According to + // //testing/libfuzzer/efficient_fuzzer.md setting max_len in the build + // target is insufficient since AFL doesn't respect it. + if (size > 512) + return 0; + + net::ProxyBypassRules rules; + std::string input(data, data + size); + rules.ParseFromString(input); + rules.ParseFromStringUsingSuffixMatching(input); + return 0; +} diff --git a/chromium/net/proxy_resolution/parse_proxy_list_fuzzer.cc b/chromium/net/proxy_resolution/parse_proxy_list_fuzzer.cc new file mode 100644 index 00000000000..2e2731c547b --- /dev/null +++ b/chromium/net/proxy_resolution/parse_proxy_list_fuzzer.cc @@ -0,0 +1,16 @@ +// Copyright 2016 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 <stddef.h> +#include <stdint.h> + +#include "net/proxy_resolution/proxy_list.h" + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + net::ProxyList list; + std::string input(data, data + size); + list.Set(input); + return 0; +} diff --git a/chromium/net/proxy_resolution/parse_proxy_list_pac_fuzzer.cc b/chromium/net/proxy_resolution/parse_proxy_list_pac_fuzzer.cc new file mode 100644 index 00000000000..370a647b6e0 --- /dev/null +++ b/chromium/net/proxy_resolution/parse_proxy_list_pac_fuzzer.cc @@ -0,0 +1,16 @@ +// Copyright 2016 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 <stddef.h> +#include <stdint.h> + +#include "net/proxy_resolution/proxy_list.h" + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + net::ProxyList list; + std::string input(data, data + size); + list.SetFromPacString(input); + return 0; +} diff --git a/chromium/net/proxy_resolution/parse_proxy_rules_fuzzer.cc b/chromium/net/proxy_resolution/parse_proxy_rules_fuzzer.cc new file mode 100644 index 00000000000..19431c2732d --- /dev/null +++ b/chromium/net/proxy_resolution/parse_proxy_rules_fuzzer.cc @@ -0,0 +1,16 @@ +// Copyright 2016 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 <stddef.h> +#include <stdint.h> + +#include "net/proxy_resolution/proxy_config.h" + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + net::ProxyConfig::ProxyRules rules; + std::string input(data, data + size); + rules.ParseFromString(input); + return 0; +} diff --git a/chromium/net/proxy_resolution/polling_proxy_config_service.cc b/chromium/net/proxy_resolution/polling_proxy_config_service.cc new file mode 100644 index 00000000000..f54dd4de1d6 --- /dev/null +++ b/chromium/net/proxy_resolution/polling_proxy_config_service.cc @@ -0,0 +1,194 @@ +// Copyright (c) 2012 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_resolution/polling_proxy_config_service.h" + +#include <memory> + +#include "base/bind.h" +#include "base/location.h" +#include "base/observer_list.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/task_scheduler/post_task.h" +#include "base/threading/thread_task_runner_handle.h" +#include "net/proxy_resolution/proxy_config.h" + +namespace net { + +// Reference-counted wrapper that does all the work (needs to be +// reference-counted since we post tasks between threads; may outlive +// the parent PollingProxyConfigService). +class PollingProxyConfigService::Core + : public base::RefCountedThreadSafe<PollingProxyConfigService::Core> { + public: + Core(base::TimeDelta poll_interval, GetConfigFunction get_config_func) + : get_config_func_(get_config_func), + poll_interval_(poll_interval), + have_initialized_origin_runner_(false), + has_config_(false), + poll_task_outstanding_(false), + poll_task_queued_(false) {} + + // Called when the parent PollingProxyConfigService is destroyed + // (observers should not be called past this point). + void Orphan() { + base::AutoLock lock(lock_); + origin_task_runner_ = NULL; + } + + bool GetLatestProxyConfig(ProxyConfig* config) { + LazyInitializeOriginLoop(); + DCHECK(origin_task_runner_->BelongsToCurrentThread()); + + OnLazyPoll(); + + // If we have already retrieved the proxy settings (on worker thread) + // then return what we last saw. + if (has_config_) { + *config = last_config_; + return true; + } + return false; + } + + void AddObserver(Observer* observer) { + LazyInitializeOriginLoop(); + DCHECK(origin_task_runner_->BelongsToCurrentThread()); + observers_.AddObserver(observer); + } + + void RemoveObserver(Observer* observer) { + DCHECK(origin_task_runner_->BelongsToCurrentThread()); + observers_.RemoveObserver(observer); + } + + // Check for a new configuration if enough time has elapsed. + void OnLazyPoll() { + LazyInitializeOriginLoop(); + DCHECK(origin_task_runner_->BelongsToCurrentThread()); + + if (last_poll_time_.is_null() || + (base::TimeTicks::Now() - last_poll_time_) > poll_interval_) { + CheckForChangesNow(); + } + } + + void CheckForChangesNow() { + LazyInitializeOriginLoop(); + DCHECK(origin_task_runner_->BelongsToCurrentThread()); + + if (poll_task_outstanding_) { + // Only allow one task to be outstanding at a time. If we get a poll + // request while we are busy, we will defer it until the current poll + // completes. + poll_task_queued_ = true; + return; + } + + last_poll_time_ = base::TimeTicks::Now(); + poll_task_outstanding_ = true; + poll_task_queued_ = false; + base::PostTaskWithTraits( + FROM_HERE, + {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, + base::Bind(&Core::PollAsync, this, get_config_func_)); + } + + private: + friend class base::RefCountedThreadSafe<Core>; + ~Core() = default; + + void PollAsync(GetConfigFunction func) { + ProxyConfig config; + func(&config); + + base::AutoLock lock(lock_); + if (origin_task_runner_.get()) { + origin_task_runner_->PostTask( + FROM_HERE, base::Bind(&Core::GetConfigCompleted, this, config)); + } + } + + // Called after the worker thread has finished retrieving a configuration. + void GetConfigCompleted(const ProxyConfig& config) { + DCHECK(poll_task_outstanding_); + poll_task_outstanding_ = false; + + if (!origin_task_runner_.get()) + return; // Was orphaned (parent has already been destroyed). + + DCHECK(origin_task_runner_->BelongsToCurrentThread()); + + if (!has_config_ || !last_config_.Equals(config)) { + // If the configuration has changed, notify the observers. + has_config_ = true; + last_config_ = config; + for (auto& observer : observers_) + observer.OnProxyConfigChanged(config, ProxyConfigService::CONFIG_VALID); + } + + if (poll_task_queued_) + CheckForChangesNow(); + } + + void LazyInitializeOriginLoop() { + // TODO(eroman): Really this should be done in the constructor, but some + // consumers constructing the ProxyConfigService on threads + // other than the ProxyConfigService's main thread, so we + // can't cache the main thread for the purpose of DCHECKs + // until the first call is made. + if (!have_initialized_origin_runner_) { + origin_task_runner_ = base::ThreadTaskRunnerHandle::Get(); + have_initialized_origin_runner_ = true; + } + } + + GetConfigFunction get_config_func_; + base::ObserverList<Observer> observers_; + ProxyConfig last_config_; + base::TimeTicks last_poll_time_; + base::TimeDelta poll_interval_; + + base::Lock lock_; + scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_; + + bool have_initialized_origin_runner_; + bool has_config_; + bool poll_task_outstanding_; + bool poll_task_queued_; +}; + +void PollingProxyConfigService::AddObserver(Observer* observer) { + core_->AddObserver(observer); +} + +void PollingProxyConfigService::RemoveObserver(Observer* observer) { + core_->RemoveObserver(observer); +} + +ProxyConfigService::ConfigAvailability + PollingProxyConfigService::GetLatestProxyConfig(ProxyConfig* config) { + return core_->GetLatestProxyConfig(config) ? CONFIG_VALID : CONFIG_PENDING; +} + +void PollingProxyConfigService::OnLazyPoll() { + core_->OnLazyPoll(); +} + +PollingProxyConfigService::PollingProxyConfigService( + base::TimeDelta poll_interval, + GetConfigFunction get_config_func) + : core_(new Core(poll_interval, get_config_func)) { +} + +PollingProxyConfigService::~PollingProxyConfigService() { + core_->Orphan(); +} + +void PollingProxyConfigService::CheckForChangesNow() { + core_->CheckForChangesNow(); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/polling_proxy_config_service.h b/chromium/net/proxy_resolution/polling_proxy_config_service.h new file mode 100644 index 00000000000..dccdab481a0 --- /dev/null +++ b/chromium/net/proxy_resolution/polling_proxy_config_service.h @@ -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. + +#ifndef NET_PROXY_RESOLUTION_POLLING_PROXY_CONFIG_SERVICE_H_ +#define NET_PROXY_RESOLUTION_POLLING_PROXY_CONFIG_SERVICE_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/proxy_config_service.h" + +namespace net { + +// PollingProxyConfigService is a base class for creating ProxyConfigService +// implementations that use polling to notice when settings have change. +// +// It runs code to get the current proxy settings on a background worker +// thread, and notifies registered observers when the value changes. +class NET_EXPORT_PRIVATE PollingProxyConfigService : public ProxyConfigService { + public: + // ProxyConfigService implementation: + void AddObserver(Observer* observer) override; + void RemoveObserver(Observer* observer) override; + ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) override; + void OnLazyPoll() override; + + protected: + // Function for retrieving the current proxy configuration. + // Implementors must be threadsafe as the function will be invoked from + // worker threads. + typedef void (*GetConfigFunction)(ProxyConfig*); + + // Creates a polling-based ProxyConfigService which will test for new + // settings at most every |poll_interval| time by calling |get_config_func| + // on a worker thread. + PollingProxyConfigService( + base::TimeDelta poll_interval, + GetConfigFunction get_config_func); + + ~PollingProxyConfigService() override; + + // Polls for changes by posting a task to the worker pool. + void CheckForChangesNow(); + + private: + class Core; + scoped_refptr<Core> core_; + + DISALLOW_COPY_AND_ASSIGN(PollingProxyConfigService); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_POLLING_PROXY_CONFIG_SERVICE_H_ diff --git a/chromium/net/proxy_resolution/proxy_bypass_rules.cc b/chromium/net/proxy_resolution/proxy_bypass_rules.cc new file mode 100644 index 00000000000..372a048fca7 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_bypass_rules.cc @@ -0,0 +1,344 @@ +// 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_resolution/proxy_bypass_rules.h" + +#include "base/strings/pattern.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_tokenizer.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "net/base/host_port_pair.h" +#include "net/base/ip_address.h" +#include "net/base/parse_number.h" +#include "net/base/url_util.h" + +namespace net { + +namespace { + +class HostnamePatternRule : public ProxyBypassRules::Rule { + public: + HostnamePatternRule(const std::string& optional_scheme, + const std::string& hostname_pattern, + int optional_port) + : optional_scheme_(base::ToLowerASCII(optional_scheme)), + hostname_pattern_(base::ToLowerASCII(hostname_pattern)), + optional_port_(optional_port) {} + + bool Matches(const GURL& url) const override { + if (optional_port_ != -1 && url.EffectiveIntPort() != optional_port_) + return false; // Didn't match port expectation. + + if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) + return false; // Didn't match scheme expectation. + + // Note it is necessary to lower-case the host, since GURL uses capital + // letters for percent-escaped characters. + return base::MatchPattern(url.host(), hostname_pattern_); + } + + std::string ToString() const override { + std::string str; + if (!optional_scheme_.empty()) + base::StringAppendF(&str, "%s://", optional_scheme_.c_str()); + str += hostname_pattern_; + if (optional_port_ != -1) + base::StringAppendF(&str, ":%d", optional_port_); + return str; + } + + std::unique_ptr<Rule> Clone() const override { + return std::make_unique<HostnamePatternRule>( + optional_scheme_, hostname_pattern_, optional_port_); + } + + private: + const std::string optional_scheme_; + const std::string hostname_pattern_; + const int optional_port_; +}; + +class BypassLocalRule : public ProxyBypassRules::Rule { + public: + bool Matches(const GURL& url) const override { + const std::string& host = url.host(); + if (host == "127.0.0.1" || host == "[::1]") + return true; + return host.find('.') == std::string::npos; + } + + std::string ToString() const override { return "<local>"; } + + std::unique_ptr<Rule> Clone() const override { + return std::make_unique<BypassLocalRule>(); + } +}; + +// Rule for matching a URL that is an IP address, if that IP address falls +// within a certain numeric range. For example, you could use this rule to +// match all the IPs in the CIDR block 10.10.3.4/24. +class BypassIPBlockRule : public ProxyBypassRules::Rule { + public: + // |ip_prefix| + |prefix_length| define the IP block to match. + BypassIPBlockRule(const std::string& description, + const std::string& optional_scheme, + const IPAddress& ip_prefix, + size_t prefix_length_in_bits) + : description_(description), + optional_scheme_(optional_scheme), + ip_prefix_(ip_prefix), + prefix_length_in_bits_(prefix_length_in_bits) {} + + bool Matches(const GURL& url) const override { + if (!url.HostIsIPAddress()) + return false; + + if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) + return false; // Didn't match scheme expectation. + + // Parse the input IP literal to a number. + IPAddress ip_address; + if (!ip_address.AssignFromIPLiteral(url.HostNoBracketsPiece())) + return false; + + // Test if it has the expected prefix. + return IPAddressMatchesPrefix(ip_address, ip_prefix_, + prefix_length_in_bits_); + } + + std::string ToString() const override { return description_; } + + std::unique_ptr<Rule> Clone() const override { + return std::make_unique<BypassIPBlockRule>( + description_, optional_scheme_, ip_prefix_, prefix_length_in_bits_); + } + + private: + const std::string description_; + const std::string optional_scheme_; + const IPAddress ip_prefix_; + const size_t prefix_length_in_bits_; +}; + +// Returns true if the given string represents an IP address. +// IPv6 addresses are expected to be bracketed. +bool IsIPAddress(const std::string& domain) { + // From GURL::HostIsIPAddress() + url::RawCanonOutputT<char, 128> ignored_output; + url::CanonHostInfo host_info; + url::Component domain_comp(0, domain.size()); + url::CanonicalizeIPAddress(domain.c_str(), domain_comp, &ignored_output, + &host_info); + return host_info.IsIPAddress(); +} + +} // namespace + +ProxyBypassRules::Rule::Rule() = default; + +ProxyBypassRules::Rule::~Rule() = default; + +bool ProxyBypassRules::Rule::Equals(const Rule& rule) const { + return ToString() == rule.ToString(); +} + +ProxyBypassRules::ProxyBypassRules() = default; + +ProxyBypassRules::ProxyBypassRules(const ProxyBypassRules& rhs) { + AssignFrom(rhs); +} + +ProxyBypassRules::~ProxyBypassRules() { + Clear(); +} + +ProxyBypassRules& ProxyBypassRules::operator=(const ProxyBypassRules& rhs) { + AssignFrom(rhs); + return *this; +} + +bool ProxyBypassRules::Matches(const GURL& url) const { + for (RuleList::const_iterator it = rules_.begin(); it != rules_.end(); ++it) { + if ((*it)->Matches(url)) + return true; + } + return false; +} + +bool ProxyBypassRules::Equals(const ProxyBypassRules& other) const { + if (rules_.size() != other.rules_.size()) + return false; + + for (size_t i = 0; i < rules_.size(); ++i) { + if (!rules_[i]->Equals(*other.rules_[i])) + return false; + } + return true; +} + +void ProxyBypassRules::ParseFromString(const std::string& raw) { + ParseFromStringInternal(raw, false); +} + +void ProxyBypassRules::ParseFromStringUsingSuffixMatching( + const std::string& raw) { + ParseFromStringInternal(raw, true); +} + +bool ProxyBypassRules::AddRuleForHostname(const std::string& optional_scheme, + const std::string& hostname_pattern, + int optional_port) { + if (hostname_pattern.empty()) + return false; + + rules_.push_back(std::make_unique<HostnamePatternRule>( + optional_scheme, hostname_pattern, optional_port)); + return true; +} + +void ProxyBypassRules::AddRuleToBypassLocal() { + rules_.push_back(std::make_unique<BypassLocalRule>()); +} + +bool ProxyBypassRules::AddRuleFromString(const std::string& raw) { + return AddRuleFromStringInternalWithLogging(raw, false); +} + +bool ProxyBypassRules::AddRuleFromStringUsingSuffixMatching( + const std::string& raw) { + return AddRuleFromStringInternalWithLogging(raw, true); +} + +std::string ProxyBypassRules::ToString() const { + std::string result; + for (RuleList::const_iterator rule(rules_.begin()); + rule != rules_.end(); + ++rule) { + result += (*rule)->ToString(); + result += ";"; + } + return result; +} + +void ProxyBypassRules::Clear() { + rules_.clear(); +} + +void ProxyBypassRules::AssignFrom(const ProxyBypassRules& other) { + Clear(); + + // Make a copy of the rules list. + for (RuleList::const_iterator it = other.rules_.begin(); + it != other.rules_.end(); ++it) { + rules_.push_back((*it)->Clone()); + } +} + +void ProxyBypassRules::ParseFromStringInternal( + const std::string& raw, + bool use_hostname_suffix_matching) { + Clear(); + + base::StringTokenizer entries(raw, ",;"); + while (entries.GetNext()) { + AddRuleFromStringInternalWithLogging(entries.token(), + use_hostname_suffix_matching); + } +} + +bool ProxyBypassRules::AddRuleFromStringInternal( + const std::string& raw_untrimmed, + bool use_hostname_suffix_matching) { + std::string raw; + base::TrimWhitespaceASCII(raw_untrimmed, base::TRIM_ALL, &raw); + + // This is the special syntax used by WinInet's bypass list -- we allow it + // on all platforms and interpret it the same way. + if (base::LowerCaseEqualsASCII(raw, "<local>")) { + AddRuleToBypassLocal(); + return true; + } + + // Extract any scheme-restriction. + std::string::size_type scheme_pos = raw.find("://"); + std::string scheme; + if (scheme_pos != std::string::npos) { + scheme = raw.substr(0, scheme_pos); + raw = raw.substr(scheme_pos + 3); + if (scheme.empty()) + return false; + } + + if (raw.empty()) + return false; + + // If there is a forward slash in the input, it is probably a CIDR style + // mask. + if (raw.find('/') != std::string::npos) { + IPAddress ip_prefix; + size_t prefix_length_in_bits; + + if (!ParseCIDRBlock(raw, &ip_prefix, &prefix_length_in_bits)) + return false; + + rules_.push_back(std::make_unique<BypassIPBlockRule>( + raw, scheme, ip_prefix, prefix_length_in_bits)); + + return true; + } + + // Check if we have an <ip-address>[:port] input. We need to treat this + // separately since the IP literal may not be in a canonical form. + std::string host; + int port; + if (ParseHostAndPort(raw, &host, &port)) { + // TODO(eroman): HostForURL() below DCHECKs() when |host| contains an + // embedded NULL. + if (host.find('\0') != std::string::npos) + return false; + + // Note that HostPortPair is used to merely to convert any IPv6 literals to + // a URL-safe format that can be used by canonicalization below. + std::string bracketed_host = HostPortPair(host, 80).HostForURL(); + if (IsIPAddress(bracketed_host)) { + // Canonicalize the IP literal before adding it as a string pattern. + GURL tmp_url("http://" + bracketed_host); + return AddRuleForHostname(scheme, tmp_url.host(), port); + } + } + + // Otherwise assume we have <hostname-pattern>[:port]. + std::string::size_type pos_colon = raw.rfind(':'); + port = -1; + if (pos_colon != std::string::npos) { + if (!ParseInt32(base::StringPiece(raw.begin() + pos_colon + 1, raw.end()), + ParseIntFormat::NON_NEGATIVE, &port) || + port > 0xFFFF) { + return false; // Port was invalid. + } + raw = raw.substr(0, pos_colon); + } + + // Special-case hostnames that begin with a period. + // For example, we remap ".google.com" --> "*.google.com". + if (base::StartsWith(raw, ".", base::CompareCase::SENSITIVE)) + raw = "*" + raw; + + // If suffix matching was asked for, make sure the pattern starts with a + // wildcard. + if (use_hostname_suffix_matching && + !base::StartsWith(raw, "*", base::CompareCase::SENSITIVE)) + raw = "*" + raw; + + return AddRuleForHostname(scheme, raw, port); +} + +bool ProxyBypassRules::AddRuleFromStringInternalWithLogging( + const std::string& raw, + bool use_hostname_suffix_matching) { + return AddRuleFromStringInternal(raw, use_hostname_suffix_matching); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_bypass_rules.h b/chromium/net/proxy_resolution/proxy_bypass_rules.h new file mode 100644 index 00000000000..c072aef741d --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_bypass_rules.h @@ -0,0 +1,181 @@ +// 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_RESOLUTION_PROXY_BYPASS_RULES_H_ +#define NET_PROXY_RESOLUTION_PROXY_BYPASS_RULES_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "net/base/net_export.h" +#include "url/gurl.h" + +namespace net { + +// ProxyBypassRules describes the set of URLs that should bypass the proxy +// settings, as a list of rules. A URL is said to match the bypass rules +// if it matches any one of these rules. +class NET_EXPORT ProxyBypassRules { + public: + // Interface for an individual proxy bypass rule. + class NET_EXPORT Rule { + public: + Rule(); + virtual ~Rule(); + + // Returns true if |url| matches the rule. + virtual bool Matches(const GURL& url) const = 0; + + // Returns a string representation of this rule. This is used both for + // visualizing the rules, and also to test equality of a rules list. + virtual std::string ToString() const = 0; + + // Creates a copy of this rule. + virtual std::unique_ptr<Rule> Clone() const = 0; + + bool Equals(const Rule& rule) const; + + private: + DISALLOW_COPY_AND_ASSIGN(Rule); + }; + + typedef std::vector<std::unique_ptr<Rule>> RuleList; + + // Note: This class supports copy constructor and assignment. + ProxyBypassRules(); + ProxyBypassRules(const ProxyBypassRules& rhs); + ~ProxyBypassRules(); + ProxyBypassRules& operator=(const ProxyBypassRules& rhs); + + // Returns the current list of rules. The rules list contains pointers + // which are owned by this class, callers should NOT keep references + // or delete them. + const RuleList& rules() const { return rules_; } + + // Returns true if |url| matches any of the proxy bypass rules. + bool Matches(const GURL& url) const; + + // Returns true if |*this| is equal to |other|; in other words, whether they + // describe the same set of rules. + bool Equals(const ProxyBypassRules& other) const; + + // Initializes the list of rules by parsing the string |raw|. |raw| is a + // comma separated list of rules. See AddRuleFromString() to see the list + // of supported formats. + void ParseFromString(const std::string& raw); + + // This is a variant of ParseFromString, which interprets hostname patterns + // as suffix tests rather than hostname tests (so "google.com" would actually + // match "*google.com"). This is only currently used for the linux no_proxy + // environment variable. It is less flexible, since with the suffix matching + // format you can't match an individual host. + // NOTE: Use ParseFromString() unless you truly need this behavior. + void ParseFromStringUsingSuffixMatching(const std::string& raw); + + // Adds a rule that matches a URL when all of the following are true: + // (a) The URL's scheme matches |optional_scheme|, if + // |!optional_scheme.empty()| + // (b) The URL's hostname matches |hostname_pattern|. + // (c) The URL's (effective) port number matches |optional_port| if + // |optional_port != -1| + // Returns true if the rule was successfully added. + bool AddRuleForHostname(const std::string& optional_scheme, + const std::string& hostname_pattern, + int optional_port); + + // Adds a rule that bypasses all "local" hostnames. + // This matches IE's interpretation of the + // "Bypass proxy server for local addresses" settings checkbox. Fully + // qualified domain names or IP addresses are considered non-local, + // regardless of what they map to (except for the loopback addresses). + void AddRuleToBypassLocal(); + + // Adds a rule given by the string |raw|. The format of |raw| can be any of + // the following: + // + // (1) [ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" <port> ] + // + // Match all hostnames that match the pattern HOSTNAME_PATTERN. + // + // Examples: + // "foobar.com", "*foobar.com", "*.foobar.com", "*foobar.com:99", + // "https://x.*.y.com:99" + // + // (2) "." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ] + // + // Match a particular domain suffix. + // + // Examples: + // ".google.com", ".com", "http://.google.com" + // + // (3) [ SCHEME "://" ] IP_LITERAL [ ":" PORT ] + // + // Match URLs which are IP address literals. + // + // Conceptually this is the similar to (1), but with special cases + // to handle IP literal canonicalization. For example matching + // on "[0:0:0::1]" would be the same as matching on "[::1]" since + // the IPv6 canonicalization is done internally. + // + // Examples: + // "127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99" + // + // (4) IP_LITERAL "/" PREFIX_LENGTH_IN_BITS + // + // Match any URL that is to an IP literal that falls between the + // given range. IP range is specified using CIDR notation. + // + // Examples: + // "192.168.1.1/16", "fefe:13::abc/33". + // + // (5) "<local>" + // + // Match local addresses. The meaning of "<local>" is whether the + // host matches one of: "127.0.0.1", "::1", "localhost". + // + // See the unit-tests for more examples. + // + // Returns true if the rule was successfully added. + bool AddRuleFromString(const std::string& raw); + + // This is a variant of AddFromString, which interprets hostname patterns as + // suffix tests rather than hostname tests (so "google.com" would actually + // match "*google.com"). This is used for KDE which interprets every rule as + // a suffix test. It is less flexible, since with the suffix matching format + // you can't match an individual host. + // + // Returns true if the rule was successfully added. + // + // NOTE: Use AddRuleFromString() unless you truly need this behavior. + bool AddRuleFromStringUsingSuffixMatching(const std::string& raw); + + // Converts the rules to string representation. Inverse operation to + // ParseFromString(). + std::string ToString() const; + + // Removes all the rules. + void Clear(); + + // Sets |*this| to |other|. + void AssignFrom(const ProxyBypassRules& other); + + private: + // The following are variants of ParseFromString() and AddRuleFromString(), + // which additionally prefix hostname patterns with a wildcard if + // |use_hostname_suffix_matching| was true. + void ParseFromStringInternal(const std::string& raw, + bool use_hostname_suffix_matching); + bool AddRuleFromStringInternal(const std::string& raw, + bool use_hostname_suffix_matching); + bool AddRuleFromStringInternalWithLogging(const std::string& raw, + bool use_hostname_suffix_matching); + + RuleList rules_; +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_BYPASS_RULES_H_ diff --git a/chromium/net/proxy_resolution/proxy_bypass_rules_unittest.cc b/chromium/net/proxy_resolution/proxy_bypass_rules_unittest.cc new file mode 100644 index 00000000000..c3e2b135d27 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_bypass_rules_unittest.cc @@ -0,0 +1,324 @@ +// Copyright (c) 2010 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_resolution/proxy_bypass_rules.h" + +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "net/proxy_resolution/proxy_config_service_common_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +TEST(ProxyBypassRulesTest, ParseAndMatchBasicHost) { + ProxyBypassRules rules; + rules.ParseFromString("wWw.gOogle.com"); + ASSERT_EQ(1u, rules.rules().size()); + EXPECT_EQ("www.google.com", rules.rules()[0]->ToString()); + + // All of these match; port, scheme, and non-hostname components don't + // matter. + EXPECT_TRUE(rules.Matches(GURL("http://www.google.com"))); + EXPECT_TRUE(rules.Matches(GURL("ftp://www.google.com:99"))); + EXPECT_TRUE(rules.Matches(GURL("https://www.google.com:81"))); + + // Must be a strict host match to work. + EXPECT_FALSE(rules.Matches(GURL("http://foo.www.google.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://xxx.google.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://google.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://www.google.com.baz.org"))); +} + +TEST(ProxyBypassRulesTest, ParseAndMatchBasicDomain) { + ProxyBypassRules rules; + rules.ParseFromString(".gOOgle.com"); + ASSERT_EQ(1u, rules.rules().size()); + // Note that we inferred this was an "ends with" test. + EXPECT_EQ("*.google.com", rules.rules()[0]->ToString()); + + // All of these match; port, scheme, and non-hostname components don't + // matter. + EXPECT_TRUE(rules.Matches(GURL("http://www.google.com"))); + EXPECT_TRUE(rules.Matches(GURL("ftp://www.google.com:99"))); + EXPECT_TRUE(rules.Matches(GURL("https://a.google.com:81"))); + EXPECT_TRUE(rules.Matches(GURL("http://foo.google.com/x/y?q"))); + EXPECT_TRUE(rules.Matches(GURL("http://foo:bar@baz.google.com#x"))); + + // Must be a strict "ends with" to work. + EXPECT_FALSE(rules.Matches(GURL("http://google.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://foo.google.com.baz.org"))); +} + +TEST(ProxyBypassRulesTest, ParseAndMatchBasicDomainWithPort) { + ProxyBypassRules rules; + rules.ParseFromString("*.GOOGLE.com:80"); + ASSERT_EQ(1u, rules.rules().size()); + EXPECT_EQ("*.google.com:80", rules.rules()[0]->ToString()); + + // All of these match; scheme, and non-hostname components don't matter. + EXPECT_TRUE(rules.Matches(GURL("http://www.google.com"))); + EXPECT_TRUE(rules.Matches(GURL("ftp://www.google.com:80"))); + EXPECT_TRUE(rules.Matches(GURL("https://a.google.com:80?x"))); + + // Must be a strict "ends with" to work. + EXPECT_FALSE(rules.Matches(GURL("http://google.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://foo.google.com.baz.org"))); + + // The ports must match. + EXPECT_FALSE(rules.Matches(GURL("http://www.google.com:90"))); + EXPECT_FALSE(rules.Matches(GURL("https://www.google.com"))); +} + +TEST(ProxyBypassRulesTest, MatchAll) { + ProxyBypassRules rules; + rules.ParseFromString("*"); + ASSERT_EQ(1u, rules.rules().size()); + EXPECT_EQ("*", rules.rules()[0]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://www.google.com"))); + EXPECT_TRUE(rules.Matches(GURL("ftp://www.foobar.com:99"))); + EXPECT_TRUE(rules.Matches(GURL("https://a.google.com:80?x"))); +} + +TEST(ProxyBypassRulesTest, WildcardAtStart) { + ProxyBypassRules rules; + rules.ParseFromString("*.org:443"); + ASSERT_EQ(1u, rules.rules().size()); + EXPECT_EQ("*.org:443", rules.rules()[0]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://www.google.org:443"))); + EXPECT_TRUE(rules.Matches(GURL("https://www.google.org"))); + + EXPECT_FALSE(rules.Matches(GURL("http://www.google.org"))); + EXPECT_FALSE(rules.Matches(GURL("https://www.google.com"))); + EXPECT_FALSE(rules.Matches(GURL("https://www.google.org.com"))); +} + +// Tests a codepath that parses hostnamepattern:port, where "port" is invalid +// by containing a leading plus. +TEST(ProxyBypassRulesTest, ParseInvalidPort) { + ProxyBypassRules rules; + EXPECT_TRUE(rules.AddRuleFromString("*.org:443")); + EXPECT_FALSE(rules.AddRuleFromString("*.com:+443")); + EXPECT_FALSE(rules.AddRuleFromString("*.com:-443")); +} + +TEST(ProxyBypassRulesTest, IPV4Address) { + ProxyBypassRules rules; + rules.ParseFromString("192.168.1.1"); + ASSERT_EQ(1u, rules.rules().size()); + EXPECT_EQ("192.168.1.1", rules.rules()[0]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://192.168.1.1"))); + EXPECT_TRUE(rules.Matches(GURL("https://192.168.1.1:90"))); + + EXPECT_FALSE(rules.Matches(GURL("http://www.google.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://sup.192.168.1.1"))); +} + +TEST(ProxyBypassRulesTest, IPV4AddressWithPort) { + ProxyBypassRules rules; + rules.ParseFromString("192.168.1.1:33"); + ASSERT_EQ(1u, rules.rules().size()); + EXPECT_EQ("192.168.1.1:33", rules.rules()[0]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://192.168.1.1:33"))); + + EXPECT_FALSE(rules.Matches(GURL("http://www.google.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://192.168.1.1"))); + EXPECT_FALSE(rules.Matches(GURL("http://sup.192.168.1.1:33"))); +} + +TEST(ProxyBypassRulesTest, IPV6Address) { + ProxyBypassRules rules; + rules.ParseFromString("[3ffe:2a00:100:7031:0:0::1]"); + ASSERT_EQ(1u, rules.rules().size()); + // Note that we canonicalized the IP address. + EXPECT_EQ("[3ffe:2a00:100:7031::1]", rules.rules()[0]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]"))); + EXPECT_TRUE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]:33"))); + + EXPECT_FALSE(rules.Matches(GURL("http://www.google.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://sup.192.168.1.1:33"))); +} + +TEST(ProxyBypassRulesTest, IPV6AddressWithPort) { + ProxyBypassRules rules; + rules.ParseFromString("[3ffe:2a00:100:7031::1]:33"); + ASSERT_EQ(1u, rules.rules().size()); + EXPECT_EQ("[3ffe:2a00:100:7031::1]:33", rules.rules()[0]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]:33"))); + + EXPECT_FALSE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]"))); + EXPECT_FALSE(rules.Matches(GURL("http://www.google.com"))); +} + +TEST(ProxyBypassRulesTest, HTTPOnly) { + ProxyBypassRules rules; + rules.ParseFromString("http://www.google.com"); + ASSERT_EQ(1u, rules.rules().size()); + EXPECT_EQ("http://www.google.com", rules.rules()[0]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://www.google.com/foo"))); + EXPECT_TRUE(rules.Matches(GURL("http://www.google.com:99"))); + + EXPECT_FALSE(rules.Matches(GURL("https://www.google.com"))); + EXPECT_FALSE(rules.Matches(GURL("ftp://www.google.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://foo.www.google.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://www.google.com.org"))); + EXPECT_FALSE(rules.Matches(GURL("https://www.google.com"))); +} + +TEST(ProxyBypassRulesTest, HTTPOnlyWithWildcard) { + ProxyBypassRules rules; + rules.ParseFromString("http://*www.google.com"); + ASSERT_EQ(1u, rules.rules().size()); + EXPECT_EQ("http://*www.google.com", rules.rules()[0]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://www.google.com/foo"))); + EXPECT_TRUE(rules.Matches(GURL("http://www.google.com:99"))); + EXPECT_TRUE(rules.Matches(GURL("http://foo.www.google.com"))); + + EXPECT_FALSE(rules.Matches(GURL("https://www.google.com"))); + EXPECT_FALSE(rules.Matches(GURL("ftp://www.google.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://www.google.com.org"))); + EXPECT_FALSE(rules.Matches(GURL("https://www.google.com"))); +} + +TEST(ProxyBypassRulesTest, UseSuffixMatching) { + ProxyBypassRules rules; + rules.ParseFromStringUsingSuffixMatching( + "foo1.com, .foo2.com, 192.168.1.1, " + "*foobar.com:80, *.foo, http://baz, <local>"); + ASSERT_EQ(7u, rules.rules().size()); + EXPECT_EQ("*foo1.com", rules.rules()[0]->ToString()); + EXPECT_EQ("*.foo2.com", rules.rules()[1]->ToString()); + EXPECT_EQ("192.168.1.1", rules.rules()[2]->ToString()); + EXPECT_EQ("*foobar.com:80", rules.rules()[3]->ToString()); + EXPECT_EQ("*.foo", rules.rules()[4]->ToString()); + EXPECT_EQ("http://*baz", rules.rules()[5]->ToString()); + EXPECT_EQ("<local>", rules.rules()[6]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://foo1.com"))); + EXPECT_TRUE(rules.Matches(GURL("http://aaafoo1.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://aaafoo1.com.net"))); +} + +TEST(ProxyBypassRulesTest, MultipleRules) { + ProxyBypassRules rules; + rules.ParseFromString(".google.com , .foobar.com:30"); + ASSERT_EQ(2u, rules.rules().size()); + + EXPECT_TRUE(rules.Matches(GURL("http://baz.google.com:40"))); + EXPECT_FALSE(rules.Matches(GURL("http://google.com:40"))); + EXPECT_TRUE(rules.Matches(GURL("http://bar.foobar.com:30"))); + EXPECT_FALSE(rules.Matches(GURL("http://bar.foobar.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://bar.foobar.com:33"))); +} + +TEST(ProxyBypassRulesTest, BadInputs) { + ProxyBypassRules rules; + EXPECT_FALSE(rules.AddRuleFromString("://")); + EXPECT_FALSE(rules.AddRuleFromString(" ")); + EXPECT_FALSE(rules.AddRuleFromString("http://")); + EXPECT_FALSE(rules.AddRuleFromString("*.foo.com:-34")); + EXPECT_EQ(0u, rules.rules().size()); +} + +TEST(ProxyBypassRulesTest, Equals) { + ProxyBypassRules rules1; + ProxyBypassRules rules2; + + rules1.ParseFromString("foo1.com, .foo2.com"); + rules2.ParseFromString("foo1.com,.FOo2.com"); + + EXPECT_TRUE(rules1.Equals(rules2)); + EXPECT_TRUE(rules2.Equals(rules1)); + + rules1.ParseFromString(".foo2.com"); + rules2.ParseFromString("foo1.com,.FOo2.com"); + + EXPECT_FALSE(rules1.Equals(rules2)); + EXPECT_FALSE(rules2.Equals(rules1)); +} + +TEST(ProxyBypassRulesTest, BypassLocalNames) { + const struct { + const char* url; + bool expected_is_local; + } tests[] = { + // Single-component hostnames are considered local. + {"http://localhost/x", true}, + {"http://www", true}, + + // IPv4 loopback interface. + {"http://127.0.0.1/x", true}, + {"http://127.0.0.1:80/x", true}, + + // IPv6 loopback interface. + {"http://[::1]:80/x", true}, + {"http://[0:0::1]:6233/x", true}, + {"http://[0:0:0:0:0:0:0:1]/x", true}, + + // Non-local URLs. + {"http://foo.com/", false}, + {"http://localhost.i/", false}, + {"http://www.google.com/", false}, + {"http://192.168.0.1/", false}, + + // Try with different protocols. + {"ftp://127.0.0.1/x", true}, + {"ftp://foobar.com/x", false}, + + // This is a bit of a gray-area, but GURL does not strip trailing dots + // in host-names, so the following are considered non-local. + {"http://www./x", false}, + {"http://localhost./x", false}, + }; + + ProxyBypassRules rules; + rules.ParseFromString("<local>"); + + for (size_t i = 0; i < arraysize(tests); ++i) { + SCOPED_TRACE(base::StringPrintf( + "Test[%d]: %s", static_cast<int>(i), tests[i].url)); + EXPECT_EQ(tests[i].expected_is_local, rules.Matches(GURL(tests[i].url))); + } +} + +TEST(ProxyBypassRulesTest, ParseAndMatchCIDR_IPv4) { + ProxyBypassRules rules; + rules.ParseFromString("192.168.1.1/16"); + ASSERT_EQ(1u, rules.rules().size()); + EXPECT_EQ("192.168.1.1/16", rules.rules()[0]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://192.168.1.1"))); + EXPECT_TRUE(rules.Matches(GURL("ftp://192.168.4.4"))); + EXPECT_TRUE(rules.Matches(GURL("https://192.168.0.0:81"))); + EXPECT_TRUE(rules.Matches(GURL("http://[::ffff:192.168.11.11]"))); + + EXPECT_FALSE(rules.Matches(GURL("http://foobar.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://192.169.1.1"))); + EXPECT_FALSE(rules.Matches(GURL("http://xxx.192.168.1.1"))); + EXPECT_FALSE(rules.Matches(GURL("http://192.168.1.1.xx"))); +} + +TEST(ProxyBypassRulesTest, ParseAndMatchCIDR_IPv6) { + ProxyBypassRules rules; + rules.ParseFromString("a:b:c:d::/48"); + ASSERT_EQ(1u, rules.rules().size()); + EXPECT_EQ("a:b:c:d::/48", rules.rules()[0]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://[A:b:C:9::]"))); + EXPECT_FALSE(rules.Matches(GURL("http://foobar.com"))); + EXPECT_FALSE(rules.Matches(GURL("http://192.169.1.1"))); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config.cc b/chromium/net/proxy_resolution/proxy_config.cc new file mode 100644 index 00000000000..f33bab69fdf --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config.cc @@ -0,0 +1,281 @@ +// Copyright (c) 2012 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_resolution/proxy_config.h" + +#include <memory> +#include <utility> + +#include "base/logging.h" +#include "base/strings/string_tokenizer.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "net/proxy_resolution/proxy_info.h" + +namespace net { + +namespace { + +// If |proxies| is non-empty, sets it in |dict| under the key |name|. +void AddProxyListToValue(const char* name, + const ProxyList& proxies, + base::DictionaryValue* dict) { + if (!proxies.IsEmpty()) + dict->Set(name, proxies.ToValue()); +} + +// Split the |uri_list| on commas and add each entry to |proxy_list| in turn. +void AddProxyURIListToProxyList(std::string uri_list, + ProxyList* proxy_list, + ProxyServer::Scheme default_scheme) { + base::StringTokenizer proxy_uri_list(uri_list, ","); + while (proxy_uri_list.GetNext()) { + proxy_list->AddProxyServer( + ProxyServer::FromURI(proxy_uri_list.token(), default_scheme)); + } +} + +} // namespace + +ProxyConfig::ProxyRules::ProxyRules() + : reverse_bypass(false), + type(Type::EMPTY) { +} + +ProxyConfig::ProxyRules::ProxyRules(const ProxyRules& other) = default; + +ProxyConfig::ProxyRules::~ProxyRules() = default; + +void ProxyConfig::ProxyRules::Apply(const GURL& url, ProxyInfo* result) const { + if (empty()) { + result->UseDirect(); + return; + } + + bool bypass_proxy = bypass_rules.Matches(url); + if (reverse_bypass) + bypass_proxy = !bypass_proxy; + if (bypass_proxy) { + result->UseDirectWithBypassedProxy(); + return; + } + + switch (type) { + case ProxyRules::Type::PROXY_LIST: { + result->UseProxyList(single_proxies); + return; + } + case ProxyRules::Type::PROXY_LIST_PER_SCHEME: { + const ProxyList* entry = MapUrlSchemeToProxyList(url.scheme()); + if (entry) { + result->UseProxyList(*entry); + } else { + // We failed to find a matching proxy server for the current URL + // scheme. Default to direct. + result->UseDirect(); + } + return; + } + default: { + result->UseDirect(); + NOTREACHED(); + return; + } + } +} + +void ProxyConfig::ProxyRules::ParseFromString(const std::string& proxy_rules) { + // Reset. + type = Type::EMPTY; + single_proxies = ProxyList(); + proxies_for_http = ProxyList(); + proxies_for_https = ProxyList(); + proxies_for_ftp = ProxyList(); + fallback_proxies = ProxyList(); + + base::StringTokenizer proxy_server_list(proxy_rules, ";"); + while (proxy_server_list.GetNext()) { + base::StringTokenizer proxy_server_for_scheme( + proxy_server_list.token_begin(), proxy_server_list.token_end(), "="); + + while (proxy_server_for_scheme.GetNext()) { + std::string url_scheme = proxy_server_for_scheme.token(); + + // If we fail to get the proxy server here, it means that + // this is a regular proxy server configuration, i.e. proxies + // are not configured per protocol. + if (!proxy_server_for_scheme.GetNext()) { + if (type == Type::PROXY_LIST_PER_SCHEME) + continue; // Unexpected. + AddProxyURIListToProxyList(url_scheme, + &single_proxies, + ProxyServer::SCHEME_HTTP); + type = Type::PROXY_LIST; + return; + } + + // Trim whitespace off the url scheme. + base::TrimWhitespaceASCII(url_scheme, base::TRIM_ALL, &url_scheme); + + // Add it to the per-scheme mappings (if supported scheme). + type = Type::PROXY_LIST_PER_SCHEME; + ProxyList* entry = MapUrlSchemeToProxyListNoFallback(url_scheme); + ProxyServer::Scheme default_scheme = ProxyServer::SCHEME_HTTP; + + // socks=XXX is inconsistent with the other formats, since "socks" + // is not a URL scheme. Rather this means "for everything else, send + // it to the SOCKS proxy server XXX". + if (url_scheme == "socks") { + DCHECK(!entry); + entry = &fallback_proxies; + // Note that here 'socks' is understood to be SOCKS4, even though + // 'socks' maps to SOCKS5 in ProxyServer::GetSchemeFromURIInternal. + default_scheme = ProxyServer::SCHEME_SOCKS4; + } + + if (entry) { + AddProxyURIListToProxyList(proxy_server_for_scheme.token(), + entry, + default_scheme); + } + } + } +} + +const ProxyList* ProxyConfig::ProxyRules::MapUrlSchemeToProxyList( + const std::string& url_scheme) const { + const ProxyList* proxy_server_list = const_cast<ProxyRules*>(this)-> + MapUrlSchemeToProxyListNoFallback(url_scheme); + if (proxy_server_list && !proxy_server_list->IsEmpty()) + return proxy_server_list; + if (url_scheme == "ws" || url_scheme == "wss") + return GetProxyListForWebSocketScheme(); + if (!fallback_proxies.IsEmpty()) + return &fallback_proxies; + return NULL; // No mapping for this scheme. Use direct. +} + +bool ProxyConfig::ProxyRules::Equals(const ProxyRules& other) const { + return type == other.type && + single_proxies.Equals(other.single_proxies) && + proxies_for_http.Equals(other.proxies_for_http) && + proxies_for_https.Equals(other.proxies_for_https) && + proxies_for_ftp.Equals(other.proxies_for_ftp) && + fallback_proxies.Equals(other.fallback_proxies) && + bypass_rules.Equals(other.bypass_rules) && + reverse_bypass == other.reverse_bypass; +} + +ProxyList* ProxyConfig::ProxyRules::MapUrlSchemeToProxyListNoFallback( + const std::string& scheme) { + DCHECK_EQ(Type::PROXY_LIST_PER_SCHEME, type); + if (scheme == "http") + return &proxies_for_http; + if (scheme == "https") + return &proxies_for_https; + if (scheme == "ftp") + return &proxies_for_ftp; + return NULL; // No mapping for this scheme. +} + +const ProxyList* ProxyConfig::ProxyRules::GetProxyListForWebSocketScheme() + const { + if (!fallback_proxies.IsEmpty()) + return &fallback_proxies; + if (!proxies_for_https.IsEmpty()) + return &proxies_for_https; + if (!proxies_for_http.IsEmpty()) + return &proxies_for_http; + return NULL; +} + +ProxyConfig::ProxyConfig() + : auto_detect_(false), + pac_mandatory_(false), + source_(PROXY_CONFIG_SOURCE_UNKNOWN) {} + +ProxyConfig::ProxyConfig(const ProxyConfig& config) = default; + +ProxyConfig::~ProxyConfig() = default; + +ProxyConfig& ProxyConfig::operator=(const ProxyConfig& config) = default; + +bool ProxyConfig::Equals(const ProxyConfig& other) const { + // The two configs can have different IDs and sources. We are just interested + // in if they have the same settings. + return auto_detect_ == other.auto_detect_ && + pac_url_ == other.pac_url_ && + pac_mandatory_ == other.pac_mandatory_ && + proxy_rules_.Equals(other.proxy_rules()); +} + +bool ProxyConfig::HasAutomaticSettings() const { + return auto_detect_ || has_pac_url(); +} + +void ProxyConfig::ClearAutomaticSettings() { + auto_detect_ = false; + pac_url_ = GURL(); +} + +std::unique_ptr<base::DictionaryValue> ProxyConfig::ToValue() const { + std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + + // Output the automatic settings. + if (auto_detect_) + dict->SetBoolean("auto_detect", auto_detect_); + if (has_pac_url()) { + dict->SetString("pac_url", pac_url_.possibly_invalid_spec()); + if (pac_mandatory_) + dict->SetBoolean("pac_mandatory", pac_mandatory_); + } + + // Output the manual settings. + if (proxy_rules_.type != ProxyRules::Type::EMPTY) { + switch (proxy_rules_.type) { + case ProxyRules::Type::PROXY_LIST: + AddProxyListToValue("single_proxy", proxy_rules_.single_proxies, + dict.get()); + break; + case ProxyRules::Type::PROXY_LIST_PER_SCHEME: { + std::unique_ptr<base::DictionaryValue> dict2( + new base::DictionaryValue()); + AddProxyListToValue("http", proxy_rules_.proxies_for_http, dict2.get()); + AddProxyListToValue("https", proxy_rules_.proxies_for_https, + dict2.get()); + AddProxyListToValue("ftp", proxy_rules_.proxies_for_ftp, dict2.get()); + AddProxyListToValue("fallback", proxy_rules_.fallback_proxies, + dict2.get()); + dict->Set("proxy_per_scheme", std::move(dict2)); + break; + } + default: + NOTREACHED(); + } + + // Output the bypass rules. + const ProxyBypassRules& bypass = proxy_rules_.bypass_rules; + if (!bypass.rules().empty()) { + if (proxy_rules_.reverse_bypass) + dict->SetBoolean("reverse_bypass", true); + + auto list = std::make_unique<base::ListValue>(); + + for (ProxyBypassRules::RuleList::const_iterator it = + bypass.rules().begin(); + it != bypass.rules().end(); ++it) { + list->AppendString((*it)->ToString()); + } + + dict->Set("bypass_list", std::move(list)); + } + } + + // Output the source. + dict->SetString("source", ProxyConfigSourceToString(source_)); + + return dict; +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config.h b/chromium/net/proxy_resolution/proxy_config.h new file mode 100644 index 00000000000..0ae0317c98a --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config.h @@ -0,0 +1,255 @@ +// Copyright (c) 2012 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_RESOLUTION_PROXY_CONFIG_H_ +#define NET_PROXY_RESOLUTION_PROXY_CONFIG_H_ + +#include <string> + +#include "net/base/net_export.h" +#include "net/base/proxy_server.h" +#include "net/proxy_resolution/proxy_bypass_rules.h" +#include "net/proxy_resolution/proxy_config_source.h" +#include "net/proxy_resolution/proxy_list.h" +#include "url/gurl.h" + +namespace base { +class Value; +class DictionaryValue; +} + +namespace net { + +class ProxyInfo; + +// ProxyConfig describes a user's proxy settings. +// +// There are two categories of proxy settings: +// (1) Automatic (indicates the methods to obtain a PAC script) +// (2) Manual (simple set of proxy servers per scheme, and bypass patterns) +// +// When both automatic and manual settings are specified, the Automatic ones +// take precedence over the manual ones. +// +// For more details see: +// http://www.chromium.org/developers/design-documents/proxy-settings-fallback +class NET_EXPORT ProxyConfig { + public: + // ProxyRules describes the "manual" proxy settings. + struct NET_EXPORT ProxyRules { + enum class Type { + EMPTY, + PROXY_LIST, + PROXY_LIST_PER_SCHEME, + }; + + // Note that the default of Type::EMPTY results in direct connections + // being made when using this ProxyConfig. + ProxyRules(); + ProxyRules(const ProxyRules& other); + ~ProxyRules(); + + bool empty() const { + return type == Type::EMPTY; + } + + // Sets |result| with the proxies to use for |url| based on the current + // rules. + void Apply(const GURL& url, ProxyInfo* result) const; + + // Parses the rules from a string, indicating which proxies to use. + // + // proxy-uri = [<proxy-scheme>"://"]<proxy-host>[":"<proxy-port>] + // + // proxy-uri-list = <proxy-uri>[","<proxy-uri-list>] + // + // url-scheme = "http" | "https" | "ftp" | "socks" + // + // scheme-proxies = [<url-scheme>"="]<proxy-uri-list> + // + // proxy-rules = scheme-proxies[";"<scheme-proxies>] + // + // Thus, the proxy-rules string should be a semicolon-separated list of + // ordered proxies that apply to a particular URL scheme. Unless specified, + // the proxy scheme for proxy-uris is assumed to be http. + // + // Some special cases: + // * If the scheme is omitted from the first proxy list, that list applies + // to all URL schemes and subsequent lists are ignored. + // * If a scheme is omitted from any proxy list after a list where a scheme + // has been provided, the list without a scheme is ignored. + // * If the url-scheme is set to 'socks', that sets a fallback list that + // to all otherwise unspecified url-schemes, however the default proxy- + // scheme for proxy urls in the 'socks' list is understood to be + // socks4:// if unspecified. + // + // For example: + // "http=foopy:80;ftp=foopy2" -- use HTTP proxy "foopy:80" for http:// + // URLs, and HTTP proxy "foopy2:80" for + // ftp:// URLs. + // "foopy:80" -- use HTTP proxy "foopy:80" for all URLs. + // "foopy:80,bar,direct://" -- use HTTP proxy "foopy:80" for all URLs, + // failing over to "bar" if "foopy:80" is + // unavailable, and after that using no + // proxy. + // "socks4://foopy" -- use SOCKS v4 proxy "foopy:1080" for all + // URLs. + // "http=foop,socks5://bar.com -- use HTTP proxy "foopy" for http URLs, + // and fail over to the SOCKS5 proxy + // "bar.com" if "foop" is unavailable. + // "http=foopy,direct:// -- use HTTP proxy "foopy" for http URLs, + // and use no proxy if "foopy" is + // unavailable. + // "http=foopy;socks=foopy2 -- use HTTP proxy "foopy" for http URLs, + // and use socks4://foopy2 for all other + // URLs. + void ParseFromString(const std::string& proxy_rules); + + // Returns one of {&proxies_for_http, &proxies_for_https, &proxies_for_ftp, + // &fallback_proxies}, or NULL if there is no proxy to use. + // Should only call this if the type is Type::PROXY_LIST_PER_SCHEME. + const ProxyList* MapUrlSchemeToProxyList( + const std::string& url_scheme) const; + + // Returns true if |*this| describes the same configuration as |other|. + bool Equals(const ProxyRules& other) const; + + // Exceptions for when not to use a proxy. + ProxyBypassRules bypass_rules; + + // Reverse the meaning of |bypass_rules|. + bool reverse_bypass; + + Type type; + + // Set if |type| is Type::PROXY_LIST. + ProxyList single_proxies; + + // Set if |type| is Type::PROXY_LIST_PER_SCHEME. + ProxyList proxies_for_http; + ProxyList proxies_for_https; + ProxyList proxies_for_ftp; + + // Used when a fallback has been defined and the url to be proxied doesn't + // match any of the standard schemes. + ProxyList fallback_proxies; + + private: + // Returns one of {&proxies_for_http, &proxies_for_https, &proxies_for_ftp} + // or NULL if it is a scheme that we don't have a mapping for. Should only + // call this if the type is Type::PROXY_LIST_PER_SCHEME. Intentionally returns + // NULL for "ws" and "wss" as those are handled specially by + // GetProxyListForWebSocketScheme(). + ProxyList* MapUrlSchemeToProxyListNoFallback(const std::string& scheme); + + // Returns the first of {&fallback_proxies, &proxies_for_https, + // &proxies_for_http} that is non-empty, or NULL. + const ProxyList* GetProxyListForWebSocketScheme() const; + }; + + ProxyConfig(); + ProxyConfig(const ProxyConfig& config); + ~ProxyConfig(); + ProxyConfig& operator=(const ProxyConfig& config); + + // Returns true if the given config is equivalent to this config. The + // comparison ignores differences in |source()|. + bool Equals(const ProxyConfig& other) const; + + // Returns true if this config contains any "automatic" settings. See the + // class description for what that means. + bool HasAutomaticSettings() const; + + void ClearAutomaticSettings(); + + // Creates a Value dump of this configuration. + std::unique_ptr<base::DictionaryValue> ToValue() const; + + ProxyRules& proxy_rules() { + return proxy_rules_; + } + + const ProxyRules& proxy_rules() const { + return proxy_rules_; + } + + void set_pac_url(const GURL& url) { + pac_url_ = url; + } + + const GURL& pac_url() const { + return pac_url_; + } + + void set_pac_mandatory(bool enable_pac_mandatory) { + pac_mandatory_ = enable_pac_mandatory; + } + + bool pac_mandatory() const { + return pac_mandatory_; + } + + bool has_pac_url() const { + return pac_url_.is_valid(); + } + + void set_auto_detect(bool enable_auto_detect) { + auto_detect_ = enable_auto_detect; + } + + bool auto_detect() const { + return auto_detect_; + } + + void set_source(ProxyConfigSource source) { + source_ = source; + } + + ProxyConfigSource source() const { + return source_; + } + + // Helpers to construct some common proxy configurations. + + static ProxyConfig CreateDirect() { + return ProxyConfig(); + } + + static ProxyConfig CreateAutoDetect() { + ProxyConfig config; + config.set_auto_detect(true); + return config; + } + + static ProxyConfig CreateFromCustomPacURL(const GURL& pac_url) { + ProxyConfig config; + config.set_pac_url(pac_url); + // By default fall back to direct connection in case PAC script fails. + config.set_pac_mandatory(false); + return config; + } + + private: + // True if the proxy configuration should be auto-detected. + bool auto_detect_; + + // If non-empty, indicates the URL of the proxy auto-config file to use. + GURL pac_url_; + + // If true, blocks all traffic in case fetching the pac script from |pac_url_| + // fails. Only valid if |pac_url_| is non-empty. + bool pac_mandatory_; + + // Manual proxy settings. + ProxyRules proxy_rules_; + + // Source of proxy settings. + ProxyConfigSource source_; +}; + +} // namespace net + + + +#endif // NET_PROXY_RESOLUTION_PROXY_CONFIG_H_ diff --git a/chromium/net/proxy_resolution/proxy_config_service.h b/chromium/net/proxy_resolution/proxy_config_service.h new file mode 100644 index 00000000000..63429159d80 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service.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_RESOLUTION_PROXY_CONFIG_SERVICE_H_ +#define NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_H_ + +#include "net/base/net_export.h" + +namespace net { + +class ProxyConfig; + +// Service for watching when the proxy settings have changed. +class NET_EXPORT ProxyConfigService { + public: + // Indicates whether proxy configuration is valid, and if not, why. + enum ConfigAvailability { + // Configuration is pending, observers will be notified later. + CONFIG_PENDING, + // Configuration is present and valid. + CONFIG_VALID, + // No configuration is set. + CONFIG_UNSET + }; + + // Observer for being notified when the proxy settings have changed. + class NET_EXPORT Observer { + public: + virtual ~Observer() {} + // Notification callback that should be invoked by ProxyConfigService + // implementors whenever the configuration changes. |availability| indicates + // the new availability status and can be CONFIG_UNSET or CONFIG_VALID (in + // which case |config| contains the configuration). Implementors must not + // pass CONFIG_PENDING. + virtual void OnProxyConfigChanged(const ProxyConfig& config, + ConfigAvailability availability) = 0; + }; + + virtual ~ProxyConfigService() {} + + // Adds/Removes an observer that will be called whenever the proxy + // configuration has changed. + virtual void AddObserver(Observer* observer) = 0; + virtual void RemoveObserver(Observer* observer) = 0; + + // Gets the most recent availability status. If a configuration is present, + // the proxy configuration is written to |config| and CONFIG_VALID is + // returned. Returns CONFIG_PENDING if it is not available yet. In this case, + // it is guaranteed that subscribed observers will be notified of a change at + // some point in the future once the configuration is available. + // Note that to avoid re-entrancy problems, implementations should not + // dispatch any change notifications from within this function. + virtual ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) = 0; + + // ProxyResolutionService will call this periodically during periods of + // activity. It can be used as a signal for polling-based implementations. + // + // Note that this is purely used as an optimization -- polling + // implementations could simply set a global timer that goes off every + // X seconds at which point they check for changes. However that has + // the disadvantage of doing continuous work even during idle periods. + virtual void OnLazyPoll() {} +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_H_ diff --git a/chromium/net/proxy_resolution/proxy_config_service_android.cc b/chromium/net/proxy_resolution/proxy_config_service_android.cc new file mode 100644 index 00000000000..ba844d07a07 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_android.cc @@ -0,0 +1,404 @@ +// Copyright (c) 2012 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_resolution/proxy_config_service_android.h" + +#include <sys/system_properties.h> + +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "base/sequenced_task_runner.h" +#include "base/strings/string_tokenizer.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "jni/ProxyChangeListener_jni.h" +#include "net/base/host_port_pair.h" +#include "net/proxy_resolution/proxy_config.h" +#include "url/third_party/mozilla/url_parse.h" + +using base::android::AttachCurrentThread; +using base::android::ConvertUTF8ToJavaString; +using base::android::ConvertJavaStringToUTF8; +using base::android::CheckException; +using base::android::ClearException; +using base::android::JavaParamRef; +using base::android::ScopedJavaGlobalRef; +using base::android::ScopedJavaLocalRef; + +namespace net { + +namespace { + +typedef ProxyConfigServiceAndroid::GetPropertyCallback GetPropertyCallback; + +// Returns whether the provided string was successfully converted to a port. +bool ConvertStringToPort(const std::string& port, int* output) { + url::Component component(0, port.size()); + int result = url::ParsePort(port.c_str(), component); + if (result == url::PORT_INVALID || result == url::PORT_UNSPECIFIED) + return false; + *output = result; + return true; +} + +ProxyServer ConstructProxyServer(ProxyServer::Scheme scheme, + const std::string& proxy_host, + const std::string& proxy_port) { + DCHECK(!proxy_host.empty()); + int port_as_int = 0; + if (proxy_port.empty()) + port_as_int = ProxyServer::GetDefaultPortForScheme(scheme); + else if (!ConvertStringToPort(proxy_port, &port_as_int)) + return ProxyServer(); + DCHECK(port_as_int > 0); + return ProxyServer( + scheme, HostPortPair(proxy_host, static_cast<uint16_t>(port_as_int))); +} + +ProxyServer LookupProxy(const std::string& prefix, + const GetPropertyCallback& get_property, + ProxyServer::Scheme scheme) { + DCHECK(!prefix.empty()); + std::string proxy_host = get_property.Run(prefix + ".proxyHost"); + if (!proxy_host.empty()) { + std::string proxy_port = get_property.Run(prefix + ".proxyPort"); + return ConstructProxyServer(scheme, proxy_host, proxy_port); + } + // Fall back to default proxy, if any. + proxy_host = get_property.Run("proxyHost"); + if (!proxy_host.empty()) { + std::string proxy_port = get_property.Run("proxyPort"); + return ConstructProxyServer(scheme, proxy_host, proxy_port); + } + return ProxyServer(); +} + +ProxyServer LookupSocksProxy(const GetPropertyCallback& get_property) { + std::string proxy_host = get_property.Run("socksProxyHost"); + if (!proxy_host.empty()) { + std::string proxy_port = get_property.Run("socksProxyPort"); + return ConstructProxyServer(ProxyServer::SCHEME_SOCKS5, proxy_host, + proxy_port); + } + return ProxyServer(); +} + +void AddBypassRules(const std::string& scheme, + const GetPropertyCallback& get_property, + ProxyBypassRules* bypass_rules) { + // The format of a hostname pattern is a list of hostnames that are separated + // by | and that use * as a wildcard. For example, setting the + // http.nonProxyHosts property to *.android.com|*.kernel.org will cause + // requests to http://developer.android.com to be made without a proxy. + + std::string non_proxy_hosts = + get_property.Run(scheme + ".nonProxyHosts"); + if (non_proxy_hosts.empty()) + return; + base::StringTokenizer tokenizer(non_proxy_hosts, "|"); + while (tokenizer.GetNext()) { + std::string token = tokenizer.token(); + std::string pattern; + base::TrimWhitespaceASCII(token, base::TRIM_ALL, &pattern); + if (pattern.empty()) + continue; + // '?' is not one of the specified pattern characters above. + DCHECK_EQ(std::string::npos, pattern.find('?')); + bypass_rules->AddRuleForHostname(scheme, pattern, -1); + } +} + +// Returns true if a valid proxy was found. +bool GetProxyRules(const GetPropertyCallback& get_property, + ProxyConfig::ProxyRules* rules) { + // See libcore/luni/src/main/java/java/net/ProxySelectorImpl.java for the + // mostly equivalent Android implementation. There is one intentional + // difference: by default Chromium uses the HTTP port (80) for HTTPS + // connections via proxy. This default is identical on other platforms. + // On the opposite, Java spec suggests to use HTTPS port (443) by default (the + // default value of https.proxyPort). + rules->type = ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME; + rules->proxies_for_http.SetSingleProxyServer( + LookupProxy("http", get_property, ProxyServer::SCHEME_HTTP)); + rules->proxies_for_https.SetSingleProxyServer( + LookupProxy("https", get_property, ProxyServer::SCHEME_HTTP)); + rules->proxies_for_ftp.SetSingleProxyServer( + LookupProxy("ftp", get_property, ProxyServer::SCHEME_HTTP)); + rules->fallback_proxies.SetSingleProxyServer(LookupSocksProxy(get_property)); + rules->bypass_rules.Clear(); + AddBypassRules("ftp", get_property, &rules->bypass_rules); + AddBypassRules("http", get_property, &rules->bypass_rules); + AddBypassRules("https", get_property, &rules->bypass_rules); + // We know a proxy was found if not all of the proxy lists are empty. + return !(rules->proxies_for_http.IsEmpty() && + rules->proxies_for_https.IsEmpty() && + rules->proxies_for_ftp.IsEmpty() && + rules->fallback_proxies.IsEmpty()); +}; + +void GetLatestProxyConfigInternal(const GetPropertyCallback& get_property, + ProxyConfig* config) { + if (!GetProxyRules(get_property, &config->proxy_rules())) + *config = ProxyConfig::CreateDirect(); +} + +std::string GetJavaProperty(const std::string& property) { + // Use Java System.getProperty to get configuration information. + // TODO(pliard): Conversion to/from UTF8 ok here? + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, property); + ScopedJavaLocalRef<jstring> result = + Java_ProxyChangeListener_getProperty(env, str); + return result.is_null() ? + std::string() : ConvertJavaStringToUTF8(env, result.obj()); +} + +void CreateStaticProxyConfig(const std::string& host, + int port, + const std::string& pac_url, + const std::vector<std::string>& exclusion_list, + ProxyConfig* config) { + if (!pac_url.empty()) { + config->set_pac_url(GURL(pac_url)); + config->set_pac_mandatory(false); + } else if (port != 0) { + std::string rules = base::StringPrintf("%s:%d", host.c_str(), port); + config->proxy_rules().ParseFromString(rules); + config->proxy_rules().bypass_rules.Clear(); + + std::vector<std::string>::const_iterator it; + for (it = exclusion_list.begin(); it != exclusion_list.end(); ++it) { + std::string pattern; + base::TrimWhitespaceASCII(*it, base::TRIM_ALL, &pattern); + if (pattern.empty()) + continue; + config->proxy_rules().bypass_rules.AddRuleForHostname("", pattern, -1); + } + } else { + *config = ProxyConfig::CreateDirect(); + } +} + +} // namespace + +class ProxyConfigServiceAndroid::Delegate + : public base::RefCountedThreadSafe<Delegate> { + public: + Delegate(const scoped_refptr<base::SequencedTaskRunner>& network_task_runner, + const scoped_refptr<base::SequencedTaskRunner>& jni_task_runner, + const GetPropertyCallback& get_property_callback) + : jni_delegate_(this), + network_task_runner_(network_task_runner), + jni_task_runner_(jni_task_runner), + get_property_callback_(get_property_callback), + exclude_pac_url_(false) { + } + + void SetupJNI() { + DCHECK(InJNISequence()); + JNIEnv* env = AttachCurrentThread(); + if (java_proxy_change_listener_.is_null()) { + java_proxy_change_listener_.Reset(Java_ProxyChangeListener_create(env)); + CHECK(!java_proxy_change_listener_.is_null()); + } + Java_ProxyChangeListener_start(env, java_proxy_change_listener_, + reinterpret_cast<intptr_t>(&jni_delegate_)); + } + + void FetchInitialConfig() { + DCHECK(InJNISequence()); + ProxyConfig proxy_config; + GetLatestProxyConfigInternal(get_property_callback_, &proxy_config); + network_task_runner_->PostTask( + FROM_HERE, base::Bind(&Delegate::SetNewConfigInNetworkSequence, this, + proxy_config)); + } + + void Shutdown() { + if (InJNISequence()) { + ShutdownInJNISequence(); + } else { + jni_task_runner_->PostTask( + FROM_HERE, base::Bind(&Delegate::ShutdownInJNISequence, this)); + } + } + + // Called only in the network sequence. + void AddObserver(Observer* observer) { + DCHECK(InNetworkSequence()); + observers_.AddObserver(observer); + } + + void RemoveObserver(Observer* observer) { + DCHECK(InNetworkSequence()); + observers_.RemoveObserver(observer); + } + + ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) { + DCHECK(InNetworkSequence()); + if (!config) + return ProxyConfigService::CONFIG_UNSET; + *config = proxy_config_; + return ProxyConfigService::CONFIG_VALID; + } + + // Called in the JNI sequence. + void ProxySettingsChanged() { + DCHECK(InJNISequence()); + ProxyConfig proxy_config; + GetLatestProxyConfigInternal(get_property_callback_, &proxy_config); + network_task_runner_->PostTask( + FROM_HERE, base::Bind(&Delegate::SetNewConfigInNetworkSequence, this, + proxy_config)); + } + + // Called in the JNI sequence. + void ProxySettingsChangedTo(const std::string& host, + int port, + const std::string& pac_url, + const std::vector<std::string>& exclusion_list) { + DCHECK(InJNISequence()); + ProxyConfig proxy_config; + if (exclude_pac_url_) { + CreateStaticProxyConfig(host, port, "", exclusion_list, &proxy_config); + } else { + CreateStaticProxyConfig(host, port, pac_url, exclusion_list, + &proxy_config); + } + network_task_runner_->PostTask( + FROM_HERE, base::Bind(&Delegate::SetNewConfigInNetworkSequence, this, + proxy_config)); + } + + void set_exclude_pac_url(bool enabled) { + exclude_pac_url_ = enabled; + } + + private: + friend class base::RefCountedThreadSafe<Delegate>; + + class JNIDelegateImpl : public ProxyConfigServiceAndroid::JNIDelegate { + public: + explicit JNIDelegateImpl(Delegate* delegate) : delegate_(delegate) {} + + // ProxyConfigServiceAndroid::JNIDelegate overrides. + void ProxySettingsChangedTo( + JNIEnv* env, + const JavaParamRef<jobject>& jself, + const JavaParamRef<jstring>& jhost, + jint jport, + const JavaParamRef<jstring>& jpac_url, + const JavaParamRef<jobjectArray>& jexclusion_list) override { + std::string host = ConvertJavaStringToUTF8(env, jhost); + std::string pac_url; + if (jpac_url) + ConvertJavaStringToUTF8(env, jpac_url, &pac_url); + std::vector<std::string> exclusion_list; + base::android::AppendJavaStringArrayToStringVector( + env, jexclusion_list, &exclusion_list); + delegate_->ProxySettingsChangedTo(host, jport, pac_url, exclusion_list); + } + + void ProxySettingsChanged(JNIEnv* env, + const JavaParamRef<jobject>& self) override { + delegate_->ProxySettingsChanged(); + } + + private: + Delegate* const delegate_; + }; + + virtual ~Delegate() {} + + void ShutdownInJNISequence() { + if (java_proxy_change_listener_.is_null()) + return; + JNIEnv* env = AttachCurrentThread(); + Java_ProxyChangeListener_stop(env, java_proxy_change_listener_); + } + + // Called on the network sequence. + void SetNewConfigInNetworkSequence(const ProxyConfig& proxy_config) { + DCHECK(InNetworkSequence()); + proxy_config_ = proxy_config; + for (auto& observer : observers_) { + observer.OnProxyConfigChanged(proxy_config, + ProxyConfigService::CONFIG_VALID); + } + } + + bool InJNISequence() const { + return jni_task_runner_->RunsTasksInCurrentSequence(); + } + + bool InNetworkSequence() const { + return network_task_runner_->RunsTasksInCurrentSequence(); + } + + ScopedJavaGlobalRef<jobject> java_proxy_change_listener_; + + JNIDelegateImpl jni_delegate_; + base::ObserverList<Observer> observers_; + scoped_refptr<base::SequencedTaskRunner> network_task_runner_; + scoped_refptr<base::SequencedTaskRunner> jni_task_runner_; + GetPropertyCallback get_property_callback_; + ProxyConfig proxy_config_; + bool exclude_pac_url_; + + DISALLOW_COPY_AND_ASSIGN(Delegate); +}; + +ProxyConfigServiceAndroid::ProxyConfigServiceAndroid( + const scoped_refptr<base::SequencedTaskRunner>& network_task_runner, + const scoped_refptr<base::SequencedTaskRunner>& jni_task_runner) + : delegate_(new Delegate( + network_task_runner, jni_task_runner, base::Bind(&GetJavaProperty))) { + delegate_->SetupJNI(); + delegate_->FetchInitialConfig(); +} + +ProxyConfigServiceAndroid::~ProxyConfigServiceAndroid() { + delegate_->Shutdown(); +} + +void ProxyConfigServiceAndroid::set_exclude_pac_url(bool enabled) { + delegate_->set_exclude_pac_url(enabled); +} + +void ProxyConfigServiceAndroid::AddObserver(Observer* observer) { + delegate_->AddObserver(observer); +} + +void ProxyConfigServiceAndroid::RemoveObserver(Observer* observer) { + delegate_->RemoveObserver(observer); +} + +ProxyConfigService::ConfigAvailability +ProxyConfigServiceAndroid::GetLatestProxyConfig(ProxyConfig* config) { + return delegate_->GetLatestProxyConfig(config); +} + +ProxyConfigServiceAndroid::ProxyConfigServiceAndroid( + const scoped_refptr<base::SequencedTaskRunner>& network_task_runner, + const scoped_refptr<base::SequencedTaskRunner>& jni_task_runner, + GetPropertyCallback get_property_callback) + : delegate_(new Delegate( + network_task_runner, jni_task_runner, get_property_callback)) { + delegate_->SetupJNI(); + delegate_->FetchInitialConfig(); +} + +void ProxyConfigServiceAndroid::ProxySettingsChanged() { + delegate_->ProxySettingsChanged(); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config_service_android.h b/chromium/net/proxy_resolution/proxy_config_service_android.h new file mode 100644 index 00000000000..6f2297730c1 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_android.h @@ -0,0 +1,98 @@ +// Copyright (c) 2012 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_RESOLUTION_PROXY_CONFIG_SERVICE_ANDROID_H_ +#define NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_ANDROID_H_ + +#include <string> + +#include "base/android/jni_android.h" +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/proxy_config_service.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace net { + +class ProxyConfig; + +class NET_EXPORT ProxyConfigServiceAndroid : public ProxyConfigService { + public: + // Callback that returns the value of the property identified by the provided + // key. If it was not found, an empty string is returned. Note that this + // interface does not let you distinguish an empty property from a + // non-existing property. This callback is invoked on the JNI thread. + typedef base::Callback<std::string (const std::string& property)> + GetPropertyCallback; + + // Separate class whose instance is owned by the Delegate class implemented in + // the .cc file. + class JNIDelegate { + public: + virtual ~JNIDelegate() {} + + // Called from Java (on JNI thread) to signal that the proxy settings have + // changed. The string and int arguments (the host/port pair for the proxy) + // are either a host/port pair or ("", 0) to indicate "no proxy". + // The third argument indicates the PAC url. + // The fourth argument is the proxy exclusion list. + virtual void ProxySettingsChangedTo( + JNIEnv*, + const base::android::JavaParamRef<jobject>&, + const base::android::JavaParamRef<jstring>&, + jint, + const base::android::JavaParamRef<jstring>&, + const base::android::JavaParamRef<jobjectArray>&) = 0; + + // Called from Java (on JNI thread) to signal that the proxy settings have + // changed. New proxy settings are fetched from the system property store. + virtual void ProxySettingsChanged( + JNIEnv*, + const base::android::JavaParamRef<jobject>&) = 0; + }; + + ProxyConfigServiceAndroid( + const scoped_refptr<base::SequencedTaskRunner>& network_task_runner, + const scoped_refptr<base::SequencedTaskRunner>& jni_task_runner); + + ~ProxyConfigServiceAndroid() override; + + // Android provides a local HTTP proxy that does PAC resolution. When this + // setting is enabled, the proxy config service ignores the PAC URL and uses + // the local proxy for all proxy resolution. + void set_exclude_pac_url(bool enabled); + + // ProxyConfigService: + // Called only on the network thread. + void AddObserver(Observer* observer) override; + void RemoveObserver(Observer* observer) override; + ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) override; + + private: + friend class ProxyConfigServiceAndroidTestBase; + class Delegate; + + // For tests. + ProxyConfigServiceAndroid( + const scoped_refptr<base::SequencedTaskRunner>& network_task_runner, + const scoped_refptr<base::SequencedTaskRunner>& jni_task_runner, + GetPropertyCallback get_property_callback); + + // For tests. + void ProxySettingsChanged(); + + scoped_refptr<Delegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(ProxyConfigServiceAndroid); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_ANDROID_H_ diff --git a/chromium/net/proxy_resolution/proxy_config_service_android_unittest.cc b/chromium/net/proxy_resolution/proxy_config_service_android_unittest.cc new file mode 100644 index 00000000000..ceae603acfa --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_android_unittest.cc @@ -0,0 +1,366 @@ +// Copyright (c) 2012 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_resolution/proxy_config_service_android.h" + +#include <map> +#include <memory> +#include <string> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "jni/AndroidProxyConfigServiceTestUtil_jni.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class TestObserver : public ProxyConfigService::Observer { + public: + TestObserver() : availability_(ProxyConfigService::CONFIG_UNSET) {} + + // ProxyConfigService::Observer: + void OnProxyConfigChanged( + const ProxyConfig& config, + ProxyConfigService::ConfigAvailability availability) override { + config_ = config; + availability_ = availability; + } + + ProxyConfigService::ConfigAvailability availability() const { + return availability_; + } + + const ProxyConfig& config() const { + return config_; + } + + private: + ProxyConfig config_; + ProxyConfigService::ConfigAvailability availability_; +}; + +// Helper class that simply prepares Java's Looper on construction. +class JavaLooperPreparer { + public: + JavaLooperPreparer() { + Java_AndroidProxyConfigServiceTestUtil_prepareLooper( + base::android::AttachCurrentThread()); + } +}; + +} // namespace + +typedef std::map<std::string, std::string> StringMap; + +class ProxyConfigServiceAndroidTestBase : public testing::Test { + protected: + // Note that the current thread's message loop is initialized by the test + // suite (see net/test/net_test_suite.cc). + ProxyConfigServiceAndroidTestBase(const StringMap& initial_configuration) + : configuration_(initial_configuration), + message_loop_(base::MessageLoop::current()), + service_(message_loop_->task_runner(), + message_loop_->task_runner(), + base::Bind(&ProxyConfigServiceAndroidTestBase::GetProperty, + base::Unretained(this))) {} + + ~ProxyConfigServiceAndroidTestBase() override {} + + // testing::Test: + void SetUp() override { + base::RunLoop().RunUntilIdle(); + service_.AddObserver(&observer_); + } + + void TearDown() override { service_.RemoveObserver(&observer_); } + + void ClearConfiguration() { + configuration_.clear(); + } + + void AddProperty(const std::string& key, const std::string& value) { + configuration_[key] = value; + } + + std::string GetProperty(const std::string& key) { + StringMap::const_iterator it = configuration_.find(key); + if (it == configuration_.end()) + return std::string(); + return it->second; + } + + void ProxySettingsChanged() { + service_.ProxySettingsChanged(); + base::RunLoop().RunUntilIdle(); + } + + void TestMapping(const std::string& url, const std::string& expected) { + ProxyConfigService::ConfigAvailability availability; + ProxyConfig proxy_config; + availability = service_.GetLatestProxyConfig(&proxy_config); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, availability); + ProxyInfo proxy_info; + proxy_config.proxy_rules().Apply(GURL(url), &proxy_info); + EXPECT_EQ(expected, proxy_info.ToPacString()); + } + + StringMap configuration_; + TestObserver observer_; + base::MessageLoop* const message_loop_; + // |java_looper_preparer_| appears before |service_| so that Java's Looper is + // prepared before constructing |service_| as it creates a ProxyChangeListener + // which requires a Looper. + JavaLooperPreparer java_looper_preparer_; + ProxyConfigServiceAndroid service_; +}; + +class ProxyConfigServiceAndroidTest : public ProxyConfigServiceAndroidTestBase { + public: + ProxyConfigServiceAndroidTest() + : ProxyConfigServiceAndroidTestBase(StringMap()) {} +}; + +class ProxyConfigServiceAndroidWithInitialConfigTest + : public ProxyConfigServiceAndroidTestBase { + public: + ProxyConfigServiceAndroidWithInitialConfigTest() + : ProxyConfigServiceAndroidTestBase(MakeInitialConfiguration()) {} + + private: + StringMap MakeInitialConfiguration() { + StringMap initial_configuration; + initial_configuration["http.proxyHost"] = "httpproxy.com"; + initial_configuration["http.proxyPort"] = "8080"; + return initial_configuration; + } +}; + +TEST_F(ProxyConfigServiceAndroidTest, TestChangePropertiesNotification) { + // Set up a non-empty configuration + AddProperty("http.proxyHost", "localhost"); + ProxySettingsChanged(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, observer_.availability()); + EXPECT_FALSE(observer_.config().proxy_rules().empty()); + + // Set up an empty configuration + ClearConfiguration(); + ProxySettingsChanged(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, observer_.availability()); + EXPECT_TRUE(observer_.config().proxy_rules().empty()); +} + +TEST_F(ProxyConfigServiceAndroidWithInitialConfigTest, TestInitialConfig) { + // Make sure that the initial config is set. + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + + // Override the initial configuration. + ClearConfiguration(); + AddProperty("http.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY httpproxy.com:80"); +} + +// !! The following test cases are automatically generated from +// !! net/android/tools/proxy_test_cases.py. +// !! Please edit that file instead of editing the test cases below and +// !! update also the corresponding Java unit tests in +// !! AndroidProxySelectorTest.java + +TEST_F(ProxyConfigServiceAndroidTest, NoProxy) { + // Test direct mapping when no proxy defined. + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndPort) { + // Test http.proxyHost and http.proxyPort works. + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostOnly) { + // We should get the default port (80) for proxied hosts. + AddProperty("http.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY httpproxy.com:80"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyPortOnly) { + // http.proxyPort only should not result in any hosts being proxied. + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts1) { + // Test that HTTP non proxy hosts are mapped correctly + AddProperty("http.nonProxyHosts", "slashdot.org"); + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://slashdot.org/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts2) { + // Test that | pattern works. + AddProperty("http.nonProxyHosts", "slashdot.org|freecode.net"); + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://freecode.net/", "DIRECT"); + TestMapping("http://slashdot.org/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts3) { + // Test that * pattern works. + AddProperty("http.nonProxyHosts", "*example.com"); + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("http://slashdot.org/", "PROXY httpproxy.com:8080"); + TestMapping("http://www.example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FtpNonProxyHosts) { + // Test that FTP non proxy hosts are mapped correctly + AddProperty("ftp.nonProxyHosts", "slashdot.org"); + AddProperty("ftp.proxyHost", "httpproxy.com"); + AddProperty("ftp.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FtpProxyHostAndPort) { + // Test ftp.proxyHost and ftp.proxyPort works. + AddProperty("ftp.proxyHost", "httpproxy.com"); + AddProperty("ftp.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FtpProxyHostOnly) { + // Test ftp.proxyHost and default port. + AddProperty("ftp.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:80"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpsProxyHostAndPort) { + // Test https.proxyHost and https.proxyPort works. + AddProperty("https.proxyHost", "httpproxy.com"); + AddProperty("https.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "PROXY httpproxy.com:8080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpsProxyHostOnly) { + // Test https.proxyHost and default port. + AddProperty("https.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "PROXY httpproxy.com:80"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostIPv6) { + // Test IPv6 https.proxyHost and default port. + AddProperty("http.proxyHost", "a:b:c::d:1"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY [a:b:c::d:1]:80"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndPortIPv6) { + // Test IPv6 http.proxyHost and http.proxyPort works. + AddProperty("http.proxyHost", "a:b:c::d:1"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY [a:b:c::d:1]:8080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndInvalidPort) { + // Test invalid http.proxyPort does not crash. + AddProperty("http.proxyHost", "a:b:c::d:1"); + AddProperty("http.proxyPort", "65536"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, DefaultProxyExplictPort) { + // Default http proxy is used if a scheme-specific one is not found. + AddProperty("ftp.proxyHost", "httpproxy.com"); + AddProperty("ftp.proxyPort", "8080"); + AddProperty("proxyHost", "defaultproxy.com"); + AddProperty("proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://example.com/", "PROXY defaultproxy.com:8080"); + TestMapping("https://example.com/", "PROXY defaultproxy.com:8080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, DefaultProxyDefaultPort) { + // Check that the default proxy port is as expected. + AddProperty("proxyHost", "defaultproxy.com"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY defaultproxy.com:80"); + TestMapping("https://example.com/", "PROXY defaultproxy.com:80"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FallbackToSocks) { + // SOCKS proxy is used if scheme-specific one is not found. + AddProperty("http.proxyHost", "defaultproxy.com"); + AddProperty("socksProxyHost", "socksproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com", "SOCKS5 socksproxy.com:1080"); + TestMapping("http://example.com/", "PROXY defaultproxy.com:80"); + TestMapping("https://example.com/", "SOCKS5 socksproxy.com:1080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, SocksExplicitPort) { + // SOCKS proxy port is used if specified + AddProperty("socksProxyHost", "socksproxy.com"); + AddProperty("socksProxyPort", "9000"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "SOCKS5 socksproxy.com:9000"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxySupercedesSocks) { + // SOCKS proxy is ignored if default HTTP proxy defined. + AddProperty("proxyHost", "defaultproxy.com"); + AddProperty("socksProxyHost", "socksproxy.com"); + AddProperty("socksProxyPort", "9000"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY defaultproxy.com:80"); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config_service_common_unittest.cc b/chromium/net/proxy_resolution/proxy_config_service_common_unittest.cc new file mode 100644 index 00000000000..aa21172b2b1 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_common_unittest.cc @@ -0,0 +1,191 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/proxy_resolution/proxy_config_service_common_unittest.h" + +#include <string> +#include <vector> + +#include "net/proxy_resolution/proxy_config.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +// Helper to verify that |expected_proxy| matches the first proxy conatined in +// |actual_proxies|, and that |actual_proxies| contains exactly one proxy. If +// either condition is untrue, then |*did_fail| is set to true, and +// |*failure_details| is filled with a description of the failure. +void MatchesProxyServerHelper(const char* failure_message, + const char* expected_proxy, + const ProxyList& actual_proxies, + ::testing::AssertionResult* failure_details, + bool* did_fail) { + // If |expected_proxy| is empty, then we expect |actual_proxies| to be so as + // well. + if (strlen(expected_proxy) == 0) { + if (!actual_proxies.IsEmpty()) { + *did_fail = true; + *failure_details + << failure_message << ". Was expecting no proxies but got " + << actual_proxies.size() << "."; + } + return; + } + + // Otherwise we check that |actual_proxies| holds a single matching proxy. + if (actual_proxies.size() != 1) { + *did_fail = true; + *failure_details + << failure_message << ". Was expecting exactly one proxy but got " + << actual_proxies.size() << "."; + return; + } + + ProxyServer actual_proxy = actual_proxies.Get(); + std::string actual_proxy_string; + if (actual_proxy.is_valid()) + actual_proxy_string = actual_proxy.ToURI(); + + if (std::string(expected_proxy) != actual_proxy_string) { + *failure_details + << failure_message << ". Was expecting: \"" << expected_proxy + << "\" but got: \"" << actual_proxy_string << "\""; + *did_fail = true; + } +} + +std::string FlattenProxyBypass(const ProxyBypassRules& bypass_rules) { + std::string flattened_proxy_bypass; + for (ProxyBypassRules::RuleList::const_iterator it = + bypass_rules.rules().begin(); + it != bypass_rules.rules().end(); ++it) { + if (!flattened_proxy_bypass.empty()) + flattened_proxy_bypass += ","; + flattened_proxy_bypass += (*it)->ToString(); + } + return flattened_proxy_bypass; +} + +} // namespace + +ProxyRulesExpectation::ProxyRulesExpectation( + ProxyConfig::ProxyRules::Type type, + const char* single_proxy, + const char* proxy_for_http, + const char* proxy_for_https, + const char* proxy_for_ftp, + const char* fallback_proxy, + const char* flattened_bypass_rules, + bool reverse_bypass) + : type(type), + single_proxy(single_proxy), + proxy_for_http(proxy_for_http), + proxy_for_https(proxy_for_https), + proxy_for_ftp(proxy_for_ftp), + fallback_proxy(fallback_proxy), + flattened_bypass_rules(flattened_bypass_rules), + reverse_bypass(reverse_bypass) { +} + + +::testing::AssertionResult ProxyRulesExpectation::Matches( + const ProxyConfig::ProxyRules& rules) const { + ::testing::AssertionResult failure_details = ::testing::AssertionFailure(); + bool failed = false; + + if (rules.type != type) { + failure_details << "Type mismatch. Expected: " << static_cast<int>(type) + << " but was: " << static_cast<int>(rules.type); + failed = true; + } + + MatchesProxyServerHelper("Bad single_proxy", single_proxy, + rules.single_proxies, &failure_details, &failed); + MatchesProxyServerHelper("Bad proxy_for_http", proxy_for_http, + rules.proxies_for_http, &failure_details, + &failed); + MatchesProxyServerHelper("Bad proxy_for_https", proxy_for_https, + rules.proxies_for_https, &failure_details, + &failed); + MatchesProxyServerHelper("Bad fallback_proxy", fallback_proxy, + rules.fallback_proxies, &failure_details, &failed); + + std::string actual_flattened_bypass = FlattenProxyBypass(rules.bypass_rules); + if (std::string(flattened_bypass_rules) != actual_flattened_bypass) { + failure_details + << "Bad bypass rules. Expected: \"" << flattened_bypass_rules + << "\" but got: \"" << actual_flattened_bypass << "\""; + failed = true; + } + + if (rules.reverse_bypass != reverse_bypass) { + failure_details << "Bad reverse_bypass. Expected: " << reverse_bypass + << " but got: " << rules.reverse_bypass; + failed = true; + } + + return failed ? failure_details : ::testing::AssertionSuccess(); +} + +// static +ProxyRulesExpectation ProxyRulesExpectation::Empty() { + return ProxyRulesExpectation(ProxyConfig::ProxyRules::Type::EMPTY, + "", "", "", "", "", "", false); +} + +// static +ProxyRulesExpectation ProxyRulesExpectation::EmptyWithBypass( + const char* flattened_bypass_rules) { + return ProxyRulesExpectation(ProxyConfig::ProxyRules::Type::EMPTY, + "", "", "", "", "", flattened_bypass_rules, + false); +} + +// static +ProxyRulesExpectation ProxyRulesExpectation::Single( + const char* single_proxy, + const char* flattened_bypass_rules) { + return ProxyRulesExpectation(ProxyConfig::ProxyRules::Type::PROXY_LIST, + single_proxy, "", "", "", "", + flattened_bypass_rules, false); +} + +// static +ProxyRulesExpectation ProxyRulesExpectation::PerScheme( + const char* proxy_http, + const char* proxy_https, + const char* proxy_ftp, + const char* flattened_bypass_rules) { + return ProxyRulesExpectation(ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + "", proxy_http, proxy_https, proxy_ftp, "", + flattened_bypass_rules, false); +} + +// static +ProxyRulesExpectation ProxyRulesExpectation::PerSchemeWithSocks( + const char* proxy_http, + const char* proxy_https, + const char* proxy_ftp, + const char* socks_proxy, + const char* flattened_bypass_rules) { + return ProxyRulesExpectation(ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + "", proxy_http, proxy_https, proxy_ftp, + socks_proxy, flattened_bypass_rules, false); +} + +// static +ProxyRulesExpectation ProxyRulesExpectation::PerSchemeWithBypassReversed( + const char* proxy_http, + const char* proxy_https, + const char* proxy_ftp, + const char* flattened_bypass_rules) { + return ProxyRulesExpectation(ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + "", proxy_http, proxy_https, proxy_ftp, "", + flattened_bypass_rules, true); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config_service_common_unittest.h b/chromium/net/proxy_resolution/proxy_config_service_common_unittest.h new file mode 100644 index 00000000000..35c6fd65207 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_common_unittest.h @@ -0,0 +1,80 @@ +// Copyright (c) 2010 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_RESOLUTION_PROXY_CONFIG_SERVICE_COMMON_UNITTEST_H_ +#define NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_COMMON_UNITTEST_H_ + +#include "net/proxy_resolution/proxy_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Helper functions to describe the expected value of a +// ProxyConfig::ProxyRules, and to check for a match. + +namespace net { + +// This structure contains our expectations on what values the ProxyRules +// should have. +struct ProxyRulesExpectation { + ProxyRulesExpectation(ProxyConfig::ProxyRules::Type type, + const char* single_proxy, + const char* proxy_for_http, + const char* proxy_for_https, + const char* proxy_for_ftp, + const char* fallback_proxy, + const char* flattened_bypass_rules, + bool reverse_bypass); + + // Call this within an EXPECT_TRUE(), to assert that |rules| matches + // our expected values |*this|. + ::testing::AssertionResult Matches( + const ProxyConfig::ProxyRules& rules) const; + + // Creates an expectation that the ProxyRules has no rules. + static ProxyRulesExpectation Empty(); + + // Creates an expectation that the ProxyRules has nothing other than + // the specified bypass rules. + static ProxyRulesExpectation EmptyWithBypass( + const char* flattened_bypass_rules); + + // Creates an expectation that the ProxyRules is for a single proxy + // server for all schemes. + static ProxyRulesExpectation Single(const char* single_proxy, + const char* flattened_bypass_rules); + + // Creates an expectation that the ProxyRules specifies a different + // proxy server for each URL scheme. + static ProxyRulesExpectation PerScheme(const char* proxy_http, + const char* proxy_https, + const char* proxy_ftp, + const char* flattened_bypass_rules); + + // Same as above, but additionally with a SOCKS fallback. + static ProxyRulesExpectation PerSchemeWithSocks( + const char* proxy_http, + const char* proxy_https, + const char* proxy_ftp, + const char* fallback_proxy, + const char* flattened_bypass_rules); + + // Same as PerScheme, but with the bypass rules reversed + static ProxyRulesExpectation PerSchemeWithBypassReversed( + const char* proxy_http, + const char* proxy_https, + const char* proxy_ftp, + const char* flattened_bypass_rules); + + ProxyConfig::ProxyRules::Type type; + const char* single_proxy; + const char* proxy_for_http; + const char* proxy_for_https; + const char* proxy_for_ftp; + const char* fallback_proxy; + const char* flattened_bypass_rules; + bool reverse_bypass; +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_COMMON_UNITTEST_H_ diff --git a/chromium/net/proxy_resolution/proxy_config_service_fixed.cc b/chromium/net/proxy_resolution/proxy_config_service_fixed.cc new file mode 100644 index 00000000000..9f06bf89d6e --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_fixed.cc @@ -0,0 +1,21 @@ +// 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_resolution/proxy_config_service_fixed.h" + +namespace net { + +ProxyConfigServiceFixed::ProxyConfigServiceFixed(const ProxyConfig& pc) + : pc_(pc) { +} + +ProxyConfigServiceFixed::~ProxyConfigServiceFixed() = default; + +ProxyConfigService::ConfigAvailability + ProxyConfigServiceFixed::GetLatestProxyConfig(ProxyConfig* config) { + *config = pc_; + return CONFIG_VALID; +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config_service_fixed.h b/chromium/net/proxy_resolution/proxy_config_service_fixed.h new file mode 100644 index 00000000000..578f9f0fa34 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_fixed.h @@ -0,0 +1,33 @@ +// 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_RESOLUTION_PROXY_CONFIG_SERVICE_FIXED_H_ +#define NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_FIXED_H_ + +#include "base/compiler_specific.h" +#include "net/base/net_errors.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_config_service.h" + +namespace net { + +// Implementation of ProxyConfigService that returns a fixed result. +class NET_EXPORT ProxyConfigServiceFixed : public ProxyConfigService { + public: + explicit ProxyConfigServiceFixed(const ProxyConfig& pc); + ~ProxyConfigServiceFixed() override; + + // ProxyConfigService methods: + void AddObserver(Observer* observer) override {} + void RemoveObserver(Observer* observer) override {} + ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) override; + + private: + ProxyConfig pc_; +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_FIXED_H_ diff --git a/chromium/net/proxy_resolution/proxy_config_service_ios.cc b/chromium/net/proxy_resolution/proxy_config_service_ios.cc new file mode 100644 index 00000000000..468d4891b62 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_ios.cc @@ -0,0 +1,109 @@ +// Copyright (c) 2012 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_resolution/proxy_config_service_ios.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <CFNetwork/CFProxySupport.h> + +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/sys_string_conversions.h" +#include "net/proxy_resolution/proxy_config.h" + +namespace net { + +namespace { + +const int kPollIntervalSec = 10; + +// Utility function to pull out a boolean value from a dictionary and return it, +// returning a default value if the key is not present. +bool GetBoolFromDictionary(CFDictionaryRef dict, + CFStringRef key, + bool default_value) { + CFNumberRef number = + base::mac::GetValueFromDictionary<CFNumberRef>(dict, key); + if (!number) + return default_value; + + int int_value; + if (CFNumberGetValue(number, kCFNumberIntType, &int_value)) + return int_value; + else + return default_value; +} + +void GetCurrentProxyConfig(ProxyConfig* config) { + base::ScopedCFTypeRef<CFDictionaryRef> config_dict( + CFNetworkCopySystemProxySettings()); + DCHECK(config_dict); + + // Auto-detect is not supported. + // The kCFNetworkProxiesProxyAutoDiscoveryEnable key is not available on iOS. + + // PAC file + + if (GetBoolFromDictionary(config_dict.get(), + kCFNetworkProxiesProxyAutoConfigEnable, + false)) { + CFStringRef pac_url_ref = base::mac::GetValueFromDictionary<CFStringRef>( + config_dict.get(), kCFNetworkProxiesProxyAutoConfigURLString); + if (pac_url_ref) + config->set_pac_url(GURL(base::SysCFStringRefToUTF8(pac_url_ref))); + } + + // Proxies (for now http). + + // The following keys are not available on iOS: + // kCFNetworkProxiesFTPEnable + // kCFNetworkProxiesFTPProxy + // kCFNetworkProxiesFTPPort + // kCFNetworkProxiesHTTPSEnable + // kCFNetworkProxiesHTTPSProxy + // kCFNetworkProxiesHTTPSPort + // kCFNetworkProxiesSOCKSEnable + // kCFNetworkProxiesSOCKSProxy + // kCFNetworkProxiesSOCKSPort + if (GetBoolFromDictionary(config_dict.get(), + kCFNetworkProxiesHTTPEnable, + false)) { + ProxyServer proxy_server = + ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP, + config_dict.get(), + kCFNetworkProxiesHTTPProxy, + kCFNetworkProxiesHTTPPort); + if (proxy_server.is_valid()) { + config->proxy_rules().type = + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME; + config->proxy_rules().proxies_for_http.SetSingleProxyServer(proxy_server); + // Desktop Safari applies the HTTP proxy to http:// URLs only, but + // Mobile Safari applies the HTTP proxy to https:// URLs as well. + config->proxy_rules().proxies_for_https.SetSingleProxyServer( + proxy_server); + } + } + + // Proxy bypass list is not supported. + // The kCFNetworkProxiesExceptionsList key is not available on iOS. + + // Proxy bypass boolean is not supported. + // The kCFNetworkProxiesExcludeSimpleHostnames key is not available on iOS. + + // Source + config->set_source(PROXY_CONFIG_SOURCE_SYSTEM); +} + +} // namespace + +ProxyConfigServiceIOS::ProxyConfigServiceIOS() + : PollingProxyConfigService(base::TimeDelta::FromSeconds(kPollIntervalSec), + GetCurrentProxyConfig) { +} + +ProxyConfigServiceIOS::~ProxyConfigServiceIOS() { +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config_service_ios.h b/chromium/net/proxy_resolution/proxy_config_service_ios.h new file mode 100644 index 00000000000..f6e342c5de0 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_ios.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012 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_RESOLUTION_PROXY_CONFIG_SERVICE_IOS_H_ +#define NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_IOS_H_ + +#include "base/macros.h" +#include "net/proxy_resolution/polling_proxy_config_service.h" + +namespace net { + +class ProxyConfigServiceIOS : public PollingProxyConfigService { + public: + // Constructs a ProxyConfigService that watches the iOS system proxy settings. + explicit ProxyConfigServiceIOS(); + ~ProxyConfigServiceIOS() override; + + private: + DISALLOW_COPY_AND_ASSIGN(ProxyConfigServiceIOS); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_IOS_H_ diff --git a/chromium/net/proxy_resolution/proxy_config_service_linux.cc b/chromium/net/proxy_resolution/proxy_config_service_linux.cc new file mode 100644 index 00000000000..9f2db1c6ca8 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_linux.cc @@ -0,0 +1,1413 @@ +// Copyright (c) 2012 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_resolution/proxy_config_service_linux.h" + +#include <errno.h> +#include <limits.h> +#include <sys/inotify.h> +#include <unistd.h> + +#include <map> +#include <utility> + +#include "base/bind.h" +#include "base/files/file_descriptor_watcher_posix.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/nix/xdg_util.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_tokenizer.h" +#include "base/strings/string_util.h" +#include "base/task_scheduler/post_task.h" +#include "base/task_scheduler/task_traits.h" +#include "base/threading/thread_restrictions.h" +#include "base/timer/timer.h" +#include "net/base/proxy_server.h" +#include "net/proxy_resolution/proxy_config.h" + +#if defined(USE_GIO) +#include <gio/gio.h> +#endif // defined(USE_GIO) + +namespace net { + +namespace { + +// Given a proxy hostname from a setting, returns that hostname with +// an appropriate proxy server scheme prefix. +// scheme indicates the desired proxy scheme: usually http, with +// socks 4 or 5 as special cases. +// TODO(arindam): Remove URI string manipulation by using MapUrlSchemeToProxy. +std::string FixupProxyHostScheme(ProxyServer::Scheme scheme, + std::string host) { + if (scheme == ProxyServer::SCHEME_SOCKS5 && + base::StartsWith(host, "socks4://", + base::CompareCase::INSENSITIVE_ASCII)) { + // We default to socks 5, but if the user specifically set it to + // socks4://, then use that. + scheme = ProxyServer::SCHEME_SOCKS4; + } + // Strip the scheme if any. + std::string::size_type colon = host.find("://"); + if (colon != std::string::npos) + host = host.substr(colon + 3); + // If a username and perhaps password are specified, give a warning. + std::string::size_type at_sign = host.find("@"); + // Should this be supported? + if (at_sign != std::string::npos) { + // ProxyConfig does not support authentication parameters, but Chrome + // will prompt for the password later. Disregard the + // authentication parameters and continue with this hostname. + LOG(WARNING) << "Proxy authentication parameters ignored, see bug 16709"; + host = host.substr(at_sign + 1); + } + // If this is a socks proxy, prepend a scheme so as to tell + // ProxyServer. This also allows ProxyServer to choose the right + // default port. + if (scheme == ProxyServer::SCHEME_SOCKS4) + host = "socks4://" + host; + else if (scheme == ProxyServer::SCHEME_SOCKS5) + host = "socks5://" + host; + // If there is a trailing slash, remove it so |host| will parse correctly + // even if it includes a port number (since the slash is not numeric). + if (!host.empty() && host.back() == '/') + host.resize(host.length() - 1); + return host; +} + +ProxyConfig GetConfigOrDirect( + const base::Optional<ProxyConfig>& optional_config) { + if (optional_config) + return optional_config.value(); + + ProxyConfig config = ProxyConfig::CreateDirect(); + config.set_source(PROXY_CONFIG_SOURCE_SYSTEM_FAILED); + return config; +} + +} // namespace + +ProxyConfigServiceLinux::Delegate::~Delegate() = default; + +bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVarForScheme( + base::StringPiece variable, + ProxyServer::Scheme scheme, + ProxyServer* result_server) { + std::string env_value; + if (!env_var_getter_->GetVar(variable, &env_value)) + return false; + + if (env_value.empty()) + return false; + + env_value = FixupProxyHostScheme(scheme, env_value); + ProxyServer proxy_server = + ProxyServer::FromURI(env_value, ProxyServer::SCHEME_HTTP); + if (proxy_server.is_valid() && !proxy_server.is_direct()) { + *result_server = proxy_server; + return true; + } + LOG(ERROR) << "Failed to parse environment variable " << variable; + return false; +} + +bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVar( + base::StringPiece variable, + ProxyServer* result_server) { + return GetProxyFromEnvVarForScheme(variable, ProxyServer::SCHEME_HTTP, + result_server); +} + +base::Optional<ProxyConfig> +ProxyConfigServiceLinux::Delegate::GetConfigFromEnv() { + base::Optional<ProxyConfig> config; + config.emplace(); + + // Check for automatic configuration first, in + // "auto_proxy". Possibly only the "environment_proxy" firefox + // extension has ever used this, but it still sounds like a good + // idea. + std::string auto_proxy; + if (env_var_getter_->GetVar("auto_proxy", &auto_proxy)) { + if (auto_proxy.empty()) { + // Defined and empty => autodetect + config->set_auto_detect(true); + } else { + // specified autoconfig URL + config->set_pac_url(GURL(auto_proxy)); + } + return config; + } + // "all_proxy" is a shortcut to avoid defining {http,https,ftp}_proxy. + ProxyServer proxy_server; + if (GetProxyFromEnvVar("all_proxy", &proxy_server)) { + config->proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST; + config->proxy_rules().single_proxies.SetSingleProxyServer(proxy_server); + } else { + bool have_http = GetProxyFromEnvVar("http_proxy", &proxy_server); + if (have_http) + config->proxy_rules().proxies_for_http.SetSingleProxyServer(proxy_server); + // It would be tempting to let http_proxy apply for all protocols + // if https_proxy and ftp_proxy are not defined. Googling turns up + // several documents that mention only http_proxy. But then the + // user really might not want to proxy https. And it doesn't seem + // like other apps do this. So we will refrain. + bool have_https = GetProxyFromEnvVar("https_proxy", &proxy_server); + if (have_https) + config->proxy_rules().proxies_for_https. + SetSingleProxyServer(proxy_server); + bool have_ftp = GetProxyFromEnvVar("ftp_proxy", &proxy_server); + if (have_ftp) + config->proxy_rules().proxies_for_ftp.SetSingleProxyServer(proxy_server); + if (have_http || have_https || have_ftp) { + // mustn't change type unless some rules are actually set. + config->proxy_rules().type = + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME; + } + } + if (config->proxy_rules().empty()) { + // If the above were not defined, try for socks. + // For environment variables, we default to version 5, per the gnome + // documentation: http://library.gnome.org/devel/gnet/stable/gnet-socks.html + ProxyServer::Scheme scheme = ProxyServer::SCHEME_SOCKS5; + std::string env_version; + if (env_var_getter_->GetVar("SOCKS_VERSION", &env_version) + && env_version == "4") + scheme = ProxyServer::SCHEME_SOCKS4; + if (GetProxyFromEnvVarForScheme("SOCKS_SERVER", scheme, &proxy_server)) { + config->proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST; + config->proxy_rules().single_proxies.SetSingleProxyServer(proxy_server); + } + } + // Look for the proxy bypass list. + std::string no_proxy; + env_var_getter_->GetVar("no_proxy", &no_proxy); + if (config->proxy_rules().empty()) { + // Having only "no_proxy" set, presumably to "*", makes it + // explicit that env vars do specify a configuration: having no + // rules specified only means the user explicitly asks for direct + // connections. + return !no_proxy.empty() ? config : base::Optional<ProxyConfig>(); + } + // Note that this uses "suffix" matching. So a bypass of "google.com" + // is understood to mean a bypass of "*google.com". + config->proxy_rules().bypass_rules.ParseFromStringUsingSuffixMatching( + no_proxy); + return config; +} + +namespace { + +const int kDebounceTimeoutMilliseconds = 250; + +#if defined(USE_GIO) +const char kProxyGSettingsSchema[] = "org.gnome.system.proxy"; + +// This setting getter uses gsettings, as used in most GNOME 3 desktops. +class SettingGetterImplGSettings + : public ProxyConfigServiceLinux::SettingGetter { + public: + SettingGetterImplGSettings() + : client_(nullptr), + http_client_(nullptr), + https_client_(nullptr), + ftp_client_(nullptr), + socks_client_(nullptr), + notify_delegate_(nullptr), + debounce_timer_(new base::OneShotTimer()) {} + + ~SettingGetterImplGSettings() override { + // client_ should have been released before now, from + // Delegate::OnDestroy(), while running on the UI thread. However + // on exiting the process, it may happen that + // Delegate::OnDestroy() task is left pending on the glib loop + // after the loop was quit, and pending tasks may then be deleted + // without being run. + if (client_) { + // gsettings client was not cleaned up. + if (task_runner_->RunsTasksInCurrentSequence()) { + // We are on the UI thread so we can clean it safely. This is + // the case at least for ui_tests running under Valgrind in + // bug 16076. + VLOG(1) << "~SettingGetterImplGSettings: releasing gsettings client"; + ShutDown(); + } else { + LOG(WARNING) << "~SettingGetterImplGSettings: leaking gsettings client"; + client_ = nullptr; + } + } + DCHECK(!client_); + } + + // CheckVersion() must be called *before* Init()! + bool CheckVersion(base::Environment* env); + + bool Init(const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner) + override { + DCHECK(glib_task_runner->RunsTasksInCurrentSequence()); + DCHECK(!client_); + DCHECK(!task_runner_.get()); + + if (!g_settings_schema_source_lookup(g_settings_schema_source_get_default(), + kProxyGSettingsSchema, FALSE) || + !(client_ = g_settings_new(kProxyGSettingsSchema))) { + // It's not clear whether/when this can return NULL. + LOG(ERROR) << "Unable to create a gsettings client"; + return false; + } + task_runner_ = glib_task_runner; + // We assume these all work if the above call worked. + http_client_ = g_settings_get_child(client_, "http"); + https_client_ = g_settings_get_child(client_, "https"); + ftp_client_ = g_settings_get_child(client_, "ftp"); + socks_client_ = g_settings_get_child(client_, "socks"); + DCHECK(http_client_ && https_client_ && ftp_client_ && socks_client_); + return true; + } + + void ShutDown() override { + if (client_) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + // This also disables gsettings notifications. + g_object_unref(socks_client_); + g_object_unref(ftp_client_); + g_object_unref(https_client_); + g_object_unref(http_client_); + g_object_unref(client_); + // We only need to null client_ because it's the only one that we check. + client_ = nullptr; + task_runner_ = nullptr; + } + debounce_timer_.reset(); + } + + bool SetUpNotifications( + ProxyConfigServiceLinux::Delegate* delegate) override { + DCHECK(client_); + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + notify_delegate_ = delegate; + // We could watch for the change-event signal instead of changed, but + // since we have to watch more than one object, we'd still have to + // debounce change notifications. This is conceptually simpler. + g_signal_connect(G_OBJECT(client_), "changed", + G_CALLBACK(OnGSettingsChangeNotification), this); + g_signal_connect(G_OBJECT(http_client_), "changed", + G_CALLBACK(OnGSettingsChangeNotification), this); + g_signal_connect(G_OBJECT(https_client_), "changed", + G_CALLBACK(OnGSettingsChangeNotification), this); + g_signal_connect(G_OBJECT(ftp_client_), "changed", + G_CALLBACK(OnGSettingsChangeNotification), this); + g_signal_connect(G_OBJECT(socks_client_), "changed", + G_CALLBACK(OnGSettingsChangeNotification), this); + // Simulate a change to avoid possibly losing updates before this point. + OnChangeNotification(); + return true; + } + + const scoped_refptr<base::SequencedTaskRunner>& GetNotificationTaskRunner() + override { + return task_runner_; + } + + ProxyConfigSource GetConfigSource() override { + return PROXY_CONFIG_SOURCE_GSETTINGS; + } + + bool GetString(StringSetting key, std::string* result) override { + DCHECK(client_); + switch (key) { + case PROXY_MODE: + return GetStringByPath(client_, "mode", result); + case PROXY_AUTOCONF_URL: + return GetStringByPath(client_, "autoconfig-url", result); + case PROXY_HTTP_HOST: + return GetStringByPath(http_client_, "host", result); + case PROXY_HTTPS_HOST: + return GetStringByPath(https_client_, "host", result); + case PROXY_FTP_HOST: + return GetStringByPath(ftp_client_, "host", result); + case PROXY_SOCKS_HOST: + return GetStringByPath(socks_client_, "host", result); + } + return false; // Placate compiler. + } + bool GetBool(BoolSetting key, bool* result) override { + DCHECK(client_); + switch (key) { + case PROXY_USE_HTTP_PROXY: + // Although there is an "enabled" boolean in http_client_, it is not set + // to true by the proxy config utility. We ignore it and return false. + return false; + case PROXY_USE_SAME_PROXY: + // Similarly, although there is a "use-same-proxy" boolean in client_, + // it is never set to false by the proxy config utility. We ignore it. + return false; + case PROXY_USE_AUTHENTICATION: + // There is also no way to set this in the proxy config utility, but it + // doesn't hurt us to get the actual setting (unlike the two above). + return GetBoolByPath(http_client_, "use-authentication", result); + } + return false; // Placate compiler. + } + bool GetInt(IntSetting key, int* result) override { + DCHECK(client_); + switch (key) { + case PROXY_HTTP_PORT: + return GetIntByPath(http_client_, "port", result); + case PROXY_HTTPS_PORT: + return GetIntByPath(https_client_, "port", result); + case PROXY_FTP_PORT: + return GetIntByPath(ftp_client_, "port", result); + case PROXY_SOCKS_PORT: + return GetIntByPath(socks_client_, "port", result); + } + return false; // Placate compiler. + } + bool GetStringList(StringListSetting key, + std::vector<std::string>* result) override { + DCHECK(client_); + switch (key) { + case PROXY_IGNORE_HOSTS: + return GetStringListByPath(client_, "ignore-hosts", result); + } + return false; // Placate compiler. + } + + bool BypassListIsReversed() override { + // This is a KDE-specific setting. + return false; + } + + bool MatchHostsUsingSuffixMatching() override { return false; } + + private: + bool GetStringByPath(GSettings* client, + base::StringPiece key, + std::string* result) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + gchar* value = g_settings_get_string(client, key.data()); + if (!value) + return false; + *result = value; + g_free(value); + return true; + } + bool GetBoolByPath(GSettings* client, base::StringPiece key, bool* result) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + *result = static_cast<bool>(g_settings_get_boolean(client, key.data())); + return true; + } + bool GetIntByPath(GSettings* client, base::StringPiece key, int* result) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + *result = g_settings_get_int(client, key.data()); + return true; + } + bool GetStringListByPath(GSettings* client, + base::StringPiece key, + std::vector<std::string>* result) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + gchar** list = g_settings_get_strv(client, key.data()); + if (!list) + return false; + for (size_t i = 0; list[i]; ++i) { + result->push_back(static_cast<char*>(list[i])); + g_free(list[i]); + } + g_free(list); + return true; + } + + // This is the callback from the debounce timer. + void OnDebouncedNotification() { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + CHECK(notify_delegate_); + // Forward to a method on the proxy config service delegate object. + notify_delegate_->OnCheckProxyConfigSettings(); + } + + void OnChangeNotification() { + // We don't use Reset() because the timer may not yet be running. + // (In that case Stop() is a no-op.) + debounce_timer_->Stop(); + debounce_timer_->Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds), + this, &SettingGetterImplGSettings::OnDebouncedNotification); + } + + // gsettings notification callback, dispatched on the default glib main loop. + static void OnGSettingsChangeNotification(GSettings* client, gchar* key, + gpointer user_data) { + VLOG(1) << "gsettings change notification for key " << key; + // We don't track which key has changed, just that something did change. + SettingGetterImplGSettings* setting_getter = + reinterpret_cast<SettingGetterImplGSettings*>(user_data); + setting_getter->OnChangeNotification(); + } + + GSettings* client_; + GSettings* http_client_; + GSettings* https_client_; + GSettings* ftp_client_; + GSettings* socks_client_; + ProxyConfigServiceLinux::Delegate* notify_delegate_; + std::unique_ptr<base::OneShotTimer> debounce_timer_; + + // Task runner for the thread that we make gsettings calls on. It should + // be the UI thread and all our methods should be called on this + // thread. Only for assertions. + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + DISALLOW_COPY_AND_ASSIGN(SettingGetterImplGSettings); +}; + +bool SettingGetterImplGSettings::CheckVersion( + base::Environment* env) { + // CheckVersion() must be called *before* Init()! + DCHECK(!client_); + + GSettings* client = nullptr; + if (g_settings_schema_source_lookup(g_settings_schema_source_get_default(), + kProxyGSettingsSchema, FALSE)) { + client = g_settings_new(kProxyGSettingsSchema); + } + if (!client) { + VLOG(1) << "Cannot create gsettings client."; + return false; + } + g_object_unref(client); + + VLOG(1) << "All gsettings tests OK. Will get proxy config from gsettings."; + return true; +} +#endif // defined(USE_GIO) + +// Converts |value| from a decimal string to an int. If there was a failure +// parsing, returns |default_value|. +int StringToIntOrDefault(base::StringPiece value, int default_value) { + int result; + if (base::StringToInt(value, &result)) + return result; + return default_value; +} + +// This is the KDE version that reads kioslaverc and simulates gsettings. +// Doing this allows the main Delegate code, as well as the unit tests +// for it, to stay the same - and the settings map fairly well besides. +class SettingGetterImplKDE : public ProxyConfigServiceLinux::SettingGetter { + public: + explicit SettingGetterImplKDE(base::Environment* env_var_getter) + : inotify_fd_(-1), + notify_delegate_(nullptr), + debounce_timer_(new base::OneShotTimer()), + indirect_manual_(false), + auto_no_pac_(false), + reversed_bypass_list_(false), + env_var_getter_(env_var_getter), + file_task_runner_(nullptr) { + // This has to be called on the UI thread (http://crbug.com/69057). + base::ThreadRestrictions::ScopedAllowIO allow_io; + + // Derive the location of the kde config dir from the environment. + std::string home; + if (env_var_getter->GetVar("KDEHOME", &home) && !home.empty()) { + // $KDEHOME is set. Use it unconditionally. + kde_config_dir_ = KDEHomeToConfigPath(base::FilePath(home)); + } else { + // $KDEHOME is unset. Try to figure out what to use. This seems to be + // the common case on most distributions. + if (!env_var_getter->GetVar(base::env_vars::kHome, &home)) + // User has no $HOME? Give up. Later we'll report the failure. + return; + if (base::nix::GetDesktopEnvironment(env_var_getter) == + base::nix::DESKTOP_ENVIRONMENT_KDE3) { + // KDE3 always uses .kde for its configuration. + base::FilePath kde_path = base::FilePath(home).Append(".kde"); + kde_config_dir_ = KDEHomeToConfigPath(kde_path); + } else if (base::nix::GetDesktopEnvironment(env_var_getter) == + base::nix::DESKTOP_ENVIRONMENT_KDE4) { + // Some distributions patch KDE4 to use .kde4 instead of .kde, so that + // both can be installed side-by-side. Sadly they don't all do this, and + // they don't always do this: some distributions have started switching + // back as well. So if there is a .kde4 directory, check the timestamps + // of the config directories within and use the newest one. + // Note that we should currently be running in the UI thread, because in + // the gsettings version, that is the only thread that can access the + // proxy settings (a gsettings restriction). As noted below, the initial + // read of the proxy settings will be done in this thread anyway, so we + // check for .kde4 here in this thread as well. + base::FilePath kde3_path = base::FilePath(home).Append(".kde"); + base::FilePath kde3_config = KDEHomeToConfigPath(kde3_path); + base::FilePath kde4_path = base::FilePath(home).Append(".kde4"); + base::FilePath kde4_config = KDEHomeToConfigPath(kde4_path); + bool use_kde4 = false; + if (base::DirectoryExists(kde4_path)) { + base::File::Info kde3_info; + base::File::Info kde4_info; + if (base::GetFileInfo(kde4_config, &kde4_info)) { + if (base::GetFileInfo(kde3_config, &kde3_info)) { + use_kde4 = kde4_info.last_modified >= kde3_info.last_modified; + } else { + use_kde4 = true; + } + } + } + if (use_kde4) { + kde_config_dir_ = KDEHomeToConfigPath(kde4_path); + } else { + kde_config_dir_ = KDEHomeToConfigPath(kde3_path); + } + } else { + // KDE 5 migrated to ~/.config for storing kioslaverc. + kde_config_dir_ = base::FilePath(home).Append(".config"); + } + } + } + + ~SettingGetterImplKDE() override { + // inotify_fd_ should have been closed before now, from + // Delegate::OnDestroy(), while running on the file thread. However + // on exiting the process, it may happen that Delegate::OnDestroy() + // task is left pending on the file loop after the loop was quit, + // and pending tasks may then be deleted without being run. + // Here in the KDE version, we can safely close the file descriptor + // anyway. (Not that it really matters; the process is exiting.) + if (inotify_fd_ >= 0) + ShutDown(); + DCHECK_LT(inotify_fd_, 0); + } + + bool Init(const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner) + override { + // This has to be called on the UI thread (http://crbug.com/69057). + base::ThreadRestrictions::ScopedAllowIO allow_io; + DCHECK_LT(inotify_fd_, 0); + inotify_fd_ = inotify_init(); + if (inotify_fd_ < 0) { + PLOG(ERROR) << "inotify_init failed"; + return false; + } + if (!base::SetNonBlocking(inotify_fd_)) { + PLOG(ERROR) << "base::SetNonBlocking failed"; + close(inotify_fd_); + inotify_fd_ = -1; + return false; + } + + constexpr base::TaskTraits kTraits = {base::TaskPriority::USER_VISIBLE, + base::MayBlock()}; + file_task_runner_ = base::CreateSequencedTaskRunnerWithTraits(kTraits); + + // The initial read is done on the current thread, not + // |file_task_runner_|, since we will need to have it for + // SetUpAndFetchInitialConfig(). + UpdateCachedSettings(); + return true; + } + + void ShutDown() override { + if (inotify_fd_ >= 0) { + ResetCachedSettings(); + inotify_watcher_.reset(); + close(inotify_fd_); + inotify_fd_ = -1; + } + debounce_timer_.reset(); + } + + bool SetUpNotifications( + ProxyConfigServiceLinux::Delegate* delegate) override { + DCHECK_GE(inotify_fd_, 0); + DCHECK(file_task_runner_->RunsTasksInCurrentSequence()); + // We can't just watch the kioslaverc file directly, since KDE will write + // a new copy of it and then rename it whenever settings are changed and + // inotify watches inodes (so we'll be watching the old deleted file after + // the first change, and it will never change again). So, we watch the + // directory instead. We then act only on changes to the kioslaverc entry. + // TODO(eroman): What if the file is deleted? (handle with IN_DELETE). + if (inotify_add_watch(inotify_fd_, kde_config_dir_.value().c_str(), + IN_MODIFY | IN_MOVED_TO) < 0) { + return false; + } + notify_delegate_ = delegate; + inotify_watcher_ = base::FileDescriptorWatcher::WatchReadable( + inotify_fd_, base::Bind(&SettingGetterImplKDE::OnChangeNotification, + base::Unretained(this))); + // Simulate a change to avoid possibly losing updates before this point. + OnChangeNotification(); + return true; + } + + const scoped_refptr<base::SequencedTaskRunner>& GetNotificationTaskRunner() + override { + return file_task_runner_; + } + + ProxyConfigSource GetConfigSource() override { + return PROXY_CONFIG_SOURCE_KDE; + } + + bool GetString(StringSetting key, std::string* result) override { + string_map_type::iterator it = string_table_.find(key); + if (it == string_table_.end()) + return false; + *result = it->second; + return true; + } + bool GetBool(BoolSetting key, bool* result) override { + // We don't ever have any booleans. + return false; + } + bool GetInt(IntSetting key, int* result) override { + // We don't ever have any integers. (See AddProxy() below about ports.) + return false; + } + bool GetStringList(StringListSetting key, + std::vector<std::string>* result) override { + strings_map_type::iterator it = strings_table_.find(key); + if (it == strings_table_.end()) + return false; + *result = it->second; + return true; + } + + bool BypassListIsReversed() override { return reversed_bypass_list_; } + + bool MatchHostsUsingSuffixMatching() override { return true; } + + private: + void ResetCachedSettings() { + string_table_.clear(); + strings_table_.clear(); + indirect_manual_ = false; + auto_no_pac_ = false; + reversed_bypass_list_ = false; + } + + base::FilePath KDEHomeToConfigPath(const base::FilePath& kde_home) { + return kde_home.Append("share").Append("config"); + } + + void AddProxy(StringSetting host_key, const std::string& value) { + if (value.empty() || value.substr(0, 3) == "//:") + // No proxy. + return; + size_t space = value.find(' '); + if (space != std::string::npos) { + // Newer versions of KDE use a space rather than a colon to separate the + // port number from the hostname. If we find this, we need to convert it. + std::string fixed = value; + fixed[space] = ':'; + string_table_[host_key] = fixed; + } else { + // We don't need to parse the port number out; GetProxyFromSettings() + // would only append it right back again. So we just leave the port + // number right in the host string. + string_table_[host_key] = value; + } + } + + void AddHostList(StringListSetting key, const std::string& value) { + std::vector<std::string> tokens; + base::StringTokenizer tk(value, ", "); + while (tk.GetNext()) { + std::string token = tk.token(); + if (!token.empty()) + tokens.push_back(token); + } + strings_table_[key] = tokens; + } + + void AddKDESetting(const std::string& key, const std::string& value) { + if (key == "ProxyType") { + const char* mode = "none"; + indirect_manual_ = false; + auto_no_pac_ = false; + int int_value = StringToIntOrDefault(value, 0); + switch (int_value) { + case 1: // Manual configuration. + mode = "manual"; + break; + case 2: // PAC URL. + mode = "auto"; + break; + case 3: // WPAD. + mode = "auto"; + auto_no_pac_ = true; + break; + case 4: // Indirect manual via environment variables. + mode = "manual"; + indirect_manual_ = true; + break; + default: // No proxy, or maybe kioslaverc syntax error. + break; + } + string_table_[PROXY_MODE] = mode; + } else if (key == "Proxy Config Script") { + string_table_[PROXY_AUTOCONF_URL] = value; + } else if (key == "httpProxy") { + AddProxy(PROXY_HTTP_HOST, value); + } else if (key == "httpsProxy") { + AddProxy(PROXY_HTTPS_HOST, value); + } else if (key == "ftpProxy") { + AddProxy(PROXY_FTP_HOST, value); + } else if (key == "socksProxy") { + // Older versions of KDE configure SOCKS in a weird way involving + // LD_PRELOAD and a library that intercepts network calls to SOCKSify + // them. We don't support it. KDE 4.8 added a proper SOCKS setting. + AddProxy(PROXY_SOCKS_HOST, value); + } else if (key == "ReversedException") { + // We count "true" or any nonzero number as true, otherwise false. + // A failure parsing the integer will also mean false. + reversed_bypass_list_ = + (value == "true" || StringToIntOrDefault(value, 0) != 0); + } else if (key == "NoProxyFor") { + AddHostList(PROXY_IGNORE_HOSTS, value); + } else if (key == "AuthMode") { + // Check for authentication, just so we can warn. + int mode = StringToIntOrDefault(value, 0); + if (mode) { + // ProxyConfig does not support authentication parameters, but + // Chrome will prompt for the password later. So we ignore this. + LOG(WARNING) << + "Proxy authentication parameters ignored, see bug 16709"; + } + } + } + + void ResolveIndirect(StringSetting key) { + string_map_type::iterator it = string_table_.find(key); + if (it != string_table_.end()) { + std::string value; + if (env_var_getter_->GetVar(it->second.c_str(), &value)) + it->second = value; + else + string_table_.erase(it); + } + } + + void ResolveIndirectList(StringListSetting key) { + strings_map_type::iterator it = strings_table_.find(key); + if (it != strings_table_.end()) { + std::string value; + if (!it->second.empty() && + env_var_getter_->GetVar(it->second[0].c_str(), &value)) + AddHostList(key, value); + else + strings_table_.erase(it); + } + } + + // The settings in kioslaverc could occur in any order, but some affect + // others. Rather than read the whole file in and then query them in an + // order that allows us to handle that, we read the settings in whatever + // order they occur and do any necessary tweaking after we finish. + void ResolveModeEffects() { + if (indirect_manual_) { + ResolveIndirect(PROXY_HTTP_HOST); + ResolveIndirect(PROXY_HTTPS_HOST); + ResolveIndirect(PROXY_FTP_HOST); + ResolveIndirectList(PROXY_IGNORE_HOSTS); + } + if (auto_no_pac_) { + // Remove the PAC URL; we're not supposed to use it. + string_table_.erase(PROXY_AUTOCONF_URL); + } + } + + // Reads kioslaverc one line at a time and calls AddKDESetting() to add + // each relevant name-value pair to the appropriate value table. + void UpdateCachedSettings() { + base::FilePath kioslaverc = kde_config_dir_.Append("kioslaverc"); + base::ScopedFILE input(base::OpenFile(kioslaverc, "r")); + if (!input.get()) + return; + ResetCachedSettings(); + bool in_proxy_settings = false; + bool line_too_long = false; + char line[BUFFER_SIZE]; + // fgets() will return NULL on EOF or error. + while (fgets(line, sizeof(line), input.get())) { + // fgets() guarantees the line will be properly terminated. + size_t length = strlen(line); + if (!length) + continue; + // This should be true even with CRLF endings. + if (line[length - 1] != '\n') { + line_too_long = true; + continue; + } + if (line_too_long) { + // The previous line had no line ending, but this done does. This is + // the end of the line that was too long, so warn here and skip it. + LOG(WARNING) << "skipped very long line in " << kioslaverc.value(); + line_too_long = false; + continue; + } + // Remove the LF at the end, and the CR if there is one. + line[--length] = '\0'; + if (length && line[length - 1] == '\r') + line[--length] = '\0'; + // Now parse the line. + if (line[0] == '[') { + // Switching sections. All we care about is whether this is + // the (a?) proxy settings section, for both KDE3 and KDE4. + in_proxy_settings = !strncmp(line, "[Proxy Settings]", 16); + } else if (in_proxy_settings) { + // A regular line, in the (a?) proxy settings section. + char* split = strchr(line, '='); + // Skip this line if it does not contain an = sign. + if (!split) + continue; + // Split the line on the = and advance |split|. + *(split++) = 0; + std::string key = line; + std::string value = split; + base::TrimWhitespaceASCII(key, base::TRIM_ALL, &key); + base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value); + // Skip this line if the key name is empty. + if (key.empty()) + continue; + // Is the value name localized? + if (key[key.length() - 1] == ']') { + // Find the matching bracket. + length = key.rfind('['); + // Skip this line if the localization indicator is malformed. + if (length == std::string::npos) + continue; + // Trim the localization indicator off. + key.resize(length); + // Remove any resulting trailing whitespace. + base::TrimWhitespaceASCII(key, base::TRIM_TRAILING, &key); + // Skip this line if the key name is now empty. + if (key.empty()) + continue; + } + // Now fill in the tables. + AddKDESetting(key, value); + } + } + if (ferror(input.get())) + LOG(ERROR) << "error reading " << kioslaverc.value(); + ResolveModeEffects(); + } + + // This is the callback from the debounce timer. + void OnDebouncedNotification() { + DCHECK(file_task_runner_->RunsTasksInCurrentSequence()); + VLOG(1) << "inotify change notification for kioslaverc"; + UpdateCachedSettings(); + CHECK(notify_delegate_); + // Forward to a method on the proxy config service delegate object. + notify_delegate_->OnCheckProxyConfigSettings(); + } + + // Called by OnFileCanReadWithoutBlocking() on the file thread. Reads + // from the inotify file descriptor and starts up a debounce timer if + // an event for kioslaverc is seen. + void OnChangeNotification() { + DCHECK_GE(inotify_fd_, 0); + DCHECK(file_task_runner_->RunsTasksInCurrentSequence()); + char event_buf[(sizeof(inotify_event) + NAME_MAX + 1) * 4]; + bool kioslaverc_touched = false; + ssize_t r; + while ((r = read(inotify_fd_, event_buf, sizeof(event_buf))) > 0) { + // inotify returns variable-length structures, which is why we have + // this strange-looking loop instead of iterating through an array. + char* event_ptr = event_buf; + while (event_ptr < event_buf + r) { + inotify_event* event = reinterpret_cast<inotify_event*>(event_ptr); + // The kernel always feeds us whole events. + CHECK_LE(event_ptr + sizeof(inotify_event), event_buf + r); + CHECK_LE(event->name + event->len, event_buf + r); + if (!strcmp(event->name, "kioslaverc")) + kioslaverc_touched = true; + // Advance the pointer just past the end of the filename. + event_ptr = event->name + event->len; + } + // We keep reading even if |kioslaverc_touched| is true to drain the + // inotify event queue. + } + if (!r) + // Instead of returning -1 and setting errno to EINVAL if there is not + // enough buffer space, older kernels (< 2.6.21) return 0. Simulate the + // new behavior (EINVAL) so we can reuse the code below. + errno = EINVAL; + if (errno != EAGAIN) { + PLOG(WARNING) << "error reading inotify file descriptor"; + if (errno == EINVAL) { + // Our buffer is not large enough to read the next event. This should + // not happen (because its size is calculated to always be sufficiently + // large), but if it does we'd warn continuously since |inotify_fd_| + // would be forever ready to read. Close it and stop watching instead. + LOG(ERROR) << "inotify failure; no longer watching kioslaverc!"; + inotify_watcher_.reset(); + close(inotify_fd_); + inotify_fd_ = -1; + } + } + if (kioslaverc_touched) { + LOG(ERROR) << "kioslaverc_touched"; + // We don't use Reset() because the timer may not yet be running. + // (In that case Stop() is a no-op.) + debounce_timer_->Stop(); + debounce_timer_->Start(FROM_HERE, base::TimeDelta::FromMilliseconds( + kDebounceTimeoutMilliseconds), this, + &SettingGetterImplKDE::OnDebouncedNotification); + } + } + + typedef std::map<StringSetting, std::string> string_map_type; + typedef std::map<StringListSetting, + std::vector<std::string> > strings_map_type; + + int inotify_fd_; + std::unique_ptr<base::FileDescriptorWatcher::Controller> inotify_watcher_; + ProxyConfigServiceLinux::Delegate* notify_delegate_; + std::unique_ptr<base::OneShotTimer> debounce_timer_; + base::FilePath kde_config_dir_; + bool indirect_manual_; + bool auto_no_pac_; + bool reversed_bypass_list_; + // We don't own |env_var_getter_|. It's safe to hold a pointer to it, since + // both it and us are owned by ProxyConfigServiceLinux::Delegate, and have the + // same lifetime. + base::Environment* env_var_getter_; + + // We cache these settings whenever we re-read the kioslaverc file. + string_map_type string_table_; + strings_map_type strings_table_; + + // Task runner for doing blocking file IO on, as well as handling inotify + // events on. + scoped_refptr<base::SequencedTaskRunner> file_task_runner_; + + DISALLOW_COPY_AND_ASSIGN(SettingGetterImplKDE); +}; + +} // namespace + +bool ProxyConfigServiceLinux::Delegate::GetProxyFromSettings( + SettingGetter::StringSetting host_key, + ProxyServer* result_server) { + std::string host; + if (!setting_getter_->GetString(host_key, &host) || host.empty()) { + // Unset or empty. + return false; + } + // Check for an optional port. + int port = 0; + SettingGetter::IntSetting port_key = + SettingGetter::HostSettingToPortSetting(host_key); + setting_getter_->GetInt(port_key, &port); + if (port != 0) { + // If a port is set and non-zero: + host += ":" + base::IntToString(port); + } + + // gsettings settings do not appear to distinguish between SOCKS version. We + // default to version 5. For more information on this policy decision, see: + // http://code.google.com/p/chromium/issues/detail?id=55912#c2 + ProxyServer::Scheme scheme = (host_key == SettingGetter::PROXY_SOCKS_HOST) ? + ProxyServer::SCHEME_SOCKS5 : ProxyServer::SCHEME_HTTP; + host = FixupProxyHostScheme(scheme, host); + ProxyServer proxy_server = ProxyServer::FromURI(host, + ProxyServer::SCHEME_HTTP); + if (proxy_server.is_valid()) { + *result_server = proxy_server; + return true; + } + return false; +} + +base::Optional<ProxyConfig> +ProxyConfigServiceLinux::Delegate::GetConfigFromSettings() { + base::Optional<ProxyConfig> config; + config.emplace(); + + std::string mode; + if (!setting_getter_->GetString(SettingGetter::PROXY_MODE, &mode)) { + // We expect this to always be set, so if we don't see it then we probably + // have a gsettings problem, and so we don't have a valid proxy config. + return base::Optional<ProxyConfig>(); + } + if (mode == "none") { + // Specifically specifies no proxy. + return config; + } + + if (mode == "auto") { + // Automatic proxy config. + std::string pac_url_str; + if (setting_getter_->GetString(SettingGetter::PROXY_AUTOCONF_URL, + &pac_url_str)) { + if (!pac_url_str.empty()) { + // If the PAC URL is actually a file path, then put file:// in front. + if (pac_url_str[0] == '/') + pac_url_str = "file://" + pac_url_str; + GURL pac_url(pac_url_str); + if (!pac_url.is_valid()) + return base::Optional<ProxyConfig>(); + config->set_pac_url(pac_url); + return config; + } + } + config->set_auto_detect(true); + return config; + } + + if (mode != "manual") { + // Mode is unrecognized. + return base::Optional<ProxyConfig>(); + } + bool use_http_proxy; + if (setting_getter_->GetBool(SettingGetter::PROXY_USE_HTTP_PROXY, + &use_http_proxy) + && !use_http_proxy) { + // Another master switch for some reason. If set to false, then no + // proxy. But we don't panic if the key doesn't exist. + return config; + } + + bool same_proxy = false; + // Indicates to use the http proxy for all protocols. This one may + // not exist (presumably on older versions); we assume false in that + // case. + setting_getter_->GetBool(SettingGetter::PROXY_USE_SAME_PROXY, + &same_proxy); + + ProxyServer proxy_for_http; + ProxyServer proxy_for_https; + ProxyServer proxy_for_ftp; + ProxyServer socks_proxy; // (socks) + + // This counts how many of the above ProxyServers were defined and valid. + size_t num_proxies_specified = 0; + + // Extract the per-scheme proxies. If we failed to parse it, or no proxy was + // specified for the scheme, then the resulting ProxyServer will be invalid. + if (GetProxyFromSettings(SettingGetter::PROXY_HTTP_HOST, &proxy_for_http)) + num_proxies_specified++; + if (GetProxyFromSettings(SettingGetter::PROXY_HTTPS_HOST, &proxy_for_https)) + num_proxies_specified++; + if (GetProxyFromSettings(SettingGetter::PROXY_FTP_HOST, &proxy_for_ftp)) + num_proxies_specified++; + if (GetProxyFromSettings(SettingGetter::PROXY_SOCKS_HOST, &socks_proxy)) + num_proxies_specified++; + + if (same_proxy) { + if (proxy_for_http.is_valid()) { + // Use the http proxy for all schemes. + config->proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST; + config->proxy_rules().single_proxies.SetSingleProxyServer(proxy_for_http); + } + } else if (num_proxies_specified > 0) { + if (socks_proxy.is_valid() && num_proxies_specified == 1) { + // If the only proxy specified was for SOCKS, use it for all schemes. + config->proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST; + config->proxy_rules().single_proxies.SetSingleProxyServer(socks_proxy); + } else { + // Otherwise use the indicated proxies per-scheme. + config->proxy_rules().type = + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME; + config->proxy_rules().proxies_for_http. + SetSingleProxyServer(proxy_for_http); + config->proxy_rules().proxies_for_https. + SetSingleProxyServer(proxy_for_https); + config->proxy_rules().proxies_for_ftp.SetSingleProxyServer(proxy_for_ftp); + config->proxy_rules().fallback_proxies.SetSingleProxyServer(socks_proxy); + } + } + + if (config->proxy_rules().empty()) { + // Manual mode but we couldn't parse any rules. + return base::Optional<ProxyConfig>(); + } + + // Check for authentication, just so we can warn. + bool use_auth = false; + setting_getter_->GetBool(SettingGetter::PROXY_USE_AUTHENTICATION, + &use_auth); + if (use_auth) { + // ProxyConfig does not support authentication parameters, but + // Chrome will prompt for the password later. So we ignore + // /system/http_proxy/*auth* settings. + LOG(WARNING) << "Proxy authentication parameters ignored, see bug 16709"; + } + + // Now the bypass list. + std::vector<std::string> ignore_hosts_list; + config->proxy_rules().bypass_rules.Clear(); + if (setting_getter_->GetStringList(SettingGetter::PROXY_IGNORE_HOSTS, + &ignore_hosts_list)) { + std::vector<std::string>::const_iterator it(ignore_hosts_list.begin()); + for (; it != ignore_hosts_list.end(); ++it) { + if (setting_getter_->MatchHostsUsingSuffixMatching()) { + config->proxy_rules().bypass_rules. + AddRuleFromStringUsingSuffixMatching(*it); + } else { + config->proxy_rules().bypass_rules.AddRuleFromString(*it); + } + } + } + // Note that there are no settings with semantics corresponding to + // bypass of local names in GNOME. In KDE, "<local>" is supported + // as a hostname rule. + + // KDE allows one to reverse the bypass rules. + config->proxy_rules().reverse_bypass = + setting_getter_->BypassListIsReversed(); + + return config; +} + +ProxyConfigServiceLinux::Delegate::Delegate( + std::unique_ptr<base::Environment> env_var_getter) + : env_var_getter_(std::move(env_var_getter)) { + // Figure out which SettingGetterImpl to use, if any. + switch (base::nix::GetDesktopEnvironment(env_var_getter_.get())) { + case base::nix::DESKTOP_ENVIRONMENT_CINNAMON: + case base::nix::DESKTOP_ENVIRONMENT_GNOME: + case base::nix::DESKTOP_ENVIRONMENT_PANTHEON: + case base::nix::DESKTOP_ENVIRONMENT_UNITY: +#if defined(USE_GIO) + { + std::unique_ptr<SettingGetterImplGSettings> gs_getter( + new SettingGetterImplGSettings()); + // We have to load symbols and check the GNOME version in use to decide + // if we should use the gsettings getter. See CheckVersion(). + if (gs_getter->CheckVersion(env_var_getter_.get())) + setting_getter_ = std::move(gs_getter); + } +#endif + break; + case base::nix::DESKTOP_ENVIRONMENT_KDE3: + case base::nix::DESKTOP_ENVIRONMENT_KDE4: + case base::nix::DESKTOP_ENVIRONMENT_KDE5: + setting_getter_.reset(new SettingGetterImplKDE(env_var_getter_.get())); + break; + case base::nix::DESKTOP_ENVIRONMENT_XFCE: + case base::nix::DESKTOP_ENVIRONMENT_OTHER: + break; + } +} + +ProxyConfigServiceLinux::Delegate::Delegate( + std::unique_ptr<base::Environment> env_var_getter, + SettingGetter* setting_getter) + : env_var_getter_(std::move(env_var_getter)), + setting_getter_(setting_getter) {} + +void ProxyConfigServiceLinux::Delegate::SetUpAndFetchInitialConfig( + const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner, + const scoped_refptr<base::SequencedTaskRunner>& main_task_runner) { + // We should be running on the default glib main loop thread right + // now. gsettings can only be accessed from this thread. + DCHECK(glib_task_runner->RunsTasksInCurrentSequence()); + glib_task_runner_ = glib_task_runner; + main_task_runner_ = main_task_runner; + + // If we are passed a NULL |main_task_runner|, then don't set up proxy + // setting change notifications. This should not be the usual case but is + // intended to/ simplify test setups. + if (!main_task_runner_.get()) + VLOG(1) << "Monitoring of proxy setting changes is disabled"; + + // Fetch and cache the current proxy config. The config is left in + // cached_config_, where GetLatestProxyConfig() running on the main TaskRunner + // will expect to find it. This is safe to do because we return + // before this ProxyConfigServiceLinux is passed on to + // the ProxyResolutionService. + + // Note: It would be nice to prioritize environment variables + // and only fall back to gsettings if env vars were unset. But + // gnome-terminal "helpfully" sets http_proxy and no_proxy, and it + // does so even if the proxy mode is set to auto, which would + // mislead us. + + cached_config_ = base::Optional<ProxyConfig>(); + if (setting_getter_ && setting_getter_->Init(glib_task_runner)) { + cached_config_ = GetConfigFromSettings(); + } + if (cached_config_) { + cached_config_->set_source(setting_getter_->GetConfigSource()); + VLOG(1) << "Obtained proxy settings from " + << ProxyConfigSourceToString(cached_config_->source()); + + // If gsettings proxy mode is "none", meaning direct, then we take + // that to be a valid config and will not check environment + // variables. The alternative would have been to look for a proxy + // wherever we can find one. + + // Keep a copy of the config for use from this thread for + // comparison with updated settings when we get notifications. + reference_config_ = cached_config_; + + // We only set up notifications if we have the main and file loops + // available. We do this after getting the initial configuration so that we + // don't have to worry about cancelling it if the initial fetch above fails. + // Note that setting up notifications has the side effect of simulating a + // change, so that we won't lose any updates that may have happened after + // the initial fetch and before setting up notifications. We'll detect the + // common case of no changes in OnCheckProxyConfigSettings() (or sooner) and + // ignore it. + if (main_task_runner.get()) { + scoped_refptr<base::SequencedTaskRunner> required_loop = + setting_getter_->GetNotificationTaskRunner(); + if (!required_loop.get() || required_loop->RunsTasksInCurrentSequence()) { + // In this case we are already on an acceptable thread. + SetUpNotifications(); + } else { + // Post a task to set up notifications. We don't wait for success. + required_loop->PostTask(FROM_HERE, base::Bind( + &ProxyConfigServiceLinux::Delegate::SetUpNotifications, this)); + } + } + } + + if (!cached_config_) { + // We fall back on environment variables. + // + // Consulting environment variables doesn't need to be done from the + // default glib main loop, but it's a tiny enough amount of work. + cached_config_ = GetConfigFromEnv(); + if (cached_config_) { + cached_config_->set_source(PROXY_CONFIG_SOURCE_ENV); + VLOG(1) << "Obtained proxy settings from environment variables"; + } + } +} + +// Depending on the SettingGetter in use, this method will be called +// on either the UI thread (GSettings) or the file thread (KDE). +void ProxyConfigServiceLinux::Delegate::SetUpNotifications() { + scoped_refptr<base::SequencedTaskRunner> required_loop = + setting_getter_->GetNotificationTaskRunner(); + DCHECK(!required_loop.get() || required_loop->RunsTasksInCurrentSequence()); + if (!setting_getter_->SetUpNotifications(this)) + LOG(ERROR) << "Unable to set up proxy configuration change notifications"; +} + +void ProxyConfigServiceLinux::Delegate::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void ProxyConfigServiceLinux::Delegate::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +ProxyConfigService::ConfigAvailability + ProxyConfigServiceLinux::Delegate::GetLatestProxyConfig( + ProxyConfig* config) { + // This is called from the main TaskRunner. + DCHECK(!main_task_runner_.get() || + main_task_runner_->RunsTasksInCurrentSequence()); + + // Simply return the last proxy configuration that glib_default_loop + // notified us of. + *config = GetConfigOrDirect(cached_config_); + + // We return CONFIG_VALID to indicate that *config was filled in. It is always + // going to be available since we initialized eagerly on the UI thread. + // TODO(eroman): do lazy initialization instead, so we no longer need + // to construct ProxyConfigServiceLinux on the UI thread. + // In which case, we may return false here. + return CONFIG_VALID; +} + +// Depending on the SettingGetter in use, this method will be called +// on either the UI thread (GSettings) or the file thread (KDE). +void ProxyConfigServiceLinux::Delegate::OnCheckProxyConfigSettings() { + scoped_refptr<base::SequencedTaskRunner> required_loop = + setting_getter_->GetNotificationTaskRunner(); + DCHECK(!required_loop.get() || required_loop->RunsTasksInCurrentSequence()); + base::Optional<ProxyConfig> new_config = GetConfigFromSettings(); + + // See if it is different from what we had before. + if (new_config.has_value() != reference_config_.has_value() || + !new_config->Equals(*reference_config_)) { + // Post a task to the main TaskRunner with the new configuration, so it can + // update |cached_config_|. + main_task_runner_->PostTask( + FROM_HERE, + base::Bind(&ProxyConfigServiceLinux::Delegate::SetNewProxyConfig, this, + new_config)); + // Update the thread-private copy in |reference_config_| as well. + reference_config_ = new_config; + } else { + VLOG(1) << "Detected no-op change to proxy settings. Doing nothing."; + } +} + +void ProxyConfigServiceLinux::Delegate::SetNewProxyConfig( + const base::Optional<ProxyConfig>& new_config) { + DCHECK(main_task_runner_->RunsTasksInCurrentSequence()); + VLOG(1) << "Proxy configuration changed"; + cached_config_ = new_config; + for (auto& observer : observers_) { + observer.OnProxyConfigChanged(GetConfigOrDirect(new_config), + ProxyConfigService::CONFIG_VALID); + } +} + +void ProxyConfigServiceLinux::Delegate::PostDestroyTask() { + if (!setting_getter_) + return; + + scoped_refptr<base::SequencedTaskRunner> shutdown_loop = + setting_getter_->GetNotificationTaskRunner(); + if (!shutdown_loop.get() || shutdown_loop->RunsTasksInCurrentSequence()) { + // Already on the right thread, call directly. + // This is the case for the unittests. + OnDestroy(); + } else { + // Post to shutdown thread. Note that on browser shutdown, we may quit + // this MessageLoop and exit the program before ever running this. + shutdown_loop->PostTask(FROM_HERE, base::Bind( + &ProxyConfigServiceLinux::Delegate::OnDestroy, this)); + } +} +void ProxyConfigServiceLinux::Delegate::OnDestroy() { + scoped_refptr<base::SequencedTaskRunner> shutdown_loop = + setting_getter_->GetNotificationTaskRunner(); + DCHECK(!shutdown_loop.get() || shutdown_loop->RunsTasksInCurrentSequence()); + setting_getter_->ShutDown(); +} + +ProxyConfigServiceLinux::ProxyConfigServiceLinux() + : delegate_(new Delegate(base::Environment::Create())) { +} + +ProxyConfigServiceLinux::~ProxyConfigServiceLinux() { + delegate_->PostDestroyTask(); +} + +ProxyConfigServiceLinux::ProxyConfigServiceLinux( + std::unique_ptr<base::Environment> env_var_getter) + : delegate_(new Delegate(std::move(env_var_getter))) {} + +ProxyConfigServiceLinux::ProxyConfigServiceLinux( + std::unique_ptr<base::Environment> env_var_getter, + SettingGetter* setting_getter) + : delegate_(new Delegate(std::move(env_var_getter), setting_getter)) {} + +void ProxyConfigServiceLinux::AddObserver(Observer* observer) { + delegate_->AddObserver(observer); +} + +void ProxyConfigServiceLinux::RemoveObserver(Observer* observer) { + delegate_->RemoveObserver(observer); +} + +ProxyConfigService::ConfigAvailability + ProxyConfigServiceLinux::GetLatestProxyConfig(ProxyConfig* config) { + return delegate_->GetLatestProxyConfig(config); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config_service_linux.h b/chromium/net/proxy_resolution/proxy_config_service_linux.h new file mode 100644 index 00000000000..1debdc13f39 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_linux.h @@ -0,0 +1,315 @@ +// Copyright (c) 2012 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_RESOLUTION_PROXY_CONFIG_SERVICE_LINUX_H_ +#define NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_LINUX_H_ + +#include <stddef.h> + +#include <memory> +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/environment.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "base/optional.h" +#include "net/base/net_export.h" +#include "net/base/proxy_server.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_config_service.h" + +namespace base { +class SingleThreadTaskRunner; +class SequencedTaskRunner; +} // namespace base + +namespace net { + +// Implementation of ProxyConfigService that retrieves the system proxy +// settings from environment variables, gconf, gsettings, or kioslaverc (KDE). +class NET_EXPORT_PRIVATE ProxyConfigServiceLinux : public ProxyConfigService { + public: + class Delegate; + + class SettingGetter { + public: + // Buffer size used in some implementations of this class when reading + // files. Defined here so unit tests can construct worst-case inputs. + static const size_t BUFFER_SIZE = 512; + + SettingGetter() {} + virtual ~SettingGetter() {} + + // Initializes the class: obtains a gconf/gsettings client, or simulates + // one, in the concrete implementations. Returns true on success. Must be + // called before using other methods, and should be called on the thread + // running the glib main loop. + // This interface supports both GNOME and KDE implementations. In the + // case of GNOME, the glib_task_runner will be used for interacting with + // gconf/gsettings as those APIs have thread affinity. Whereas in the case + // of KDE, its configuration files will be monitored at well-known locations + // and glib_task_runner will not be used. Instead, blocking file I/O + // operations will be posted directly using the task scheduler. + virtual bool Init(const scoped_refptr<base::SingleThreadTaskRunner>& + glib_task_runner) = 0; + + // Releases the gconf/gsettings client, which clears cached directories and + // stops notifications. + virtual void ShutDown() = 0; + + // Requests notification of gconf/gsettings changes for proxy + // settings. Returns true on success. + virtual bool SetUpNotifications(Delegate* delegate) = 0; + + // Returns the message loop for the thread on which this object + // handles notifications, and also on which it must be destroyed. + // Returns NULL if it does not matter. + virtual const scoped_refptr<base::SequencedTaskRunner>& + GetNotificationTaskRunner() = 0; + + // Returns the source of proxy settings. + virtual ProxyConfigSource GetConfigSource() = 0; + + // These are all the values that can be fetched. We used to just use the + // corresponding paths in gconf for these, but gconf is now obsolete and + // in the future we'll be using mostly gsettings/kioslaverc so we + // enumerate them instead to avoid unnecessary string operations. + enum StringSetting { + PROXY_MODE, + PROXY_AUTOCONF_URL, + PROXY_HTTP_HOST, + PROXY_HTTPS_HOST, + PROXY_FTP_HOST, + PROXY_SOCKS_HOST, + }; + enum BoolSetting { + PROXY_USE_HTTP_PROXY, + PROXY_USE_SAME_PROXY, + PROXY_USE_AUTHENTICATION, + }; + enum IntSetting { + PROXY_HTTP_PORT, + PROXY_HTTPS_PORT, + PROXY_FTP_PORT, + PROXY_SOCKS_PORT, + }; + enum StringListSetting { + PROXY_IGNORE_HOSTS, + }; + + // Given a PROXY_*_HOST value, return the corresponding PROXY_*_PORT value. + static IntSetting HostSettingToPortSetting(StringSetting host) { + switch (host) { + case PROXY_HTTP_HOST: + return PROXY_HTTP_PORT; + case PROXY_HTTPS_HOST: + return PROXY_HTTPS_PORT; + case PROXY_FTP_HOST: + return PROXY_FTP_PORT; + case PROXY_SOCKS_HOST: + return PROXY_SOCKS_PORT; + default: + NOTREACHED(); + return PROXY_HTTP_PORT; // Placate compiler. + } + } + + // Gets a string type value from the data source and stores it in + // |*result|. Returns false if the key is unset or on error. Must only be + // called after a successful call to Init(), and not after a failed call + // to SetUpNotifications() or after calling Release(). + virtual bool GetString(StringSetting key, std::string* result) = 0; + // Same thing for a bool typed value. + virtual bool GetBool(BoolSetting key, bool* result) = 0; + // Same for an int typed value. + virtual bool GetInt(IntSetting key, int* result) = 0; + // And for a string list. + virtual bool GetStringList(StringListSetting key, + std::vector<std::string>* result) = 0; + + // Returns true if the bypass list should be interpreted as a proxy + // whitelist rather than blacklist. (This is KDE-specific.) + virtual bool BypassListIsReversed() = 0; + + // Returns true if the bypass rules should be interpreted as + // suffix-matching rules. + virtual bool MatchHostsUsingSuffixMatching() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(SettingGetter); + }; + + // ProxyConfigServiceLinux is created on the glib thread, and + // SetUpAndFetchInitialConfig() is immediately called to synchronously + // fetch the original configuration and set up change notifications on + // the ProxyConfigService's main SequencedTaskRunner, which is passed to its + // constructor (Which may or may not run tasks on the glib thread). + // + // Past that point, it is accessed periodically through the + // ProxyConfigService interface (GetLatestProxyConfig, AddObserver, + // RemoveObserver) from the main TaskRunner. + // + // Setting change notification callbacks can occur at any time and are + // run on either the glib thread (gconf/gsettings) or a separate file thread + // (KDE). The new settings are fetched on that thread, and the resulting proxy + // config is posted to the main TaskRunner through + // Delegate::SetNewProxyConfig(). We then notify observers on that TaskRunner + // of the configuration change. + // + // ProxyConfigServiceLinux is deleted from the main TaskRunner. + // + // The substance of the ProxyConfigServiceLinux implementation is + // wrapped in the Delegate ref counted class. On deleting the + // ProxyConfigServiceLinux, Delegate::OnDestroy() is posted to either + // the glib thread (gconf/gsettings) or a file thread (KDE) where change + // notifications will be safely stopped before releasing Delegate. + + class Delegate : public base::RefCountedThreadSafe<Delegate> { + public: + // Normal constructor. + explicit Delegate(std::unique_ptr<base::Environment> env_var_getter); + + // Constructor for testing. + Delegate(std::unique_ptr<base::Environment> env_var_getter, + SettingGetter* setting_getter); + + // Synchronously obtains the proxy configuration. If gconf, + // gsettings, or kioslaverc are used, also enables notifications for + // setting changes. gconf/gsettings must only be accessed from the + // thread running the default glib main loop, and so this method + // must be called from the glib thread. The message loop for the + // ProxyConfigService's main SequencedTaskRunner is specified so that + // notifications can post tasks to it (and for assertions). + void SetUpAndFetchInitialConfig( + const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner, + const scoped_refptr<base::SequencedTaskRunner>& main_task_runner); + + // Handler for setting change notifications: fetches a new proxy + // configuration from settings, and if this config is different + // than what we had before, posts a task to have it stored in + // cached_config_. + // Left public for simplicity. + void OnCheckProxyConfigSettings(); + + // Called on the service's main TaskRunner. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + ProxyConfigService::ConfigAvailability GetLatestProxyConfig( + ProxyConfig* config); + + // Posts a call to OnDestroy() to the glib or a file task runner, + // depending on the setting getter in use. Called from + // ProxyConfigServiceLinux's destructor. + void PostDestroyTask(); + // Safely stops change notifications. Posted to either the glib thread or + // sequenced task runner, depending on the setting getter in use. + void OnDestroy(); + + private: + friend class base::RefCountedThreadSafe<Delegate>; + + ~Delegate(); + + // Obtains an environment variable's value. Parses a proxy server + // specification from it and puts it in result. Returns true if the + // requested variable is defined and the value valid. + bool GetProxyFromEnvVarForScheme(base::StringPiece variable, + ProxyServer::Scheme scheme, + ProxyServer* result_server); + // As above but with scheme set to HTTP, for convenience. + bool GetProxyFromEnvVar(base::StringPiece variable, + ProxyServer* result_server); + // Returns a proxy config based on the environment variables, or empty value + // on failure. + base::Optional<ProxyConfig> GetConfigFromEnv(); + + // Obtains host and port config settings and parses a proxy server + // specification from it and puts it in result. Returns true if the + // requested variable is defined and the value valid. + bool GetProxyFromSettings(SettingGetter::StringSetting host_key, + ProxyServer* result_server); + // Returns a proxy config based on the settings, or empty value + // on failure. + base::Optional<ProxyConfig> GetConfigFromSettings(); + + // This method is posted from the glib thread to the main TaskRunner to + // carry the new config information. + void SetNewProxyConfig(const base::Optional<ProxyConfig>& new_config); + + // This method is run on the getter's notification thread. + void SetUpNotifications(); + + std::unique_ptr<base::Environment> env_var_getter_; + std::unique_ptr<SettingGetter> setting_getter_; + + // Cached proxy configuration, to be returned by + // GetLatestProxyConfig. Initially populated from the glib thread, but + // afterwards only accessed from the main TaskRunner. + base::Optional<ProxyConfig> cached_config_; + + // A copy kept on the glib thread of the last seen proxy config, so as + // to avoid posting a call to SetNewProxyConfig when we get a + // notification but the config has not actually changed. + base::Optional<ProxyConfig> reference_config_; + + // The task runner for the glib thread, aka main browser thread. This thread + // is where we run the glib main loop (see + // base/message_loop/message_pump_glib.h). It is the glib default loop in + // the sense that it runs the glib default context: as in the context where + // sources are added by g_timeout_add and g_idle_add, and returned by + // g_main_context_default. gconf uses glib timeouts and idles and possibly + // other callbacks that will all be dispatched on this thread. Since gconf + // is not thread safe, any use of gconf must be done on the thread running + // this loop. + scoped_refptr<base::SingleThreadTaskRunner> glib_task_runner_; + // Task runner for the main TaskRunner. GetLatestProxyConfig() is called + // from the thread running this loop. + scoped_refptr<base::SequencedTaskRunner> main_task_runner_; + + base::ObserverList<Observer> observers_; + + DISALLOW_COPY_AND_ASSIGN(Delegate); + }; + + // Thin wrapper shell around Delegate. + + // Usual constructor + ProxyConfigServiceLinux(); + + // For testing: take alternate setting and env var getter implementations. + explicit ProxyConfigServiceLinux( + std::unique_ptr<base::Environment> env_var_getter); + ProxyConfigServiceLinux(std::unique_ptr<base::Environment> env_var_getter, + SettingGetter* setting_getter); + + ~ProxyConfigServiceLinux() override; + + void SetupAndFetchInitialConfig( + const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner, + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) { + delegate_->SetUpAndFetchInitialConfig(glib_task_runner, io_task_runner); + } + void OnCheckProxyConfigSettings() { + delegate_->OnCheckProxyConfigSettings(); + } + + // ProxyConfigService methods: + // Called from main TaskRunner. + void AddObserver(Observer* observer) override; + void RemoveObserver(Observer* observer) override; + ProxyConfigService::ConfigAvailability GetLatestProxyConfig( + ProxyConfig* config) override; + + private: + scoped_refptr<Delegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(ProxyConfigServiceLinux); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_LINUX_H_ diff --git a/chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc b/chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc new file mode 100644 index 00000000000..7aec908af44 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc @@ -0,0 +1,1912 @@ +// Copyright (c) 2012 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_resolution/proxy_config_service_linux.h" + +#include <map> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/format_macros.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "base/threading/thread_task_runner_handle.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_config_service_common_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +// TODO(eroman): Convert these to parameterized tests using TEST_P(). + +namespace net { +namespace { + +// Set of values for all environment variables that we might +// query. NULL represents an unset variable. +struct EnvVarValues { + // The strange capitalization is so that the field matches the + // environment variable name exactly. + const char* DESKTOP_SESSION; + const char* HOME; + const char* KDEHOME; + const char* KDE_SESSION_VERSION; + const char* XDG_CURRENT_DESKTOP; + const char* auto_proxy; + const char* all_proxy; + const char* http_proxy; + const char* https_proxy; + const char* ftp_proxy; + const char* SOCKS_SERVER; + const char* SOCKS_VERSION; + const char* no_proxy; +}; + +// Undo macro pollution from GDK includes (from message_loop.h). +#undef TRUE +#undef FALSE + +// So as to distinguish between an unset boolean variable and +// one that is false. +enum BoolSettingValue { UNSET = 0, TRUE, FALSE }; + +// Set of values for all gsettings settings that we might query. +struct GSettingsValues { + // strings + const char* mode; + const char* autoconfig_url; + const char* http_host; + const char* secure_host; + const char* ftp_host; + const char* socks_host; + // integers + int http_port; + int secure_port; + int ftp_port; + int socks_port; + // booleans + BoolSettingValue use_proxy; + BoolSettingValue same_proxy; + BoolSettingValue use_auth; + // string list + std::vector<std::string> ignore_hosts; +}; + +// Mapping from a setting name to the location of the corresponding +// value (inside a EnvVarValues or GSettingsValues struct). +template <typename key_type, typename value_type> +struct SettingsTable { + typedef std::map<key_type, value_type*> map_type; + + // Gets the value from its location + value_type Get(key_type key) { + auto it = settings.find(key); + // In case there's a typo or the unittest becomes out of sync. + CHECK(it != settings.end()) << "key " << key << " not found"; + value_type* value_ptr = it->second; + return *value_ptr; + } + + map_type settings; +}; + +class MockEnvironment : public base::Environment { + public: + MockEnvironment() { +#define ENTRY(x) table_[#x] = &values.x + ENTRY(DESKTOP_SESSION); + ENTRY(HOME); + ENTRY(KDEHOME); + ENTRY(KDE_SESSION_VERSION); + ENTRY(XDG_CURRENT_DESKTOP); + ENTRY(auto_proxy); + ENTRY(all_proxy); + ENTRY(http_proxy); + ENTRY(https_proxy); + ENTRY(ftp_proxy); + ENTRY(no_proxy); + ENTRY(SOCKS_SERVER); + ENTRY(SOCKS_VERSION); +#undef ENTRY + Reset(); + } + + // Zeroes all environment values. + void Reset() { + EnvVarValues zero_values = {0}; + values = zero_values; + } + + // Begin base::Environment implementation. + bool GetVar(base::StringPiece variable_name, std::string* result) override { + auto it = table_.find(variable_name); + if (it == table_.end() || !*it->second) + return false; + + // Note that the variable may be defined but empty. + *result = *(it->second); + return true; + } + + bool SetVar(base::StringPiece variable_name, + const std::string& new_value) override { + ADD_FAILURE(); + return false; + } + + bool UnSetVar(base::StringPiece variable_name) override { + ADD_FAILURE(); + return false; + } + // End base::Environment implementation. + + // Intentionally public, for convenience when setting up a test. + EnvVarValues values; + + private: + std::map<base::StringPiece, const char**> table_; +}; + +class MockSettingGetter : public ProxyConfigServiceLinux::SettingGetter { + public: + typedef ProxyConfigServiceLinux::SettingGetter SettingGetter; + MockSettingGetter() { +#define ENTRY(key, field) \ + strings_table.settings[SettingGetter::key] = &values.field + ENTRY(PROXY_MODE, mode); + ENTRY(PROXY_AUTOCONF_URL, autoconfig_url); + ENTRY(PROXY_HTTP_HOST, http_host); + ENTRY(PROXY_HTTPS_HOST, secure_host); + ENTRY(PROXY_FTP_HOST, ftp_host); + ENTRY(PROXY_SOCKS_HOST, socks_host); +#undef ENTRY +#define ENTRY(key, field) \ + ints_table.settings[SettingGetter::key] = &values.field + ENTRY(PROXY_HTTP_PORT, http_port); + ENTRY(PROXY_HTTPS_PORT, secure_port); + ENTRY(PROXY_FTP_PORT, ftp_port); + ENTRY(PROXY_SOCKS_PORT, socks_port); +#undef ENTRY +#define ENTRY(key, field) \ + bools_table.settings[SettingGetter::key] = &values.field + ENTRY(PROXY_USE_HTTP_PROXY, use_proxy); + ENTRY(PROXY_USE_SAME_PROXY, same_proxy); + ENTRY(PROXY_USE_AUTHENTICATION, use_auth); +#undef ENTRY + string_lists_table.settings[SettingGetter::PROXY_IGNORE_HOSTS] = + &values.ignore_hosts; + Reset(); + } + + // Zeros all environment values. + void Reset() { + GSettingsValues zero_values = {0}; + values = zero_values; + } + + bool Init(const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner) + override { + task_runner_ = glib_task_runner; + return true; + } + + void ShutDown() override {} + + bool SetUpNotifications( + ProxyConfigServiceLinux::Delegate* delegate) override { + return true; + } + + const scoped_refptr<base::SequencedTaskRunner>& GetNotificationTaskRunner() + override { + return task_runner_; + } + + ProxyConfigSource GetConfigSource() override { + return PROXY_CONFIG_SOURCE_TEST; + } + + bool GetString(StringSetting key, std::string* result) override { + const char* value = strings_table.Get(key); + if (value) { + *result = value; + return true; + } + return false; + } + + bool GetBool(BoolSetting key, bool* result) override { + BoolSettingValue value = bools_table.Get(key); + switch (value) { + case UNSET: + return false; + case TRUE: + *result = true; + break; + case FALSE: + *result = false; + } + return true; + } + + bool GetInt(IntSetting key, int* result) override { + // We don't bother to distinguish unset keys from 0 values. + *result = ints_table.Get(key); + return true; + } + + bool GetStringList(StringListSetting key, + std::vector<std::string>* result) override { + *result = string_lists_table.Get(key); + // We don't bother to distinguish unset keys from empty lists. + return !result->empty(); + } + + bool BypassListIsReversed() override { return false; } + + bool MatchHostsUsingSuffixMatching() override { return false; } + + // Intentionally public, for convenience when setting up a test. + GSettingsValues values; + + private: + scoped_refptr<base::SequencedTaskRunner> task_runner_; + SettingsTable<StringSetting, const char*> strings_table; + SettingsTable<BoolSetting, BoolSettingValue> bools_table; + SettingsTable<IntSetting, int> ints_table; + SettingsTable<StringListSetting, std::vector<std::string>> string_lists_table; +}; + +// This helper class runs ProxyConfigServiceLinux::GetLatestProxyConfig() on +// the main TaskRunner and synchronously waits for the result. +// Some code duplicated from proxy_script_fetcher_unittest.cc. +class SyncConfigGetter : public ProxyConfigService::Observer { + public: + // Takes ownership of |config_service|. + explicit SyncConfigGetter(ProxyConfigServiceLinux* config_service) + : event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED), + main_thread_("Main_Thread"), + config_service_(config_service), + matches_pac_url_event_( + base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED) { + // Start the main IO thread. + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_IO; + main_thread_.StartWithOptions(options); + + // Make sure the thread started. + main_thread_.task_runner()->PostTask( + FROM_HERE, base::Bind(&SyncConfigGetter::Init, base::Unretained(this))); + Wait(); + } + + ~SyncConfigGetter() override { + // Clean up the main thread. + main_thread_.task_runner()->PostTask( + FROM_HERE, + base::Bind(&SyncConfigGetter::CleanUp, base::Unretained(this))); + Wait(); + } + + // Does gsettings setup and initial fetch of the proxy config, + // all on the calling thread (meant to be the thread with the + // default glib main loop, which is the glib thread). + void SetupAndInitialFetch() { + config_service_->SetupAndFetchInitialConfig( + base::ThreadTaskRunnerHandle::Get(), main_thread_.task_runner()); + } + // Synchronously gets the proxy config. + ProxyConfigService::ConfigAvailability SyncGetLatestProxyConfig( + ProxyConfig* config) { + main_thread_.task_runner()->PostTask( + FROM_HERE, base::Bind(&SyncConfigGetter::GetLatestConfigOnIOThread, + base::Unretained(this))); + Wait(); + *config = proxy_config_; + return get_latest_config_result_; + } + + // Instructs |matches_pac_url_event_| to be signalled once the configuration + // changes to |pac_url|. The way to use this function is: + // + // SetExpectedPacUrl(..); + // WriteFile(...) + // WaitUntilPacUrlMatchesExpectation(); + // + // The expectation must be set *before* any file-level mutation is done, + // otherwise the change may be received before + // WaitUntilPacUrlMatchesExpectation(), and subsequently be lost. + void SetExpectedPacUrl(const std::string& pac_url) { + base::AutoLock lock(lock_); + expected_pac_url_ = GURL(pac_url); + } + + // Blocks until the proxy config service has received a configuration + // matching the value previously passed to SetExpectedPacUrl(). + void WaitUntilPacUrlMatchesExpectation() { + matches_pac_url_event_.Wait(); + matches_pac_url_event_.Reset(); + } + + private: + void OnProxyConfigChanged( + const ProxyConfig& config, + ProxyConfigService::ConfigAvailability availability) override { + // If the configuration changed to |expected_pac_url_| signal the event. + base::AutoLock lock(lock_); + if (config.has_pac_url() && config.pac_url() == expected_pac_url_) { + expected_pac_url_ = GURL(); + matches_pac_url_event_.Signal(); + } + } + + // [Runs on |main_thread_|] + void Init() { + config_service_->AddObserver(this); + event_.Signal(); + } + + // Calls GetLatestProxyConfig, running on |main_thread_| Signals |event_| + // on completion. + void GetLatestConfigOnIOThread() { + get_latest_config_result_ = + config_service_->GetLatestProxyConfig(&proxy_config_); + event_.Signal(); + } + + // [Runs on |main_thread_|] Signals |event_| on cleanup completion. + void CleanUp() { + config_service_->RemoveObserver(this); + delete config_service_; + base::RunLoop().RunUntilIdle(); + event_.Signal(); + } + + void Wait() { + event_.Wait(); + event_.Reset(); + } + + base::WaitableEvent event_; + base::Thread main_thread_; + + ProxyConfigServiceLinux* config_service_; + + // The config obtained by |main_thread_| and read back by the main + // thread. + ProxyConfig proxy_config_; + + // Return value from GetLatestProxyConfig(). + ProxyConfigService::ConfigAvailability get_latest_config_result_; + + // If valid, |expected_pac_url_| is the URL that is being waited for in + // the proxy configuration. The URL should only be accessed while |lock_| + // is held. Once a configuration arrives for |expected_pac_url_| then the + // event |matches_pac_url_event_| will be signalled. + base::Lock lock_; + GURL expected_pac_url_; + base::WaitableEvent matches_pac_url_event_; +}; + +// This test fixture is only really needed for the KDEConfigParser test case, +// but all the test cases with the same prefix ("ProxyConfigServiceLinuxTest") +// must use the same test fixture class (also "ProxyConfigServiceLinuxTest"). +class ProxyConfigServiceLinuxTest : public PlatformTest { + protected: + void SetUp() override { + PlatformTest::SetUp(); + // Set up a temporary KDE home directory. + std::string prefix("ProxyConfigServiceLinuxTest_user_home"); + base::CreateNewTempDirectory(prefix, &user_home_); + config_home_ = user_home_.Append(FILE_PATH_LITERAL(".config")); + kde_home_ = user_home_.Append(FILE_PATH_LITERAL(".kde")); + base::FilePath path = kde_home_.Append(FILE_PATH_LITERAL("share")); + path = path.Append(FILE_PATH_LITERAL("config")); + base::CreateDirectory(path); + kioslaverc_ = path.Append(FILE_PATH_LITERAL("kioslaverc")); + // Set up paths but do not create the directory for .kde4. + kde4_home_ = user_home_.Append(FILE_PATH_LITERAL(".kde4")); + path = kde4_home_.Append(FILE_PATH_LITERAL("share")); + kde4_config_ = path.Append(FILE_PATH_LITERAL("config")); + kioslaverc4_ = kde4_config_.Append(FILE_PATH_LITERAL("kioslaverc")); + // Set up paths for KDE 5 + kioslaverc5_ = config_home_.Append(FILE_PATH_LITERAL("kioslaverc")); + } + + void TearDown() override { + // Delete the temporary KDE home directory. + base::DeleteFile(user_home_, true); + PlatformTest::TearDown(); + } + + base::FilePath user_home_; + base::FilePath config_home_; + // KDE3 paths. + base::FilePath kde_home_; + base::FilePath kioslaverc_; + // KDE4 paths. + base::FilePath kde4_home_; + base::FilePath kde4_config_; + base::FilePath kioslaverc4_; + // KDE5 paths. + base::FilePath kioslaverc5_; +}; + +// Builds an identifier for each test in an array. +#define TEST_DESC(desc) base::StringPrintf("at line %d <%s>", __LINE__, desc) + +TEST_F(ProxyConfigServiceLinuxTest, BasicGSettingsTest) { + std::vector<std::string> empty_ignores; + + std::vector<std::string> google_ignores; + google_ignores.push_back("*.google.com"); + + // Inspired from proxy_config_service_win_unittest.cc. + // Very neat, but harder to track down failures though. + const struct { + // Short description to identify the test + std::string description; + + // Input. + GSettingsValues values; + + // Expected outputs (availability and fields of ProxyConfig). + ProxyConfigService::ConfigAvailability availability; + bool auto_detect; + GURL pac_url; + ProxyRulesExpectation proxy_rules; + } tests[] = { + { + TEST_DESC("No proxying"), + { + // Input. + "none", // mode + "", // autoconfig_url + "", "", "", "", // hosts + 0, 0, 0, 0, // ports + FALSE, FALSE, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Auto detect"), + { + // Input. + "auto", // mode + "", // autoconfig_url + "", "", "", "", // hosts + 0, 0, 0, 0, // ports + FALSE, FALSE, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + true, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Valid PAC URL"), + { + // Input. + "auto", // mode + "http://wpad/wpad.dat", // autoconfig_url + "", "", "", "", // hosts + 0, 0, 0, 0, // ports + FALSE, FALSE, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL("http://wpad/wpad.dat"), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Invalid PAC URL"), + { + // Input. + "auto", // mode + "wpad.dat", // autoconfig_url + "", "", "", "", // hosts + 0, 0, 0, 0, // ports + FALSE, FALSE, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Single-host in proxy list"), + { + // Input. + "manual", // mode + "", // autoconfig_url + "www.google.com", "", "", "", // hosts + 80, 0, 0, 0, // ports + TRUE, TRUE, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single("www.google.com:80", // single proxy + ""), // bypass rules + }, + + { + TEST_DESC("use_http_proxy is honored"), + { + // Input. + "manual", // mode + "", // autoconfig_url + "www.google.com", "", "", "", // hosts + 80, 0, 0, 0, // ports + FALSE, TRUE, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("use_http_proxy and use_same_proxy are optional"), + { + // Input. + "manual", // mode + "", // autoconfig_url + "www.google.com", "", "", "", // hosts + 80, 0, 0, 0, // ports + UNSET, UNSET, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "", // ftp + ""), // bypass rules + }, + + { + TEST_DESC("Single-host, different port"), + { + // Input. + "manual", // mode + "", // autoconfig_url + "www.google.com", "", "", "", // hosts + 88, 0, 0, 0, // ports + TRUE, TRUE, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single("www.google.com:88", // single proxy + ""), // bypass rules + }, + + { + TEST_DESC("Per-scheme proxy rules"), + { + // Input. + "manual", // mode + "", // autoconfig_url + "www.google.com", // http_host + "www.foo.com", // secure_host + "ftp.foo.com", // ftp + "", // socks + 88, 110, 121, 0, // ports + TRUE, FALSE, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:88", // http + "www.foo.com:110", // https + "ftp.foo.com:121", // ftp + ""), // bypass rules + }, + + { + TEST_DESC("socks"), + { + // Input. + "manual", // mode + "", // autoconfig_url + "", "", "", "socks.com", // hosts + 0, 0, 0, 99, // ports + TRUE, FALSE, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single( + "socks5://socks.com:99", // single proxy + "") // bypass rules + }, + + { + TEST_DESC("Per-scheme proxy rules with fallback to SOCKS"), + { + // Input. + "manual", // mode + "", // autoconfig_url + "www.google.com", // http_host + "www.foo.com", // secure_host + "ftp.foo.com", // ftp + "foobar.net", // socks + 88, 110, 121, 99, // ports + TRUE, FALSE, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerSchemeWithSocks( + "www.google.com:88", // http + "www.foo.com:110", // https + "ftp.foo.com:121", // ftp + "socks5://foobar.net:99", // socks + ""), // bypass rules + }, + + { + TEST_DESC( + "Per-scheme proxy rules (just HTTP) with fallback to SOCKS"), + { + // Input. + "manual", // mode + "", // autoconfig_url + "www.google.com", // http_host + "", // secure_host + "", // ftp + "foobar.net", // socks + 88, 0, 0, 99, // ports + TRUE, FALSE, FALSE, // use, same, auth + empty_ignores, // ignore_hosts + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerSchemeWithSocks( + "www.google.com:88", // http + "", // https + "", // ftp + "socks5://foobar.net:99", // socks + ""), // bypass rules + }, + + { + TEST_DESC("Bypass *.google.com"), + { + // Input. + "manual", // mode + "", // autoconfig_url + "www.google.com", "", "", "", // hosts + 80, 0, 0, 0, // ports + TRUE, TRUE, FALSE, // use, same, auth + google_ignores, // ignore_hosts + }, + + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single("www.google.com:80", // single proxy + "*.google.com"), // bypass rules + }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, + tests[i].description.c_str())); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + MockSettingGetter* setting_getter = new MockSettingGetter; + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env), setting_getter)); + ProxyConfig config; + setting_getter->values = tests[i].values; + sync_config_getter.SetupAndInitialFetch(); + ProxyConfigService::ConfigAvailability availability = + sync_config_getter.SyncGetLatestProxyConfig(&config); + EXPECT_EQ(tests[i].availability, availability); + + if (availability == ProxyConfigService::CONFIG_VALID) { + EXPECT_EQ(tests[i].auto_detect, config.auto_detect()); + EXPECT_EQ(tests[i].pac_url, config.pac_url()); + EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules())); + } + } +} + +TEST_F(ProxyConfigServiceLinuxTest, BasicEnvTest) { + // Inspired from proxy_config_service_win_unittest.cc. + const struct { + // Short description to identify the test + std::string description; + + // Input. + EnvVarValues values; + + // Expected outputs (availability and fields of ProxyConfig). + ProxyConfigService::ConfigAvailability availability; + bool auto_detect; + GURL pac_url; + ProxyRulesExpectation proxy_rules; + } tests[] = { + { + TEST_DESC("No proxying"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + nullptr, // auto_proxy + nullptr, // all_proxy + nullptr, nullptr, nullptr, // per-proto proxies + nullptr, nullptr, // SOCKS + "*", // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Auto detect"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + "", // auto_proxy + nullptr, // all_proxy + nullptr, nullptr, nullptr, // per-proto proxies + nullptr, nullptr, // SOCKS + nullptr, // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + true, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Valid PAC URL"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + "http://wpad/wpad.dat", // auto_proxy + nullptr, // all_proxy + nullptr, nullptr, nullptr, // per-proto proxies + nullptr, nullptr, // SOCKS + nullptr, // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL("http://wpad/wpad.dat"), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Invalid PAC URL"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + "wpad.dat", // auto_proxy + nullptr, // all_proxy + nullptr, nullptr, nullptr, // per-proto proxies + nullptr, nullptr, // SOCKS + nullptr, // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Single-host in proxy list"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + nullptr, // auto_proxy + "www.google.com", // all_proxy + nullptr, nullptr, nullptr, // per-proto proxies + nullptr, nullptr, // SOCKS + nullptr, // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single("www.google.com:80", // single proxy + ""), // bypass rules + }, + + { + TEST_DESC("Single-host, different port"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + nullptr, // auto_proxy + "www.google.com:99", // all_proxy + nullptr, nullptr, nullptr, // per-proto proxies + nullptr, nullptr, // SOCKS + nullptr, // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single("www.google.com:99", // single + ""), // bypass rules + }, + + { + TEST_DESC("Tolerate a scheme"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + nullptr, // auto_proxy + "http://www.google.com:99", // all_proxy + nullptr, nullptr, nullptr, // per-proto proxies + nullptr, nullptr, // SOCKS + nullptr, // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single("www.google.com:99", // single proxy + ""), // bypass rules + }, + + { + TEST_DESC("Per-scheme proxy rules"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + nullptr, // auto_proxy + nullptr, // all_proxy + "www.google.com:80", "www.foo.com:110", + "ftp.foo.com:121", // per-proto + nullptr, nullptr, // SOCKS + nullptr, // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "www.foo.com:110", // https + "ftp.foo.com:121", // ftp + ""), // bypass rules + }, + + { + TEST_DESC("socks"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + nullptr, // auto_proxy + "", // all_proxy + nullptr, nullptr, nullptr, // per-proto proxies + "socks.com:888", nullptr, // SOCKS + nullptr, // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single( + "socks5://socks.com:888", // single proxy + ""), // bypass rules + }, + + { + TEST_DESC("socks4"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + nullptr, // auto_proxy + "", // all_proxy + nullptr, nullptr, nullptr, // per-proto proxies + "socks.com:888", "4", // SOCKS + nullptr, // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single( + "socks4://socks.com:888", // single proxy + ""), // bypass rules + }, + + { + TEST_DESC("socks default port"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + nullptr, // auto_proxy + "", // all_proxy + nullptr, nullptr, nullptr, // per-proto proxies + "socks.com", nullptr, // SOCKS + nullptr, // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single( + "socks5://socks.com:1080", // single proxy + ""), // bypass rules + }, + + { + TEST_DESC("bypass"), + { + // Input. + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + nullptr, // auto_proxy + "www.google.com", // all_proxy + nullptr, nullptr, nullptr, // per-proto + nullptr, nullptr, // SOCKS + ".google.com, foo.com:99, 1.2.3.4:22, 127.0.0.1/8", // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single( + "www.google.com:80", + "*.google.com,*foo.com:99,1.2.3.4:22,127.0.0.1/8"), + }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, + tests[i].description.c_str())); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values = tests[i].values; + MockSettingGetter* setting_getter = new MockSettingGetter; + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env), setting_getter)); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + ProxyConfigService::ConfigAvailability availability = + sync_config_getter.SyncGetLatestProxyConfig(&config); + EXPECT_EQ(tests[i].availability, availability); + + if (availability == ProxyConfigService::CONFIG_VALID) { + EXPECT_EQ(tests[i].auto_detect, config.auto_detect()); + EXPECT_EQ(tests[i].pac_url, config.pac_url()); + EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules())); + } + } +} + +TEST_F(ProxyConfigServiceLinuxTest, GSettingsNotification) { + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + MockSettingGetter* setting_getter = new MockSettingGetter; + ProxyConfigServiceLinux* service = + new ProxyConfigServiceLinux(std::move(env), setting_getter); + SyncConfigGetter sync_config_getter(service); + ProxyConfig config; + + // Start with no proxy. + setting_getter->values.mode = "none"; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_FALSE(config.auto_detect()); + + // Now set to auto-detect. + setting_getter->values.mode = "auto"; + // Simulate setting change notification callback. + service->OnCheckProxyConfigSettings(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.auto_detect()); +} + +TEST_F(ProxyConfigServiceLinuxTest, KDEConfigParser) { + // One of the tests below needs a worst-case long line prefix. We build it + // programmatically so that it will always be the right size. + std::string long_line; + size_t limit = ProxyConfigServiceLinux::SettingGetter::BUFFER_SIZE - 1; + for (size_t i = 0; i < limit; ++i) + long_line += "-"; + + // Inspired from proxy_config_service_win_unittest.cc. + const struct { + // Short description to identify the test + std::string description; + + // Input. + std::string kioslaverc; + EnvVarValues env_values; + + // Expected outputs (availability and fields of ProxyConfig). + ProxyConfigService::ConfigAvailability availability; + bool auto_detect; + GURL pac_url; + ProxyRulesExpectation proxy_rules; + } tests[] = { + { + TEST_DESC("No proxying"), + + // Input. + "[Proxy Settings]\nProxyType=0\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + { + TEST_DESC("Invalid proxy type (ProxyType=-3)"), + + // Input. + "[Proxy Settings]\nProxyType=-3\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Invalid proxy type (ProxyType=AB-)"), + + // Input. + "[Proxy Settings]\nProxyType=AB-\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Auto detect"), + + // Input. + "[Proxy Settings]\nProxyType=3\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + true, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Valid PAC URL"), + + // Input. + "[Proxy Settings]\nProxyType=2\n" + "Proxy Config Script=http://wpad/wpad.dat\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL("http://wpad/wpad.dat"), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Valid PAC file without file://"), + + // Input. + "[Proxy Settings]\nProxyType=2\n" + "Proxy Config Script=/wpad/wpad.dat\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL("file:///wpad/wpad.dat"), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Per-scheme proxy rules"), + + // Input. + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" + "httpsProxy=www.foo.com\nftpProxy=ftp.foo.com\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "www.foo.com:80", // https + "ftp.foo.com:80", // http + ""), // bypass rules + }, + + { + TEST_DESC("Only HTTP proxy specified"), + + // Input. + "[Proxy Settings]\nProxyType=1\n" + "httpProxy=www.google.com\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "", // ftp + ""), // bypass rules + }, + + { + TEST_DESC("Only HTTP proxy specified, different port"), + + // Input. + "[Proxy Settings]\nProxyType=1\n" + "httpProxy=www.google.com:88\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:88", // http + "", // https + "", // ftp + ""), // bypass rules + }, + + { + TEST_DESC( + "Only HTTP proxy specified, different port, space-delimited"), + + // Input. + "[Proxy Settings]\nProxyType=1\n" + "httpProxy=www.google.com 88\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:88", // http + "", // https + "", // ftp + ""), // bypass rules + }, + + { + TEST_DESC("Bypass *.google.com"), + + // Input. + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" + "NoProxyFor=.google.com\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "", // ftp + "*.google.com"), // bypass rules + }, + + { + TEST_DESC("Bypass *.google.com and *.kde.org"), + + // Input. + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" + "NoProxyFor=.google.com,.kde.org\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme( + "www.google.com:80", // http + "", // https + "", // ftp + "*.google.com,*.kde.org"), // bypass rules + }, + + { + TEST_DESC("Correctly parse bypass list with ReversedException=true"), + + // Input. + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" + "NoProxyFor=.google.com\nReversedException=true\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerSchemeWithBypassReversed( + "www.google.com:80", // http + "", // https + "", // ftp + "*.google.com"), // bypass rules + }, + + { + TEST_DESC("Correctly parse bypass list with ReversedException=false"), + + // Input. + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" + "NoProxyFor=.google.com\nReversedException=false\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "", // ftp + "*.google.com"), // bypass rules + }, + + { + TEST_DESC("Correctly parse bypass list with ReversedException=1"), + + // Input. + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" + "NoProxyFor=.google.com\nReversedException=1\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerSchemeWithBypassReversed( + "www.google.com:80", // http + "", // https + "", // ftp + "*.google.com"), // bypass rules + }, + + { + TEST_DESC("Overflow: ReversedException=18446744073709551617"), + + // Input. + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" + "NoProxyFor=.google.com\nReversedException=18446744073709551617\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "", // ftp + "*.google.com"), // bypass rules + }, + + { + TEST_DESC("Not a number: ReversedException=noitpecxE"), + + // Input. + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" + "NoProxyFor=.google.com\nReversedException=noitpecxE\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "", // ftp + "*.google.com"), // bypass rules + }, + + { + TEST_DESC("socks"), + + // Input. + "[Proxy Settings]\nProxyType=1\nsocksProxy=socks.com 888\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single( + "socks5://socks.com:888", // single proxy + ""), // bypass rules + }, + + { + TEST_DESC("socks4"), + + // Input. + "[Proxy Settings]\nProxyType=1\nsocksProxy=socks4://socks.com 888\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single( + "socks4://socks.com:888", // single proxy + ""), // bypass rules + }, + + { + TEST_DESC("Treat all hostname patterns as wildcard patterns"), + + // Input. + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" + "NoProxyFor=google.com,kde.org,<local>\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme( + "www.google.com:80", // http + "", // https + "", // ftp + "*google.com,*kde.org,<local>"), // bypass rules + }, + + { + TEST_DESC("Allow trailing whitespace after boolean value"), + + // Input. + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" + "NoProxyFor=.google.com\nReversedException=true \n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerSchemeWithBypassReversed( + "www.google.com:80", // http + "", // https + "", // ftp + "*.google.com"), // bypass rules + }, + + { + TEST_DESC("Ignore settings outside [Proxy Settings]"), + + // Input. + "httpsProxy=www.foo.com\n[Proxy Settings]\nProxyType=1\n" + "httpProxy=www.google.com\n[Other Section]\nftpProxy=ftp.foo.com\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "", // ftp + ""), // bypass rules + }, + + { + TEST_DESC("Handle CRLF line endings"), + + // Input. + "[Proxy Settings]\r\nProxyType=1\r\nhttpProxy=www.google.com\r\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "", // ftp + ""), // bypass rules + }, + + { + TEST_DESC("Handle blank lines and mixed line endings"), + + // Input. + "[Proxy Settings]\r\n\nProxyType=1\n\r\nhttpProxy=www.google.com\n\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "", // ftp + ""), // bypass rules + }, + + { + TEST_DESC("Handle localized settings"), + + // Input. + "[Proxy Settings]\nProxyType[$e]=1\nhttpProxy[$e]=www.google.com\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "", // ftp + ""), // bypass rules + }, + + { + TEST_DESC("Ignore malformed localized settings"), + + // Input. + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" + "httpsProxy$e]=www.foo.com\nftpProxy=ftp.foo.com\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "ftp.foo.com:80", // ftp + ""), // bypass rules + }, + + { + TEST_DESC("Handle strange whitespace"), + + // Input. + "[Proxy Settings]\nProxyType [$e] =2\n" + " Proxy Config Script = http:// foo\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL("http:// foo"), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Ignore all of a line which is too long"), + + // Input. + std::string("[Proxy Settings]\nProxyType=1\nftpProxy=ftp.foo.com\n") + + long_line + "httpsProxy=www.foo.com\nhttpProxy=www.google.com\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "ftp.foo.com:80", // ftp + ""), // bypass rules + }, + + { + TEST_DESC("Indirect Proxy - no env vars set"), + + // Input. + "[Proxy Settings]\nProxyType=4\nhttpProxy=http_proxy\n" + "httpsProxy=https_proxy\nftpProxy=ftp_proxy\nNoProxyFor=no_proxy\n", + {}, // env_values + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + { + TEST_DESC("Indirect Proxy - with env vars set"), + + // Input. + "[Proxy Settings]\nProxyType=4\nhttpProxy=http_proxy\n" + "httpsProxy=https_proxy\nftpProxy=ftp_proxy\nNoProxyFor=no_proxy\n", + { + // env_values + nullptr, // DESKTOP_SESSION + nullptr, // HOME + nullptr, // KDEHOME + nullptr, // KDE_SESSION_VERSION + nullptr, // XDG_CURRENT_DESKTOP + nullptr, // auto_proxy + nullptr, // all_proxy + "www.normal.com", // http_proxy + "www.secure.com", // https_proxy + "ftp.foo.com", // ftp_proxy + nullptr, nullptr, // SOCKS + ".google.com, .kde.org", // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme( + "www.normal.com:80", // http + "www.secure.com:80", // https + "ftp.foo.com:80", // ftp + "*.google.com,*.kde.org"), // bypass rules + }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, + tests[i].description.c_str())); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values = tests[i].env_values; + // Force the KDE getter to be used and tell it where the test is. + env->values.DESKTOP_SESSION = "kde4"; + env->values.KDEHOME = kde_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + // Overwrite the kioslaverc file. + base::WriteFile(kioslaverc_, tests[i].kioslaverc.c_str(), + tests[i].kioslaverc.length()); + sync_config_getter.SetupAndInitialFetch(); + ProxyConfigService::ConfigAvailability availability = + sync_config_getter.SyncGetLatestProxyConfig(&config); + EXPECT_EQ(tests[i].availability, availability); + + if (availability == ProxyConfigService::CONFIG_VALID) { + EXPECT_EQ(tests[i].auto_detect, config.auto_detect()); + EXPECT_EQ(tests[i].pac_url, config.pac_url()); + EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules())); + } + } +} + +TEST_F(ProxyConfigServiceLinuxTest, KDEHomePicker) { + // Auto detect proxy settings. + std::string slaverc3 = "[Proxy Settings]\nProxyType=3\n"; + // Valid PAC URL. + std::string slaverc4 = + "[Proxy Settings]\nProxyType=2\n" + "Proxy Config Script=http://wpad/wpad.dat\n"; + GURL slaverc4_pac_url("http://wpad/wpad.dat"); + // Basic HTTP proxy setting. + std::string slaverc5 = + "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com 80\n"; + ProxyRulesExpectation slaverc5_rules = + ProxyRulesExpectation::PerScheme("www.google.com:80", // http + "", // https + "", // ftp + ""); // bypass rules + + // Overwrite the .kde kioslaverc file. + base::WriteFile(kioslaverc_, slaverc3.c_str(), slaverc3.length()); + + // If .kde4 exists it will mess up the first test. It should not, as + // we created the directory for $HOME in the test setup. + CHECK(!base::DirectoryExists(kde4_home_)); + + { + SCOPED_TRACE("KDE4, no .kde4 directory, verify fallback"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.DESKTOP_SESSION = "kde4"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.auto_detect()); + EXPECT_EQ(GURL(), config.pac_url()); + } + + // Now create .kde4 and put a kioslaverc in the config directory. + // Note that its timestamp will be at least as new as the .kde one. + base::CreateDirectory(kde4_config_); + base::WriteFile(kioslaverc4_, slaverc4.c_str(), slaverc4.length()); + CHECK(base::PathExists(kioslaverc4_)); + + { + SCOPED_TRACE("KDE4, .kde4 directory present, use it"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.DESKTOP_SESSION = "kde4"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_FALSE(config.auto_detect()); + EXPECT_EQ(slaverc4_pac_url, config.pac_url()); + } + + { + SCOPED_TRACE("KDE3, .kde4 directory present, ignore it"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.DESKTOP_SESSION = "kde"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.auto_detect()); + EXPECT_EQ(GURL(), config.pac_url()); + } + + { + SCOPED_TRACE("KDE4, .kde4 directory present, KDEHOME set to .kde"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.DESKTOP_SESSION = "kde4"; + env->values.HOME = user_home_.value().c_str(); + env->values.KDEHOME = kde_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.auto_detect()); + EXPECT_EQ(GURL(), config.pac_url()); + } + + // Finally, make the .kde4 config directory older than the .kde directory + // and make sure we then use .kde instead of .kde4 since it's newer. + base::TouchFile(kde4_config_, base::Time(), base::Time()); + + { + SCOPED_TRACE("KDE4, very old .kde4 directory present, use .kde"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.DESKTOP_SESSION = "kde4"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.auto_detect()); + EXPECT_EQ(GURL(), config.pac_url()); + } + + // For KDE 5 create ${HOME}/.config and put a kioslaverc in the directory. + base::CreateDirectory(config_home_); + base::WriteFile(kioslaverc5_, slaverc5.c_str(), slaverc5.length()); + CHECK(base::PathExists(kioslaverc5_)); + + { + SCOPED_TRACE("KDE5, .kde and .kde4 present, use .config"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.XDG_CURRENT_DESKTOP = "KDE"; + env->values.KDE_SESSION_VERSION = "5"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_FALSE(config.auto_detect()); + EXPECT_TRUE(slaverc5_rules.Matches(config.proxy_rules())); + } +} + +void WriteFile(const base::FilePath& path, base::StringPiece data) { + EXPECT_TRUE(base::WriteFile(path, data.data(), data.size())); +} + +// Tests that the KDE proxy config service watches for file and directory +// changes. +TEST_F(ProxyConfigServiceLinuxTest, KDEFileChanged) { + // Set up the initial .kde kioslaverc file. + WriteFile(kioslaverc_, + "[Proxy Settings]\nProxyType=2\n" + "Proxy Config Script=http://version1/wpad.dat\n"); + + // Initialize the config service using kioslaverc. + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.DESKTOP_SESSION = "kde4"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.has_pac_url()); + EXPECT_EQ(GURL("http://version1/wpad.dat"), config.pac_url()); + + //----------------------------------------------------- + + // Change the kioslaverc file by overwriting it. Verify that the change was + // observed. + sync_config_getter.SetExpectedPacUrl("http://version2/wpad.dat"); + + WriteFile(kioslaverc_, + "[Proxy Settings]\nProxyType=2\n" + "Proxy Config Script=http://version2/wpad.dat\n"); + + // Wait for change to be noticed. + sync_config_getter.WaitUntilPacUrlMatchesExpectation(); + + //----------------------------------------------------- + + // Change the kioslaverc file by renaming it. If only the file's inode + // were being watched (rather than directory) this will not result in + // an observable change. Note that KDE when re-writing proxy settings does + // so by renaming a new file, so the inode will change. + sync_config_getter.SetExpectedPacUrl("http://version3/wpad.dat"); + + // Create a new file, and rename it into place. + WriteFile(kioslaverc_.AddExtension("new"), + "[Proxy Settings]\nProxyType=2\n" + "Proxy Config Script=http://version3/wpad.dat\n"); + base::Move(kioslaverc_, kioslaverc_.AddExtension("old")); + base::Move(kioslaverc_.AddExtension("new"), kioslaverc_); + + // Wait for change to be noticed. + sync_config_getter.WaitUntilPacUrlMatchesExpectation(); + + //----------------------------------------------------- + + // Change the kioslaverc file once more by ovewriting it. This is really + // just another test to make sure things still work after the directory + // change was observed (this final test probably isn't very useful). + sync_config_getter.SetExpectedPacUrl("http://version4/wpad.dat"); + + WriteFile(kioslaverc_, + "[Proxy Settings]\nProxyType=2\n" + "Proxy Config Script=http://version4/wpad.dat\n"); + + // Wait for change to be noticed. + sync_config_getter.WaitUntilPacUrlMatchesExpectation(); + + //----------------------------------------------------- + + // TODO(eroman): Add a test where kioslaverc is deleted next. Currently this + // doesn't trigger any notifications, but it probably should. +} + +} // namespace + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config_service_mac.cc b/chromium/net/proxy_resolution/proxy_config_service_mac.cc new file mode 100644 index 00000000000..f236aa473af --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_mac.cc @@ -0,0 +1,284 @@ +// Copyright (c) 2012 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_resolution/proxy_config_service_mac.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <SystemConfiguration/SystemConfiguration.h> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/sys_string_conversions.h" +#include "net/base/net_errors.h" +#include "net/base/proxy_server.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_info.h" + +namespace net { + +namespace { + +// Utility function to pull out a boolean value from a dictionary and return it, +// returning a default value if the key is not present. +bool GetBoolFromDictionary(CFDictionaryRef dict, + CFStringRef key, + bool default_value) { + CFNumberRef number = base::mac::GetValueFromDictionary<CFNumberRef>(dict, + key); + if (!number) + return default_value; + + int int_value; + if (CFNumberGetValue(number, kCFNumberIntType, &int_value)) + return int_value; + else + return default_value; +} + +void GetCurrentProxyConfig(ProxyConfig* config) { + base::ScopedCFTypeRef<CFDictionaryRef> config_dict( + SCDynamicStoreCopyProxies(NULL)); + DCHECK(config_dict); + + // auto-detect + + // There appears to be no UI for this configuration option, and we're not sure + // if Apple's proxy code even takes it into account. But the constant is in + // the header file so we'll use it. + config->set_auto_detect( + GetBoolFromDictionary(config_dict.get(), + kSCPropNetProxiesProxyAutoDiscoveryEnable, + false)); + + // PAC file + + if (GetBoolFromDictionary(config_dict.get(), + kSCPropNetProxiesProxyAutoConfigEnable, + false)) { + CFStringRef pac_url_ref = base::mac::GetValueFromDictionary<CFStringRef>( + config_dict.get(), kSCPropNetProxiesProxyAutoConfigURLString); + if (pac_url_ref) + config->set_pac_url(GURL(base::SysCFStringRefToUTF8(pac_url_ref))); + } + + // proxies (for now ftp, http, https, and SOCKS) + + if (GetBoolFromDictionary(config_dict.get(), + kSCPropNetProxiesFTPEnable, + false)) { + ProxyServer proxy_server = + ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP, + config_dict.get(), + kSCPropNetProxiesFTPProxy, + kSCPropNetProxiesFTPPort); + if (proxy_server.is_valid()) { + config->proxy_rules().type = + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME; + config->proxy_rules().proxies_for_ftp.SetSingleProxyServer(proxy_server); + } + } + if (GetBoolFromDictionary(config_dict.get(), + kSCPropNetProxiesHTTPEnable, + false)) { + ProxyServer proxy_server = + ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP, + config_dict.get(), + kSCPropNetProxiesHTTPProxy, + kSCPropNetProxiesHTTPPort); + if (proxy_server.is_valid()) { + config->proxy_rules().type = + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME; + config->proxy_rules().proxies_for_http.SetSingleProxyServer(proxy_server); + } + } + if (GetBoolFromDictionary(config_dict.get(), + kSCPropNetProxiesHTTPSEnable, + false)) { + ProxyServer proxy_server = + ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP, + config_dict.get(), + kSCPropNetProxiesHTTPSProxy, + kSCPropNetProxiesHTTPSPort); + if (proxy_server.is_valid()) { + config->proxy_rules().type = + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME; + config->proxy_rules().proxies_for_https. + SetSingleProxyServer(proxy_server); + } + } + if (GetBoolFromDictionary(config_dict.get(), + kSCPropNetProxiesSOCKSEnable, + false)) { + ProxyServer proxy_server = + ProxyServer::FromDictionary(ProxyServer::SCHEME_SOCKS5, + config_dict.get(), + kSCPropNetProxiesSOCKSProxy, + kSCPropNetProxiesSOCKSPort); + if (proxy_server.is_valid()) { + config->proxy_rules().type = + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME; + config->proxy_rules().fallback_proxies.SetSingleProxyServer(proxy_server); + } + } + + // proxy bypass list + + CFArrayRef bypass_array_ref = base::mac::GetValueFromDictionary<CFArrayRef>( + config_dict.get(), kSCPropNetProxiesExceptionsList); + if (bypass_array_ref) { + CFIndex bypass_array_count = CFArrayGetCount(bypass_array_ref); + for (CFIndex i = 0; i < bypass_array_count; ++i) { + CFStringRef bypass_item_ref = base::mac::CFCast<CFStringRef>( + CFArrayGetValueAtIndex(bypass_array_ref, i)); + if (!bypass_item_ref) { + LOG(WARNING) << "Expected value for item " << i + << " in the kSCPropNetProxiesExceptionsList" + " to be a CFStringRef but it was not"; + + } else { + config->proxy_rules().bypass_rules.AddRuleFromString( + base::SysCFStringRefToUTF8(bypass_item_ref)); + } + } + } + + // proxy bypass boolean + + if (GetBoolFromDictionary(config_dict.get(), + kSCPropNetProxiesExcludeSimpleHostnames, + false)) { + config->proxy_rules().bypass_rules.AddRuleToBypassLocal(); + } + + // Source + config->set_source(PROXY_CONFIG_SOURCE_SYSTEM); +} + +} // namespace + +// Reference-counted helper for posting a task to +// ProxyConfigServiceMac::OnProxyConfigChanged between the notifier and IO +// thread. This helper object may outlive the ProxyConfigServiceMac. +class ProxyConfigServiceMac::Helper + : public base::RefCountedThreadSafe<ProxyConfigServiceMac::Helper> { + public: + explicit Helper(ProxyConfigServiceMac* parent) : parent_(parent) { + DCHECK(parent); + } + + // Called when the parent is destroyed. + void Orphan() { + parent_ = NULL; + } + + void OnProxyConfigChanged(const ProxyConfig& new_config) { + if (parent_) + parent_->OnProxyConfigChanged(new_config); + } + + private: + friend class base::RefCountedThreadSafe<Helper>; + ~Helper() {} + + ProxyConfigServiceMac* parent_; +}; + +void ProxyConfigServiceMac::Forwarder::SetDynamicStoreNotificationKeys( + SCDynamicStoreRef store) { + proxy_config_service_->SetDynamicStoreNotificationKeys(store); +} + +void ProxyConfigServiceMac::Forwarder::OnNetworkConfigChange( + CFArrayRef changed_keys) { + proxy_config_service_->OnNetworkConfigChange(changed_keys); +} + +ProxyConfigServiceMac::ProxyConfigServiceMac( + const scoped_refptr<base::SequencedTaskRunner>& sequenced_task_runner) + : forwarder_(this), + has_fetched_config_(false), + helper_(new Helper(this)), + sequenced_task_runner_(sequenced_task_runner) { + DCHECK(sequenced_task_runner_.get()); + config_watcher_.reset(new NetworkConfigWatcherMac(&forwarder_)); +} + +ProxyConfigServiceMac::~ProxyConfigServiceMac() { + DCHECK(sequenced_task_runner_->RunsTasksInCurrentSequence()); + // Delete the config_watcher_ to ensure the notifier thread finishes before + // this object is destroyed. + config_watcher_.reset(); + helper_->Orphan(); +} + +void ProxyConfigServiceMac::AddObserver(Observer* observer) { + DCHECK(sequenced_task_runner_->RunsTasksInCurrentSequence()); + observers_.AddObserver(observer); +} + +void ProxyConfigServiceMac::RemoveObserver(Observer* observer) { + DCHECK(sequenced_task_runner_->RunsTasksInCurrentSequence()); + observers_.RemoveObserver(observer); +} + +ProxyConfigService::ConfigAvailability +ProxyConfigServiceMac::GetLatestProxyConfig(ProxyConfig* config) { + DCHECK(sequenced_task_runner_->RunsTasksInCurrentSequence()); + + // Lazy-initialize by fetching the proxy setting from this thread. + if (!has_fetched_config_) { + GetCurrentProxyConfig(&last_config_fetched_); + has_fetched_config_ = true; + } + + *config = last_config_fetched_; + return has_fetched_config_ ? CONFIG_VALID : CONFIG_PENDING; +} + +void ProxyConfigServiceMac::SetDynamicStoreNotificationKeys( + SCDynamicStoreRef store) { + // Called on notifier thread. + + CFStringRef proxies_key = SCDynamicStoreKeyCreateProxies(NULL); + CFArrayRef key_array = CFArrayCreate( + NULL, (const void **)(&proxies_key), 1, &kCFTypeArrayCallBacks); + + bool ret = SCDynamicStoreSetNotificationKeys(store, key_array, NULL); + // TODO(willchan): Figure out a proper way to handle this rather than crash. + CHECK(ret); + + CFRelease(key_array); + CFRelease(proxies_key); +} + +void ProxyConfigServiceMac::OnNetworkConfigChange(CFArrayRef changed_keys) { + // Called on notifier thread. + + // Fetch the new system proxy configuration. + ProxyConfig new_config; + GetCurrentProxyConfig(&new_config); + + // Call OnProxyConfigChanged() on the TakeRunner to notify our observers. + sequenced_task_runner_->PostTask( + FROM_HERE, + base::Bind(&Helper::OnProxyConfigChanged, helper_.get(), new_config)); +} + +void ProxyConfigServiceMac::OnProxyConfigChanged( + const ProxyConfig& new_config) { + DCHECK(sequenced_task_runner_->RunsTasksInCurrentSequence()); + + // Keep track of the last value we have seen. + has_fetched_config_ = true; + last_config_fetched_ = new_config; + + // Notify all the observers. + for (auto& observer : observers_) + observer.OnProxyConfigChanged(new_config, CONFIG_VALID); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config_service_mac.h b/chromium/net/proxy_resolution/proxy_config_service_mac.h new file mode 100644 index 00000000000..6b434f56fd6 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_mac.h @@ -0,0 +1,85 @@ +// Copyright (c) 2012 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_RESOLUTION_PROXY_CONFIG_SERVICE_MAC_H_ +#define NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_MAC_H_ + +#include <memory> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "net/base/network_config_watcher_mac.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_config_service.h" + +namespace base { +class SequencedTaskRunner; +} // namespace base + +namespace net { + +class ProxyConfigServiceMac : public ProxyConfigService { + public: + // Constructs a ProxyConfigService that watches the Mac OS system settings. + // This instance is expected to be operated and deleted on + // |sequenced_task_runner| (however it may be constructed elsewhere). + explicit ProxyConfigServiceMac( + const scoped_refptr<base::SequencedTaskRunner>& sequenced_task_runner); + ~ProxyConfigServiceMac() override; + + public: + // ProxyConfigService implementation: + void AddObserver(Observer* observer) override; + void RemoveObserver(Observer* observer) override; + ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) override; + + private: + class Helper; + + // Forwarder just exists to keep the NetworkConfigWatcherMac API out of + // ProxyConfigServiceMac's public API. + class Forwarder : public NetworkConfigWatcherMac::Delegate { + public: + explicit Forwarder(ProxyConfigServiceMac* proxy_config_service) + : proxy_config_service_(proxy_config_service) {} + + // NetworkConfigWatcherMac::Delegate implementation: + void StartReachabilityNotifications() override {} + void SetDynamicStoreNotificationKeys(SCDynamicStoreRef store) override; + void OnNetworkConfigChange(CFArrayRef changed_keys) override; + + private: + ProxyConfigServiceMac* const proxy_config_service_; + DISALLOW_COPY_AND_ASSIGN(Forwarder); + }; + + // Methods directly called by the NetworkConfigWatcherMac::Delegate: + void SetDynamicStoreNotificationKeys(SCDynamicStoreRef store); + void OnNetworkConfigChange(CFArrayRef changed_keys); + + // Called when the proxy configuration has changed, to notify the observers. + void OnProxyConfigChanged(const ProxyConfig& new_config); + + Forwarder forwarder_; + std::unique_ptr<const NetworkConfigWatcherMac> config_watcher_; + + base::ObserverList<Observer> observers_; + + // Holds the last system proxy settings that we fetched. + bool has_fetched_config_; + ProxyConfig last_config_fetched_; + + scoped_refptr<Helper> helper_; + + // The task runner that |this| will be operated on. + const scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_; + + DISALLOW_COPY_AND_ASSIGN(ProxyConfigServiceMac); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_MAC_H_ diff --git a/chromium/net/proxy_resolution/proxy_config_service_win.cc b/chromium/net/proxy_resolution/proxy_config_service_win.cc new file mode 100644 index 00000000000..b298a13be1e --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_win.cc @@ -0,0 +1,170 @@ +// Copyright (c) 2012 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_resolution/proxy_config_service_win.h" + +#include <windows.h> +#include <winhttp.h> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/strings/string_tokenizer.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" +#include "base/win/registry.h" +#include "base/win/scoped_handle.h" +#include "net/base/net_errors.h" +#include "net/proxy_resolution/proxy_config.h" + +namespace net { + +namespace { + +const int kPollIntervalSec = 10; + +void FreeIEConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* ie_config) { + if (ie_config->lpszAutoConfigUrl) + GlobalFree(ie_config->lpszAutoConfigUrl); + if (ie_config->lpszProxy) + GlobalFree(ie_config->lpszProxy); + if (ie_config->lpszProxyBypass) + GlobalFree(ie_config->lpszProxyBypass); +} + +} // namespace + +ProxyConfigServiceWin::ProxyConfigServiceWin() + : PollingProxyConfigService( + base::TimeDelta::FromSeconds(kPollIntervalSec), + &ProxyConfigServiceWin::GetCurrentProxyConfig) { +} + +ProxyConfigServiceWin::~ProxyConfigServiceWin() { + // The registry functions below will end up going to disk. TODO: Do this on + // another thread to avoid slowing the current thread. http://crbug.com/61453 + base::ThreadRestrictions::ScopedAllowIO allow_io; + keys_to_watch_.clear(); +} + +void ProxyConfigServiceWin::AddObserver(Observer* observer) { + // Lazily-initialize our registry watcher. + StartWatchingRegistryForChanges(); + + // Let the super-class do its work now. + PollingProxyConfigService::AddObserver(observer); +} + +void ProxyConfigServiceWin::StartWatchingRegistryForChanges() { + if (!keys_to_watch_.empty()) + return; // Already initialized. + + // The registry functions below will end up going to disk. Do this on another + // thread to avoid slowing the current thread. http://crbug.com/61453 + base::ThreadRestrictions::ScopedAllowIO allow_io; + + // There are a number of different places where proxy settings can live + // in the registry. In some cases it appears in a binary value, in other + // cases string values. Furthermore winhttp and wininet appear to have + // separate stores, and proxy settings can be configured per-machine + // or per-user. + // + // This function is probably not exhaustive in the registry locations it + // watches for changes, however it should catch the majority of the + // cases. In case we have missed some less common triggers (likely), we + // will catch them during the periodic (10 second) polling, so things + // will recover. + + AddKeyToWatchList( + HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"); + + AddKeyToWatchList( + HKEY_LOCAL_MACHINE, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"); + + AddKeyToWatchList( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Policies\\Microsoft\\Windows\\CurrentVersion\\" + L"Internet Settings"); +} + +bool ProxyConfigServiceWin::AddKeyToWatchList(HKEY rootkey, + const wchar_t* subkey) { + std::unique_ptr<base::win::RegKey> key = + std::make_unique<base::win::RegKey>(); + if (key->Create(rootkey, subkey, KEY_NOTIFY) != ERROR_SUCCESS) + return false; + + if (!key->StartWatching(base::Bind(&ProxyConfigServiceWin::OnObjectSignaled, + base::Unretained(this), + base::Unretained(key.get())))) { + return false; + } + + keys_to_watch_.push_back(std::move(key)); + return true; +} + +void ProxyConfigServiceWin::OnObjectSignaled(base::win::RegKey* key) { + // Figure out which registry key signalled this change. + auto it = std::find_if(keys_to_watch_.begin(), keys_to_watch_.end(), + [key](const std::unique_ptr<base::win::RegKey>& ptr) { + return ptr.get() == key; + }); + DCHECK(it != keys_to_watch_.end()); + + // Keep watching the registry key. + if (!key->StartWatching(base::Bind(&ProxyConfigServiceWin::OnObjectSignaled, + base::Unretained(this), + base::Unretained(key)))) { + keys_to_watch_.erase(it); + } + + // Have the PollingProxyConfigService test for changes. + CheckForChangesNow(); +} + +// static +void ProxyConfigServiceWin::GetCurrentProxyConfig(ProxyConfig* config) { + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_config = {0}; + if (!WinHttpGetIEProxyConfigForCurrentUser(&ie_config)) { + LOG(ERROR) << "WinHttpGetIEProxyConfigForCurrentUser failed: " << + GetLastError(); + *config = ProxyConfig::CreateDirect(); + config->set_source(PROXY_CONFIG_SOURCE_SYSTEM_FAILED); + return; + } + SetFromIEConfig(config, ie_config); + FreeIEConfig(&ie_config); +} + +// static +void ProxyConfigServiceWin::SetFromIEConfig( + ProxyConfig* config, + const WINHTTP_CURRENT_USER_IE_PROXY_CONFIG& ie_config) { + if (ie_config.fAutoDetect) + config->set_auto_detect(true); + if (ie_config.lpszProxy) { + // lpszProxy may be a single proxy, or a proxy per scheme. The format + // is compatible with ProxyConfig::ProxyRules's string format. + config->proxy_rules().ParseFromString( + base::UTF16ToASCII(ie_config.lpszProxy)); + } + if (ie_config.lpszProxyBypass) { + std::string proxy_bypass = base::UTF16ToASCII(ie_config.lpszProxyBypass); + + base::StringTokenizer proxy_server_bypass_list(proxy_bypass, ";, \t\n\r"); + while (proxy_server_bypass_list.GetNext()) { + std::string bypass_url_domain = proxy_server_bypass_list.token(); + config->proxy_rules().bypass_rules.AddRuleFromString(bypass_url_domain); + } + } + if (ie_config.lpszAutoConfigUrl) + config->set_pac_url(GURL(ie_config.lpszAutoConfigUrl)); + config->set_source(PROXY_CONFIG_SOURCE_SYSTEM); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config_service_win.h b/chromium/net/proxy_resolution/proxy_config_service_win.h new file mode 100644 index 00000000000..9ee40c5a1a7 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_win.h @@ -0,0 +1,83 @@ +// 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_RESOLUTION_PROXY_CONFIG_SERVICE_WIN_H_ +#define NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_WIN_H_ + +#include <windows.h> +#include <winhttp.h> + +#include <memory> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/polling_proxy_config_service.h" + +namespace base { +namespace win { +class RegKey; +} +} // namespace base. + +namespace net { + +// Implementation of ProxyConfigService that retrieves the system proxy +// settings. +// +// It works by calling WinHttpGetIEProxyConfigForCurrentUser() to fetch the +// Internet Explorer proxy settings. +// +// We use two different strategies to notice when the configuration has +// changed: +// +// (1) Watch the internet explorer settings registry keys for changes. When +// one of the registry keys pertaining to proxy settings has changed, we +// call WinHttpGetIEProxyConfigForCurrentUser() again to read the +// configuration's new value. +// +// (2) Do regular polling every 10 seconds during network activity to see if +// WinHttpGetIEProxyConfigForCurrentUser() returns something different. +// +// Ideally strategy (1) should be sufficient to pick up all of the changes. +// However we still do the regular polling as a precaution in case the +// implementation details of WinHttpGetIEProxyConfigForCurrentUser() ever +// change, or in case we got it wrong (and are not checking all possible +// registry dependencies). +class NET_EXPORT_PRIVATE ProxyConfigServiceWin + : public PollingProxyConfigService { + public: + ProxyConfigServiceWin(); + ~ProxyConfigServiceWin() override; + + // Overrides a function from PollingProxyConfigService. + void AddObserver(Observer* observer) override; + + private: + FRIEND_TEST_ALL_PREFIXES(ProxyConfigServiceWinTest, SetFromIEConfig); + + // Registers change observers on the registry keys relating to proxy settings. + void StartWatchingRegistryForChanges(); + + // Creates a new key and appends it to |keys_to_watch_|. If the key fails to + // be created, it is not appended to the list and we return false. + bool AddKeyToWatchList(HKEY rootkey, const wchar_t* subkey); + + // This is called whenever one of the registry keys we are watching change. + void OnObjectSignaled(base::win::RegKey* key); + + static void GetCurrentProxyConfig(ProxyConfig* config); + + // Set |config| using the proxy configuration values of |ie_config|. + static void SetFromIEConfig( + ProxyConfig* config, + const WINHTTP_CURRENT_USER_IE_PROXY_CONFIG& ie_config); + + std::vector<std::unique_ptr<base::win::RegKey>> keys_to_watch_; +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_CONFIG_SERVICE_WIN_H_ diff --git a/chromium/net/proxy_resolution/proxy_config_service_win_unittest.cc b/chromium/net/proxy_resolution/proxy_config_service_win_unittest.cc new file mode 100644 index 00000000000..8ea7eb3abe5 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_win_unittest.cc @@ -0,0 +1,215 @@ +// Copyright (c) 2012 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_resolution/proxy_config_service_win.h" + +#include "net/base/net_errors.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_config_service_common_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +TEST(ProxyConfigServiceWinTest, SetFromIEConfig) { + // Like WINHTTP_CURRENT_USER_IE_PROXY_CONFIG, but with const strings. + struct IEProxyConfig { + BOOL auto_detect; + const wchar_t* auto_config_url; + const wchar_t* proxy; + const wchar_t* proxy_bypass; + }; + const struct { + // Input. + IEProxyConfig ie_config; + + // Expected outputs (fields of the ProxyConfig). + bool auto_detect; + GURL pac_url; + ProxyRulesExpectation proxy_rules; + const char* proxy_bypass_list; // newline separated + } tests[] = { + // Auto detect. + { + { // Input. + TRUE, // fAutoDetect + NULL, // lpszAutoConfigUrl + NULL, // lpszProxy + NULL, // lpszProxyBypass + }, + + // Expected result. + true, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + // Valid PAC url + { + { // Input. + FALSE, // fAutoDetect + L"http://wpad/wpad.dat", // lpszAutoConfigUrl + NULL, // lpszProxy + NULL, // lpszProxy_bypass + }, + + // Expected result. + false, // auto_detect + GURL("http://wpad/wpad.dat"), // pac_url + ProxyRulesExpectation::Empty(), + }, + + // Invalid PAC url string. + { + { // Input. + FALSE, // fAutoDetect + L"wpad.dat", // lpszAutoConfigUrl + NULL, // lpszProxy + NULL, // lpszProxy_bypass + }, + + // Expected result. + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Empty(), + }, + + // Single-host in proxy list. + { + { // Input. + FALSE, // fAutoDetect + NULL, // lpszAutoConfigUrl + L"www.google.com", // lpszProxy + NULL, // lpszProxy_bypass + }, + + // Expected result. + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::Single( + "www.google.com:80", // single proxy + ""), // bypass rules + }, + + // Per-scheme proxy rules. + { + { // Input. + FALSE, // fAutoDetect + NULL, // lpszAutoConfigUrl + L"http=www.google.com:80;https=www.foo.com:110", // lpszProxy + NULL, // lpszProxy_bypass + }, + + // Expected result. + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme( + "www.google.com:80", // http + "www.foo.com:110", // https + "", // ftp + ""), // bypass rules + }, + + // SOCKS proxy configuration. + { + { // Input. + FALSE, // fAutoDetect + NULL, // lpszAutoConfigUrl + L"http=www.google.com:80;https=www.foo.com:110;" + L"ftp=ftpproxy:20;socks=foopy:130", // lpszProxy + NULL, // lpszProxy_bypass + }, + + // Expected result. + // Note that "socks" is interprted as meaning "socks4", since that is how + // Internet Explorer applies the settings. For more details on this + // policy, see: + // http://code.google.com/p/chromium/issues/detail?id=55912#c2 + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerSchemeWithSocks( + "www.google.com:80", // http + "www.foo.com:110", // https + "ftpproxy:20", // ftp + "socks4://foopy:130", // socks + ""), // bypass rules + }, + + // Bypass local names. + { + { // Input. + TRUE, // fAutoDetect + NULL, // lpszAutoConfigUrl + NULL, // lpszProxy + L"<local>", // lpszProxy_bypass + }, + + true, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::EmptyWithBypass("<local>"), + }, + + // Bypass "google.com" and local names, using semicolon as delimiter + // (ignoring white space). + { + { // Input. + TRUE, // fAutoDetect + NULL, // lpszAutoConfigUrl + NULL, // lpszProxy + L"<local> ; google.com", // lpszProxy_bypass + }, + + // Expected result. + true, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::EmptyWithBypass("<local>,google.com"), + }, + + // Bypass "foo.com" and "google.com", using lines as delimiter. + { + { // Input. + TRUE, // fAutoDetect + NULL, // lpszAutoConfigUrl + NULL, // lpszProxy + L"foo.com\r\ngoogle.com", // lpszProxy_bypass + }, + + // Expected result. + true, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::EmptyWithBypass("foo.com,google.com"), + }, + + // Bypass "foo.com" and "google.com", using commas as delimiter. + { + { // Input. + TRUE, // fAutoDetect + NULL, // lpszAutoConfigUrl + NULL, // lpszProxy + L"foo.com, google.com", // lpszProxy_bypass + }, + + // Expected result. + true, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::EmptyWithBypass("foo.com,google.com"), + }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_config = { + tests[i].ie_config.auto_detect, + const_cast<wchar_t*>(tests[i].ie_config.auto_config_url), + const_cast<wchar_t*>(tests[i].ie_config.proxy), + const_cast<wchar_t*>(tests[i].ie_config.proxy_bypass)}; + ProxyConfig config; + ProxyConfigServiceWin::SetFromIEConfig(&config, ie_config); + + EXPECT_EQ(tests[i].auto_detect, config.auto_detect()); + EXPECT_EQ(tests[i].pac_url, config.pac_url()); + EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules())); + EXPECT_EQ(PROXY_CONFIG_SOURCE_SYSTEM, config.source()); + } +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config_source.cc b/chromium/net/proxy_resolution/proxy_config_source.cc new file mode 100644 index 00000000000..304719ae0dd --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_source.cc @@ -0,0 +1,33 @@ +// Copyright (c) 2012 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_resolution/proxy_config_source.h" + +#include "base/logging.h" + +namespace net { + +namespace { + +const char* const kSourceNames[] = { + "UNKNOWN", + "SYSTEM", + "SYSTEM FAILED", + "GSETTINGS", + "KDE", + "ENV", + "CUSTOM", + "TEST" +}; +static_assert(arraysize(kSourceNames) == NUM_PROXY_CONFIG_SOURCES, + "kSourceNames has incorrect size"); + +} // namespace + +const char* ProxyConfigSourceToString(ProxyConfigSource source) { + DCHECK_GT(NUM_PROXY_CONFIG_SOURCES, source); + return kSourceNames[source]; +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config_source.h b/chromium/net/proxy_resolution/proxy_config_source.h new file mode 100644 index 00000000000..ff0fcfbd5f9 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_source.h @@ -0,0 +1,36 @@ +// Copyright (c) 2012 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_RESOLUTION_PROXY_CONFIG_SOURCE_H_ +#define NET_PROXY_RESOLUTION_PROXY_CONFIG_SOURCE_H_ + +namespace net { + +// Source of the configuration settings encapsulated in a ProxyConfig object. + +// The source information is used for determining how credentials are used and +// for logging. When adding new values, remember to add a string to +// kSourceNames[] in proxy_config_source.cc. +enum ProxyConfigSource { + PROXY_CONFIG_SOURCE_UNKNOWN, // The source hasn't been set. + PROXY_CONFIG_SOURCE_SYSTEM, // System settings (Win/Mac). + PROXY_CONFIG_SOURCE_SYSTEM_FAILED, // Default settings after failure to + // determine system settings. + PROXY_CONFIG_SOURCE_GSETTINGS, // GSettings (Linux). + PROXY_CONFIG_SOURCE_KDE, // KDE (Linux). + PROXY_CONFIG_SOURCE_ENV, // Environment variables. + PROXY_CONFIG_SOURCE_CUSTOM, // Custom settings local to the + // application (command line, + // extensions, application + // specific preferences, etc.) + PROXY_CONFIG_SOURCE_TEST, // Test settings. + NUM_PROXY_CONFIG_SOURCES +}; + +// Returns a textual representation of the source. +const char* ProxyConfigSourceToString(ProxyConfigSource source); + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_CONFIG_SOURCE_H_ diff --git a/chromium/net/proxy_resolution/proxy_config_unittest.cc b/chromium/net/proxy_resolution/proxy_config_unittest.cc new file mode 100644 index 00000000000..51b5703d9a6 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_unittest.cc @@ -0,0 +1,428 @@ +// Copyright (c) 2012 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_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_config_service_common_unittest.h" +#include "net/proxy_resolution/proxy_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace { + +void ExpectProxyServerEquals(const char* expectation, + const ProxyList& proxy_servers) { + if (expectation == NULL) { + EXPECT_TRUE(proxy_servers.IsEmpty()); + } else { + EXPECT_EQ(expectation, proxy_servers.ToPacString()); + } +} + +TEST(ProxyConfigTest, Equals) { + // Test |ProxyConfig::auto_detect|. + + ProxyConfig config1; + config1.set_auto_detect(true); + + ProxyConfig config2; + config2.set_auto_detect(false); + + EXPECT_FALSE(config1.Equals(config2)); + EXPECT_FALSE(config2.Equals(config1)); + + config2.set_auto_detect(true); + + EXPECT_TRUE(config1.Equals(config2)); + EXPECT_TRUE(config2.Equals(config1)); + + // Test |ProxyConfig::pac_url|. + + config2.set_pac_url(GURL("http://wpad/wpad.dat")); + + EXPECT_FALSE(config1.Equals(config2)); + EXPECT_FALSE(config2.Equals(config1)); + + config1.set_pac_url(GURL("http://wpad/wpad.dat")); + + EXPECT_TRUE(config1.Equals(config2)); + EXPECT_TRUE(config2.Equals(config1)); + + // Test |ProxyConfig::proxy_rules|. + + config2.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST; + config2.proxy_rules().single_proxies.SetSingleProxyServer( + ProxyServer::FromURI("myproxy:80", ProxyServer::SCHEME_HTTP)); + + EXPECT_FALSE(config1.Equals(config2)); + EXPECT_FALSE(config2.Equals(config1)); + + config1.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST; + config1.proxy_rules().single_proxies.SetSingleProxyServer( + ProxyServer::FromURI("myproxy:100", ProxyServer::SCHEME_HTTP)); + + EXPECT_FALSE(config1.Equals(config2)); + EXPECT_FALSE(config2.Equals(config1)); + + config1.proxy_rules().single_proxies.SetSingleProxyServer( + ProxyServer::FromURI("myproxy", ProxyServer::SCHEME_HTTP)); + + EXPECT_TRUE(config1.Equals(config2)); + EXPECT_TRUE(config2.Equals(config1)); + + // Test |ProxyConfig::bypass_rules|. + + config2.proxy_rules().bypass_rules.AddRuleFromString("*.google.com"); + + EXPECT_FALSE(config1.Equals(config2)); + EXPECT_FALSE(config2.Equals(config1)); + + config1.proxy_rules().bypass_rules.AddRuleFromString("*.google.com"); + + EXPECT_TRUE(config1.Equals(config2)); + EXPECT_TRUE(config2.Equals(config1)); + + // Test |ProxyConfig::proxy_rules.reverse_bypass|. + + config2.proxy_rules().reverse_bypass = true; + + EXPECT_FALSE(config1.Equals(config2)); + EXPECT_FALSE(config2.Equals(config1)); + + config1.proxy_rules().reverse_bypass = true; + + EXPECT_TRUE(config1.Equals(config2)); + EXPECT_TRUE(config2.Equals(config1)); +} + +TEST(ProxyConfigTest, ParseProxyRules) { + const struct { + const char* proxy_rules; + + ProxyConfig::ProxyRules::Type type; + // These will be PAC-stle strings, eg 'PROXY foo.com' + const char* single_proxy; + const char* proxy_for_http; + const char* proxy_for_https; + const char* proxy_for_ftp; + const char* fallback_proxy; + } tests[] = { + // One HTTP proxy for all schemes. + { + "myproxy:80", + + ProxyConfig::ProxyRules::Type::PROXY_LIST, + "PROXY myproxy:80", + NULL, + NULL, + NULL, + NULL, + }, + + // Multiple HTTP proxies for all schemes. + { + "myproxy:80,https://myotherproxy", + + ProxyConfig::ProxyRules::Type::PROXY_LIST, + "PROXY myproxy:80;HTTPS myotherproxy:443", + NULL, + NULL, + NULL, + NULL, + }, + + // Only specify a proxy server for "http://" urls. + { + "http=myproxy:80", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + "PROXY myproxy:80", + NULL, + NULL, + NULL, + }, + + // Specify an HTTP proxy for "ftp://" and a SOCKS proxy for "https://" urls. + { + "ftp=ftp-proxy ; https=socks4://foopy", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + NULL, + "SOCKS foopy:1080", + "PROXY ftp-proxy:80", + NULL, + }, + + // Give a scheme-specific proxy as well as a non-scheme specific. + // The first entry "foopy" takes precedance marking this list as + // Type::PROXY_LIST. + { + "foopy ; ftp=ftp-proxy", + + ProxyConfig::ProxyRules::Type::PROXY_LIST, + "PROXY foopy:80", + NULL, + NULL, + NULL, + NULL, + }, + + // Give a scheme-specific proxy as well as a non-scheme specific. + // The first entry "ftp=ftp-proxy" takes precedance marking this list as + // Type::PROXY_LIST_PER_SCHEME. + { + "ftp=ftp-proxy ; foopy", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + NULL, + NULL, + "PROXY ftp-proxy:80", + NULL, + }, + + // Include a list of entries for a single scheme. + { + "ftp=ftp1,ftp2,ftp3", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + NULL, + NULL, + "PROXY ftp1:80;PROXY ftp2:80;PROXY ftp3:80", + NULL, + }, + + // Include multiple entries for the same scheme -- they accumulate. + { + "http=http1,http2; http=http3", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + "PROXY http1:80;PROXY http2:80;PROXY http3:80", + NULL, + NULL, + NULL, + }, + + // Include lists of entries for multiple schemes. + { + "ftp=ftp1,ftp2,ftp3 ; http=http1,http2; ", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + "PROXY http1:80;PROXY http2:80", + NULL, + "PROXY ftp1:80;PROXY ftp2:80;PROXY ftp3:80", + NULL, + }, + + // Include non-default proxy schemes. + { + "http=https://secure_proxy; ftp=socks4://socks_proxy; https=socks://foo", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + "HTTPS secure_proxy:443", + "SOCKS5 foo:1080", + "SOCKS socks_proxy:1080", + NULL, + }, + + // Only SOCKS proxy present, others being blank. + { + "socks=foopy", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + NULL, + NULL, + NULL, + "SOCKS foopy:1080", + }, + + // SOCKS proxy present along with other proxies too + { + "http=httpproxy ; https=httpsproxy ; ftp=ftpproxy ; socks=foopy ", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + "PROXY httpproxy:80", + "PROXY httpsproxy:80", + "PROXY ftpproxy:80", + "SOCKS foopy:1080", + }, + + // SOCKS proxy (with modifier) present along with some proxies + // (FTP being blank) + { + "http=httpproxy ; https=httpsproxy ; socks=socks5://foopy ", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + "PROXY httpproxy:80", + "PROXY httpsproxy:80", + NULL, + "SOCKS5 foopy:1080", + }, + + // Include unsupported schemes -- they are discarded. + { + "crazy=foopy ; foo=bar ; https=myhttpsproxy", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + NULL, + "PROXY myhttpsproxy:80", + NULL, + NULL, + }, + + // direct:// as first option for a scheme. + { + "http=direct://,myhttpproxy; https=direct://", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + "DIRECT;PROXY myhttpproxy:80", + "DIRECT", + NULL, + NULL, + }, + + // direct:// as a second option for a scheme. + { + "http=myhttpproxy,direct://", + + ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + NULL, + "PROXY myhttpproxy:80;DIRECT", + NULL, + NULL, + NULL, + }, + + }; + + ProxyConfig config; + + for (size_t i = 0; i < arraysize(tests); ++i) { + config.proxy_rules().ParseFromString(tests[i].proxy_rules); + + EXPECT_EQ(tests[i].type, config.proxy_rules().type); + ExpectProxyServerEquals(tests[i].single_proxy, + config.proxy_rules().single_proxies); + ExpectProxyServerEquals(tests[i].proxy_for_http, + config.proxy_rules().proxies_for_http); + ExpectProxyServerEquals(tests[i].proxy_for_https, + config.proxy_rules().proxies_for_https); + ExpectProxyServerEquals(tests[i].proxy_for_ftp, + config.proxy_rules().proxies_for_ftp); + ExpectProxyServerEquals(tests[i].fallback_proxy, + config.proxy_rules().fallback_proxies); + } +} + +TEST(ProxyConfigTest, ProxyRulesSetBypassFlag) { + // Test whether the did_bypass_proxy() flag is set in proxy info correctly. + ProxyConfig::ProxyRules rules; + ProxyInfo result; + + rules.ParseFromString("http=httpproxy:80"); + rules.bypass_rules.AddRuleFromString(".com"); + + rules.Apply(GURL("http://example.com"), &result); + EXPECT_TRUE(result.is_direct_only()); + EXPECT_TRUE(result.did_bypass_proxy()); + + rules.Apply(GURL("http://example.org"), &result); + EXPECT_FALSE(result.is_direct()); + EXPECT_FALSE(result.did_bypass_proxy()); + + // Try with reversed bypass rules. + rules.reverse_bypass = true; + + rules.Apply(GURL("http://example.org"), &result); + EXPECT_TRUE(result.is_direct_only()); + EXPECT_TRUE(result.did_bypass_proxy()); + + rules.Apply(GURL("http://example.com"), &result); + EXPECT_FALSE(result.is_direct()); + EXPECT_FALSE(result.did_bypass_proxy()); +} + +static const char kWsUrl[] = "ws://example.com/echo"; +static const char kWssUrl[] = "wss://example.com/echo"; + +class ProxyConfigWebSocketTest : public ::testing::Test { + protected: + void ParseFromString(const std::string& rules) { + rules_.ParseFromString(rules); + } + void Apply(const GURL& gurl) { rules_.Apply(gurl, &info_); } + std::string ToPacString() const { return info_.ToPacString(); } + + static GURL WsUrl() { return GURL(kWsUrl); } + static GURL WssUrl() { return GURL(kWssUrl); } + + ProxyConfig::ProxyRules rules_; + ProxyInfo info_; +}; + +// If a single proxy is set for all protocols, WebSocket uses it. +TEST_F(ProxyConfigWebSocketTest, UsesProxy) { + ParseFromString("proxy:3128"); + Apply(WsUrl()); + EXPECT_EQ("PROXY proxy:3128", ToPacString()); +} + +// See RFC6455 Section 4.1. item 3, "_Proxy Usage_". +TEST_F(ProxyConfigWebSocketTest, PrefersSocks) { + ParseFromString( + "http=proxy:3128 ; https=sslproxy:3128 ; socks=socksproxy:1080"); + Apply(WsUrl()); + EXPECT_EQ("SOCKS socksproxy:1080", ToPacString()); +} + +TEST_F(ProxyConfigWebSocketTest, PrefersHttpsToHttp) { + ParseFromString("http=proxy:3128 ; https=sslproxy:3128"); + Apply(WssUrl()); + EXPECT_EQ("PROXY sslproxy:3128", ToPacString()); +} + +TEST_F(ProxyConfigWebSocketTest, PrefersHttpsEvenForWs) { + ParseFromString("http=proxy:3128 ; https=sslproxy:3128"); + Apply(WsUrl()); + EXPECT_EQ("PROXY sslproxy:3128", ToPacString()); +} + +TEST_F(ProxyConfigWebSocketTest, PrefersHttpToDirect) { + ParseFromString("http=proxy:3128"); + Apply(WssUrl()); + EXPECT_EQ("PROXY proxy:3128", ToPacString()); +} + +TEST_F(ProxyConfigWebSocketTest, IgnoresFtpProxy) { + ParseFromString("ftp=ftpproxy:3128"); + Apply(WssUrl()); + EXPECT_EQ("DIRECT", ToPacString()); +} + +TEST_F(ProxyConfigWebSocketTest, ObeysBypassRules) { + ParseFromString("http=proxy:3128 ; https=sslproxy:3128"); + rules_.bypass_rules.AddRuleFromString(".chromium.org"); + Apply(GURL("wss://codereview.chromium.org/feed")); + EXPECT_EQ("DIRECT", ToPacString()); +} + +TEST_F(ProxyConfigWebSocketTest, ObeysLocalBypass) { + ParseFromString("http=proxy:3128 ; https=sslproxy:3128"); + rules_.bypass_rules.AddRuleFromString("<local>"); + Apply(GURL("ws://localhost/feed")); + EXPECT_EQ("DIRECT", ToPacString()); +} + +} // namespace +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_info.cc b/chromium/net/proxy_resolution/proxy_info.cc new file mode 100644 index 00000000000..dbcc8c45a85 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_info.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2012 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_resolution/proxy_info.h" + +#include "net/proxy_resolution/proxy_retry_info.h" + +namespace net { + +ProxyInfo::ProxyInfo() + : config_source_(PROXY_CONFIG_SOURCE_UNKNOWN), + did_bypass_proxy_(false), + did_use_pac_script_(false) {} + +ProxyInfo::ProxyInfo(const ProxyInfo& other) = default; + +ProxyInfo::~ProxyInfo() = default; + +void ProxyInfo::Use(const ProxyInfo& other) { + proxy_resolve_start_time_ = other.proxy_resolve_start_time_; + proxy_resolve_end_time_ = other.proxy_resolve_end_time_; + proxy_list_ = other.proxy_list_; + proxy_retry_info_ = other.proxy_retry_info_; + config_source_ = other.config_source_; + did_bypass_proxy_ = other.did_bypass_proxy_; + did_use_pac_script_ = other.did_use_pac_script_; +} + +void ProxyInfo::UseDirect() { + Reset(); + proxy_list_.SetSingleProxyServer(ProxyServer::Direct()); +} + +void ProxyInfo::UseDirectWithBypassedProxy() { + UseDirect(); + did_bypass_proxy_ = true; +} + +void ProxyInfo::UseNamedProxy(const std::string& proxy_uri_list) { + Reset(); + proxy_list_.Set(proxy_uri_list); +} + +void ProxyInfo::UseProxyServer(const ProxyServer& proxy_server) { + Reset(); + proxy_list_.SetSingleProxyServer(proxy_server); +} + +void ProxyInfo::UsePacString(const std::string& pac_string) { + Reset(); + proxy_list_.SetFromPacString(pac_string); +} + +void ProxyInfo::UseProxyList(const ProxyList& proxy_list) { + Reset(); + proxy_list_ = proxy_list; +} + +void ProxyInfo::OverrideProxyList(const ProxyList& proxy_list) { + proxy_list_ = proxy_list; +} + +void ProxyInfo::SetAlternativeProxy(const ProxyServer& proxy_server) { + alternative_proxy_ = proxy_server; +} + +std::string ProxyInfo::ToPacString() const { + return proxy_list_.ToPacString(); +} + +bool ProxyInfo::Fallback(int net_error, const NetLogWithSource& net_log) { + return proxy_list_.Fallback(&proxy_retry_info_, net_error, net_log); +} + +void ProxyInfo::DeprioritizeBadProxies( + const ProxyRetryInfoMap& proxy_retry_info) { + proxy_list_.DeprioritizeBadProxies(proxy_retry_info); +} + +void ProxyInfo::RemoveProxiesWithoutScheme(int scheme_bit_field) { + proxy_list_.RemoveProxiesWithoutScheme(scheme_bit_field); +} + +void ProxyInfo::Reset() { + proxy_resolve_start_time_ = base::TimeTicks(); + proxy_resolve_end_time_ = base::TimeTicks(); + proxy_list_.Clear(); + alternative_proxy_ = net::ProxyServer(); + proxy_retry_info_.clear(); + config_source_ = PROXY_CONFIG_SOURCE_UNKNOWN; + did_bypass_proxy_ = false; + did_use_pac_script_ = false; +} + +const NetworkTrafficAnnotationTag ProxyInfo::traffic_annotation() const { + // TODO(crbug.com/656607): Get appropriate annotation from the origin of + // config_source_. + return NO_TRAFFIC_ANNOTATION_BUG_656607; +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_info.h b/chromium/net/proxy_resolution/proxy_info.h new file mode 100644 index 00000000000..bf65e4a3683 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_info.h @@ -0,0 +1,212 @@ +// Copyright (c) 2012 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_RESOLUTION_PROXY_INFO_H_ +#define NET_PROXY_RESOLUTION_PROXY_INFO_H_ + +#include <string> + +#include "base/gtest_prod_util.h" +#include "base/time/time.h" +#include "net/base/net_export.h" +#include "net/base/proxy_server.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_list.h" +#include "net/proxy_resolution/proxy_retry_info.h" +#include "net/traffic_annotation/network_traffic_annotation.h" + +namespace net { + +class NetLogWithSource; + +// This object holds proxy information returned by ResolveProxy. +class NET_EXPORT ProxyInfo { + public: + ProxyInfo(); + ProxyInfo(const ProxyInfo& other); + ~ProxyInfo(); + // Default copy-constructor and assignment operator are OK! + + // Uses the same proxy server as the given |proxy_info|. + void Use(const ProxyInfo& proxy_info); + + // Uses a direct connection. + void UseDirect(); + + // Uses a direct connection. did_bypass_proxy() will return true to indicate + // that the direct connection is the result of configured proxy bypass rules. + // + // See also the note for UseDirect(). + void UseDirectWithBypassedProxy(); + + // Uses a specific proxy server, of the form: + // proxy-uri = [<scheme> "://"] <hostname> [":" <port>] + // This may optionally be a semi-colon delimited list of <proxy-uri>. + // It is OK to have LWS between entries. + // + // See also the note for UseDirect(). + void UseNamedProxy(const std::string& proxy_uri_list); + + // Sets the proxy list to a single entry, |proxy_server|. + // + // See also the note for UseDirect(). + void UseProxyServer(const ProxyServer& proxy_server); + + // Parses from the given PAC result. + // + // See also the note for UseDirect(). + void UsePacString(const std::string& pac_string); + + // Uses the proxies from the given list. + // + // See also the note for UseDirect(). + void UseProxyList(const ProxyList& proxy_list); + + // Uses the proxies from the given list, but does not otherwise reset the + // proxy configuration. + void OverrideProxyList(const ProxyList& proxy_list); + + // Sets the alternative service to try when connecting to the first valid + // proxy server, but does not otherwise reset the proxy configuration. + void SetAlternativeProxy(const ProxyServer& proxy_server); + + // Returns true if this proxy info specifies a direct connection. + bool is_direct() const { + // We don't implicitly fallback to DIRECT unless it was added to the list. + if (is_empty()) + return false; + return proxy_list_.Get().is_direct(); + } + + bool is_direct_only() const { + return is_direct() && proxy_list_.size() == 1 && proxy_retry_info_.empty(); + } + + // Returns true if the first valid proxy server is an https proxy. + bool is_https() const { + if (is_empty()) + return false; + return proxy_server().is_https(); + } + + // Returns true if the first valid proxy server is an http proxy. + bool is_http() const { + if (is_empty()) + return false; + return proxy_server().is_http(); + } + + // Returns true if the first valid proxy server is a quic proxy. + bool is_quic() const { + if (is_empty()) + return false; + return proxy_server().is_quic(); + } + + // Returns true if the first valid proxy server is a socks server. + bool is_socks() const { + if (is_empty()) + return false; + return proxy_server().is_socks(); + } + + // Returns true if this proxy info has no proxies left to try. + bool is_empty() const { + return proxy_list_.IsEmpty(); + } + + // Returns true if this proxy resolution is using a direct connection due to + // proxy bypass rules. + bool did_bypass_proxy() const { + return did_bypass_proxy_; + } + + // Returns true if the proxy resolution was done using a PAC script. + bool did_use_pac_script() const { + return did_use_pac_script_; + } + + // Returns the first valid proxy server. is_empty() must be false to be able + // to call this function. + const ProxyServer& proxy_server() const { return proxy_list_.Get(); } + + // Returns the source for configuration settings used for proxy resolution. + ProxyConfigSource config_source() const { return config_source_; } + + // Returns traffic annotation tag based on current config source. + const NetworkTrafficAnnotationTag traffic_annotation() const; + + // See description in ProxyList::ToPacString(). + std::string ToPacString() const; + + // Marks the current proxy as bad. |net_error| should contain the network + // error encountered when this proxy was tried, if any. If this fallback + // is not because of a network error, then |OK| should be passed in (eg. for + // reasons such as local policy). Returns true if there is another proxy + // available to try in |proxy_list_|. + bool Fallback(int net_error, const NetLogWithSource& net_log); + + // De-prioritizes the proxies that we have cached as not working, by moving + // them to the end of the proxy list. + void DeprioritizeBadProxies(const ProxyRetryInfoMap& proxy_retry_info); + + // Deletes any entry which doesn't have one of the specified proxy schemes. + void RemoveProxiesWithoutScheme(int scheme_bit_field); + + // Returns the list of proxies to use. + const ProxyList& proxy_list() const { + return proxy_list_; + } + + // Returns the alternative proxy, which may be invalid. + const ProxyServer& alternative_proxy() const { return alternative_proxy_; } + + base::TimeTicks proxy_resolve_start_time() const { + return proxy_resolve_start_time_; + } + + base::TimeTicks proxy_resolve_end_time() const { + return proxy_resolve_end_time_; + } + + private: + friend class ProxyResolutionService; + FRIEND_TEST_ALL_PREFIXES(ProxyInfoTest, UseVsOverrideProxyList); + + const ProxyRetryInfoMap& proxy_retry_info() const { + return proxy_retry_info_; + } + + // Reset proxy and config settings. + void Reset(); + + // The ordered list of proxy servers (including DIRECT attempts) remaining to + // try. If proxy_list_ is empty, then there is nothing left to fall back to. + ProxyList proxy_list_; + + // An alternative to proxy_server() (in the sense of HTTP Alternative + // Services). + ProxyServer alternative_proxy_; + + // List of proxies that have been tried already. + ProxyRetryInfoMap proxy_retry_info_; + + // The source of the proxy settings used, + ProxyConfigSource config_source_; + + // Whether the proxy result represent a proxy bypass. + bool did_bypass_proxy_; + + // Whether we used a PAC script for resolving the proxy. + bool did_use_pac_script_; + + // How long it took to resolve the proxy. Times are both null if proxy was + // determined synchronously without running a PAC. + base::TimeTicks proxy_resolve_start_time_; + base::TimeTicks proxy_resolve_end_time_; +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_INFO_H_ diff --git a/chromium/net/proxy_resolution/proxy_info_unittest.cc b/chromium/net/proxy_resolution/proxy_info_unittest.cc new file mode 100644 index 00000000000..8492c00a0cc --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_info_unittest.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2012 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_resolution/proxy_info.h" + +#include "net/base/net_errors.h" +#include "net/log/net_log_with_source.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_list.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace { + +TEST(ProxyInfoTest, ProxyInfoIsDirectOnly) { + // Test the is_direct_only() predicate. + ProxyInfo info; + + // An empty ProxyInfo is not considered direct. + EXPECT_FALSE(info.is_direct_only()); + + info.UseDirect(); + EXPECT_TRUE(info.is_direct_only()); + + info.UsePacString("DIRECT"); + EXPECT_TRUE(info.is_direct_only()); + + info.UsePacString("PROXY myproxy:80"); + EXPECT_FALSE(info.is_direct_only()); + + info.UsePacString("DIRECT; PROXY myproxy:80"); + EXPECT_TRUE(info.is_direct()); + EXPECT_FALSE(info.is_direct_only()); + + info.UsePacString("PROXY myproxy:80; DIRECT"); + EXPECT_FALSE(info.is_direct()); + EXPECT_FALSE(info.is_direct_only()); + EXPECT_EQ(2u, info.proxy_list().size()); + EXPECT_EQ("PROXY myproxy:80;DIRECT", info.proxy_list().ToPacString()); + // After falling back to direct, we shouldn't consider it DIRECT only. + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + EXPECT_TRUE(info.is_direct()); + EXPECT_FALSE(info.is_direct_only()); +} + +} // namespace + +TEST(ProxyInfoTest, UseVsOverrideProxyList) { + ProxyInfo info; + ProxyList proxy_list; + proxy_list.Set("http://foo.com"); + info.OverrideProxyList(proxy_list); + EXPECT_EQ("PROXY foo.com:80", info.proxy_list().ToPacString()); + proxy_list.Set("http://bar.com"); + info.UseProxyList(proxy_list); + EXPECT_EQ("PROXY bar.com:80", info.proxy_list().ToPacString()); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_list.cc b/chromium/net/proxy_resolution/proxy_list.cc new file mode 100644 index 00000000000..6a0e1a8f27b --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_list.cc @@ -0,0 +1,223 @@ +// Copyright (c) 2012 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_resolution/proxy_list.h" + +#include "base/callback.h" +#include "base/logging.h" +#include "base/strings/string_tokenizer.h" +#include "base/time/time.h" +#include "base/values.h" +#include "net/base/proxy_server.h" +#include "net/log/net_log.h" +#include "net/log/net_log_event_type.h" +#include "net/log/net_log_with_source.h" + +using base::TimeDelta; +using base::TimeTicks; + +namespace net { + +ProxyList::ProxyList() = default; + +ProxyList::ProxyList(const ProxyList& other) = default; + +ProxyList::~ProxyList() = default; + +void ProxyList::Set(const std::string& proxy_uri_list) { + proxies_.clear(); + base::StringTokenizer str_tok(proxy_uri_list, ";"); + while (str_tok.GetNext()) { + ProxyServer uri = ProxyServer::FromURI( + str_tok.token_begin(), str_tok.token_end(), ProxyServer::SCHEME_HTTP); + // Silently discard malformed inputs. + if (uri.is_valid()) + proxies_.push_back(uri); + } +} + +void ProxyList::SetSingleProxyServer(const ProxyServer& proxy_server) { + proxies_.clear(); + AddProxyServer(proxy_server); +} + +void ProxyList::AddProxyServer(const ProxyServer& proxy_server) { + if (proxy_server.is_valid()) + proxies_.push_back(proxy_server); +} + +void ProxyList::DeprioritizeBadProxies( + const ProxyRetryInfoMap& proxy_retry_info) { + // Partition the proxy list in two: + // (1) the known bad proxies + // (2) everything else + std::vector<ProxyServer> good_proxies; + std::vector<ProxyServer> bad_proxies_to_try; + + std::vector<ProxyServer>::const_iterator iter = proxies_.begin(); + for (; iter != proxies_.end(); ++iter) { + ProxyRetryInfoMap::const_iterator bad_proxy = + proxy_retry_info.find(iter->ToURI()); + if (bad_proxy != proxy_retry_info.end()) { + // This proxy is bad. Check if it's time to retry. + if (bad_proxy->second.bad_until >= TimeTicks::Now()) { + // still invalid. + if (bad_proxy->second.try_while_bad) + bad_proxies_to_try.push_back(*iter); + continue; + } + } + good_proxies.push_back(*iter); + } + + // "proxies_ = good_proxies + bad_proxies" + proxies_.swap(good_proxies); + proxies_.insert(proxies_.end(), bad_proxies_to_try.begin(), + bad_proxies_to_try.end()); +} + +void ProxyList::RemoveProxiesWithoutScheme(int scheme_bit_field) { + for (std::vector<ProxyServer>::iterator it = proxies_.begin(); + it != proxies_.end(); ) { + if (!(scheme_bit_field & it->scheme())) { + it = proxies_.erase(it); + continue; + } + ++it; + } +} + +void ProxyList::Clear() { + proxies_.clear(); +} + +bool ProxyList::IsEmpty() const { + return proxies_.empty(); +} + +size_t ProxyList::size() const { + return proxies_.size(); +} + +// Returns true if |*this| lists the same proxies as |other|. +bool ProxyList::Equals(const ProxyList& other) const { + if (size() != other.size()) + return false; + return proxies_ == other.proxies_; +} + +const ProxyServer& ProxyList::Get() const { + DCHECK(!proxies_.empty()); + return proxies_[0]; +} + +const std::vector<ProxyServer>& ProxyList::GetAll() const { + return proxies_; +} + +void ProxyList::SetFromPacString(const std::string& pac_string) { + base::StringTokenizer entry_tok(pac_string, ";"); + proxies_.clear(); + while (entry_tok.GetNext()) { + ProxyServer uri = ProxyServer::FromPacString( + entry_tok.token_begin(), entry_tok.token_end()); + // Silently discard malformed inputs. + if (uri.is_valid()) + proxies_.push_back(uri); + } + + // If we failed to parse anything from the PAC results list, fallback to + // DIRECT (this basically means an error in the PAC script). + if (proxies_.empty()) { + proxies_.push_back(ProxyServer::Direct()); + } +} + +std::string ProxyList::ToPacString() const { + std::string proxy_list; + std::vector<ProxyServer>::const_iterator iter = proxies_.begin(); + for (; iter != proxies_.end(); ++iter) { + if (!proxy_list.empty()) + proxy_list += ";"; + proxy_list += iter->ToPacString(); + } + return proxy_list.empty() ? std::string() : proxy_list; +} + +std::unique_ptr<base::ListValue> ProxyList::ToValue() const { + std::unique_ptr<base::ListValue> list(new base::ListValue()); + for (size_t i = 0; i < proxies_.size(); ++i) + list->AppendString(proxies_[i].ToURI()); + return list; +} + +bool ProxyList::Fallback(ProxyRetryInfoMap* proxy_retry_info, + int net_error, + const NetLogWithSource& net_log) { + if (proxies_.empty()) { + NOTREACHED(); + return false; + } + // By default, proxies are not retried for 5 minutes. + UpdateRetryInfoOnFallback(proxy_retry_info, TimeDelta::FromMinutes(5), true, + std::vector<ProxyServer>(), net_error, net_log); + + // Remove this proxy from our list. + proxies_.erase(proxies_.begin()); + return !proxies_.empty(); +} + +void ProxyList::AddProxyToRetryList(ProxyRetryInfoMap* proxy_retry_info, + base::TimeDelta retry_delay, + bool try_while_bad, + const ProxyServer& proxy_to_retry, + int net_error, + const NetLogWithSource& net_log) const { + // Mark this proxy as bad. + TimeTicks bad_until = TimeTicks::Now() + retry_delay; + std::string proxy_key = proxy_to_retry.ToURI(); + ProxyRetryInfoMap::iterator iter = proxy_retry_info->find(proxy_key); + if (iter == proxy_retry_info->end() || bad_until > iter->second.bad_until) { + ProxyRetryInfo retry_info; + retry_info.current_delay = retry_delay; + retry_info.bad_until = bad_until; + retry_info.try_while_bad = try_while_bad; + retry_info.net_error = net_error; + (*proxy_retry_info)[proxy_key] = retry_info; + } + net_log.AddEvent(NetLogEventType::PROXY_LIST_FALLBACK, + NetLog::StringCallback("bad_proxy", &proxy_key)); +} + +void ProxyList::UpdateRetryInfoOnFallback( + ProxyRetryInfoMap* proxy_retry_info, + base::TimeDelta retry_delay, + bool reconsider, + const std::vector<ProxyServer>& additional_proxies_to_bypass, + int net_error, + const NetLogWithSource& net_log) const { + DCHECK(!retry_delay.is_zero()); + + if (proxies_.empty()) { + NOTREACHED(); + return; + } + + if (!proxies_[0].is_direct()) { + AddProxyToRetryList(proxy_retry_info, + retry_delay, + reconsider, + proxies_[0], + net_error, + net_log); + // If any additional proxies to bypass are specified, add to the retry map + // as well. + for (const ProxyServer& additional_proxy : additional_proxies_to_bypass) { + AddProxyToRetryList(proxy_retry_info, retry_delay, reconsider, + additional_proxy, net_error, net_log); + } + } +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_list.h b/chromium/net/proxy_resolution/proxy_list.h new file mode 100644 index 00000000000..b01003b8124 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_list.h @@ -0,0 +1,134 @@ +// Copyright (c) 2012 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_RESOLUTION_PROXY_LIST_H_ +#define NET_PROXY_RESOLUTION_PROXY_LIST_H_ + +#include <stddef.h> + +#include <memory> +#include <string> +#include <vector> + +#include "net/base/net_export.h" +#include "net/proxy_resolution/proxy_retry_info.h" + +namespace base { +class ListValue; +class TimeDelta; +} + +namespace net { + +class ProxyServer; +class NetLogWithSource; + +// This class is used to hold a list of proxies returned by GetProxyForUrl or +// manually configured. It handles proxy fallback if multiple servers are +// specified. +class NET_EXPORT_PRIVATE ProxyList { + public: + ProxyList(); + ProxyList(const ProxyList& other); + ~ProxyList(); + + // Initializes the proxy list to a string containing one or more proxy servers + // delimited by a semicolon. + void Set(const std::string& proxy_uri_list); + + // Set the proxy list to a single entry, |proxy_server|. + void SetSingleProxyServer(const ProxyServer& proxy_server); + + // Append a single proxy server to the end of the proxy list. + void AddProxyServer(const ProxyServer& proxy_server); + + // De-prioritizes the proxies that are cached as not working but are allowed + // to be reconsidered, by moving them to the end of the fallback list. + void DeprioritizeBadProxies(const ProxyRetryInfoMap& proxy_retry_info); + + // Delete any entry which doesn't have one of the specified proxy schemes. + // |scheme_bit_field| is a bunch of ProxyServer::Scheme bitwise ORed together. + void RemoveProxiesWithoutScheme(int scheme_bit_field); + + // Clear the proxy list. + void Clear(); + + // Returns true if there is nothing left in the ProxyList. + bool IsEmpty() const; + + // Returns the number of proxy servers in this list. + size_t size() const; + + // Returns true if |*this| lists the same proxies as |other|. + bool Equals(const ProxyList& other) const; + + // Returns the first proxy server in the list. It is only valid to call + // this if !IsEmpty(). + const ProxyServer& Get() const; + + // Returns all proxy servers in the list. + const std::vector<ProxyServer>& GetAll() const; + + // Sets the list by parsing the pac result |pac_string|. + // Some examples for |pac_string|: + // "DIRECT" + // "PROXY foopy1" + // "PROXY foopy1; SOCKS4 foopy2:1188" + // Does a best-effort parse, and silently discards any errors. + void SetFromPacString(const std::string& pac_string); + + // Returns a PAC-style semicolon-separated list of valid proxy servers. + // For example: "PROXY xxx.xxx.xxx.xxx:xx; SOCKS yyy.yyy.yyy:yy". + std::string ToPacString() const; + + // Returns a serialized value for the list. + std::unique_ptr<base::ListValue> ToValue() const; + + // Marks the current proxy server as bad and deletes it from the list. The + // list of known bad proxies is given by |proxy_retry_info|. |net_error| + // should contain the network error encountered when this proxy was tried, if + // any. If this fallback is not because of a network error, then |OK| should + // be passed in (eg. for reasons such as local policy). Returns true if there + // is another server available in the list. + bool Fallback(ProxyRetryInfoMap* proxy_retry_info, + int net_error, + const NetLogWithSource& net_log); + + // Updates |proxy_retry_info| to indicate that the first proxy in the list + // is bad. This is distinct from Fallback(), above, to allow updating proxy + // retry information without modifying a given transction's proxy list. Will + // retry after |retry_delay| if positive, and will use the default proxy retry + // duration otherwise. It may reconsider the proxy beforehand if |reconsider| + // is true. Additionally updates |proxy_retry_info| with + // |additional_proxies_to_bypass|. |net_error| should contain the network + // error countered when this proxy was tried, or OK if the proxy retry info is + // being updated for a non-network related reason (e.g. local policy). + void UpdateRetryInfoOnFallback( + ProxyRetryInfoMap* proxy_retry_info, + base::TimeDelta retry_delay, + bool reconsider, + const std::vector<ProxyServer>& additional_proxies_to_bypass, + int net_error, + const NetLogWithSource& net_log) const; + + private: + // Updates |proxy_retry_info| to indicate that the |proxy_to_retry| in + // |proxies_| is bad for |retry_delay|, but may be reconsidered earlier if + // |try_while_bad| is true. |net_error| should contain the network error + // countered when this proxy was tried, or OK if the proxy retry info is + // being updated for a non-network related reason (e.g. local policy). + void AddProxyToRetryList(ProxyRetryInfoMap* proxy_retry_info, + base::TimeDelta retry_delay, + bool try_while_bad, + const ProxyServer& proxy_to_retry, + int net_error, + const NetLogWithSource& net_log) const; + + // List of proxies. + std::vector<ProxyServer> proxies_; +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_LIST_H_ diff --git a/chromium/net/proxy_resolution/proxy_list_unittest.cc b/chromium/net/proxy_resolution/proxy_list_unittest.cc new file mode 100644 index 00000000000..18ca37902f7 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_list_unittest.cc @@ -0,0 +1,316 @@ +// Copyright (c) 2006-2008 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_resolution/proxy_list.h" + +#include <vector> + +#include "net/base/net_errors.h" +#include "net/base/proxy_server.h" +#include "net/log/net_log_with_source.h" +#include "net/proxy_resolution/proxy_retry_info.h" +#include "net/test/gtest_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using net::test::IsOk; + +namespace net { + +namespace { + +// Test parsing from a PAC string. +TEST(ProxyListTest, SetFromPacString) { + const struct { + const char* pac_input; + const char* pac_output; + } tests[] = { + // Valid inputs: + { "PROXY foopy:10", + "PROXY foopy:10", + }, + { " DIRECT", // leading space. + "DIRECT", + }, + { "PROXY foopy1 ; proxy foopy2;\t DIRECT", + "PROXY foopy1:80;PROXY foopy2:80;DIRECT", + }, + { "proxy foopy1 ; SOCKS foopy2", + "PROXY foopy1:80;SOCKS foopy2:1080", + }, + // Try putting DIRECT first. + { "DIRECT ; proxy foopy1 ; DIRECT ; SOCKS5 foopy2;DIRECT ", + "DIRECT;PROXY foopy1:80;DIRECT;SOCKS5 foopy2:1080;DIRECT", + }, + // Try putting DIRECT consecutively. + { "DIRECT ; proxy foopy1:80; DIRECT ; DIRECT", + "DIRECT;PROXY foopy1:80;DIRECT;DIRECT", + }, + + // Invalid inputs (parts which aren't understood get + // silently discarded): + // + // If the proxy list string parsed to empty, automatically fall-back to + // DIRECT. + { "PROXY-foopy:10", + "DIRECT", + }, + { "PROXY", + "DIRECT", + }, + { "PROXY foopy1 ; JUNK ; JUNK ; SOCKS5 foopy2 ; ;", + "PROXY foopy1:80;SOCKS5 foopy2:1080", + }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + ProxyList list; + list.SetFromPacString(tests[i].pac_input); + EXPECT_EQ(tests[i].pac_output, list.ToPacString()); + EXPECT_FALSE(list.IsEmpty()); + } +} + +TEST(ProxyListTest, RemoveProxiesWithoutScheme) { + const struct { + const char* pac_input; + int filter; + const char* filtered_pac_output; + } tests[] = { + { "PROXY foopy:10 ; SOCKS5 foopy2 ; SOCKS foopy11 ; PROXY foopy3 ; DIRECT", + // Remove anything that isn't HTTP or DIRECT. + ProxyServer::SCHEME_DIRECT | ProxyServer::SCHEME_HTTP, + "PROXY foopy:10;PROXY foopy3:80;DIRECT", + }, + { "PROXY foopy:10 ; SOCKS5 foopy2", + // Remove anything that isn't HTTP or SOCKS5. + ProxyServer::SCHEME_DIRECT | ProxyServer::SCHEME_SOCKS4, + "", + }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + ProxyList list; + list.SetFromPacString(tests[i].pac_input); + list.RemoveProxiesWithoutScheme(tests[i].filter); + EXPECT_EQ(tests[i].filtered_pac_output, list.ToPacString()); + } +} + +TEST(ProxyListTest, DeprioritizeBadProxies) { + // Retry info that marks a proxy as being bad for a *very* long time (to avoid + // the test depending on the current time.) + ProxyRetryInfo proxy_retry_info; + proxy_retry_info.bad_until = + base::TimeTicks::Now() + base::TimeDelta::FromDays(1); + + // Call DeprioritizeBadProxies with an empty map -- should have no effect. + { + ProxyList list; + list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); + + ProxyRetryInfoMap retry_info_map; + list.DeprioritizeBadProxies(retry_info_map); + EXPECT_EQ("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80", + list.ToPacString()); + } + + // Call DeprioritizeBadProxies with 2 of the three proxies marked as bad. + // These proxies should be retried last. + { + ProxyList list; + list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); + + ProxyRetryInfoMap retry_info_map; + retry_info_map["foopy1:80"] = proxy_retry_info; + retry_info_map["foopy3:80"] = proxy_retry_info; + retry_info_map["socks5://localhost:1080"] = proxy_retry_info; + + list.DeprioritizeBadProxies(retry_info_map); + + EXPECT_EQ("PROXY foopy2:80;PROXY foopy1:80;PROXY foopy3:80", + list.ToPacString()); + } + + // Call DeprioritizeBadProxies where ALL of the proxies are marked as bad. + // This should have no effect on the order. + { + ProxyList list; + list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); + + ProxyRetryInfoMap retry_info_map; + retry_info_map["foopy1:80"] = proxy_retry_info; + retry_info_map["foopy2:80"] = proxy_retry_info; + retry_info_map["foopy3:80"] = proxy_retry_info; + + list.DeprioritizeBadProxies(retry_info_map); + + EXPECT_EQ("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80", + list.ToPacString()); + } + + // Call DeprioritizeBadProxies with 2 of the three proxies marked as bad. Of + // the 2 bad proxies, one is to be reconsidered and should be retried last. + // The other is not to be reconsidered and should be removed from the list. + { + ProxyList list; + list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); + + ProxyRetryInfoMap retry_info_map; + // |proxy_retry_info.reconsider defaults to true. + retry_info_map["foopy1:80"] = proxy_retry_info; + proxy_retry_info.try_while_bad = false; + retry_info_map["foopy3:80"] = proxy_retry_info; + proxy_retry_info.try_while_bad = true; + retry_info_map["socks5://localhost:1080"] = proxy_retry_info; + + list.DeprioritizeBadProxies(retry_info_map); + + EXPECT_EQ("PROXY foopy2:80;PROXY foopy1:80", + list.ToPacString()); + } +} + +TEST(ProxyListTest, UpdateRetryInfoOnFallback) { + ProxyRetryInfo proxy_retry_info; + // Retrying should put the first proxy on the retry list. + { + ProxyList list; + ProxyRetryInfoMap retry_info_map; + NetLogWithSource net_log; + ProxyServer proxy_server( + ProxyServer::FromURI("foopy1:80", ProxyServer::SCHEME_HTTP)); + std::vector<ProxyServer> bad_proxies; + bad_proxies.push_back(proxy_server); + list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); + list.UpdateRetryInfoOnFallback( + &retry_info_map, base::TimeDelta::FromSeconds(60), true, bad_proxies, + ERR_PROXY_CONNECTION_FAILED, net_log); + EXPECT_TRUE(retry_info_map.end() != retry_info_map.find("foopy1:80")); + EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, + retry_info_map[proxy_server.ToURI()].net_error); + EXPECT_TRUE(retry_info_map.end() == retry_info_map.find("foopy2:80")); + EXPECT_TRUE(retry_info_map.end() == retry_info_map.find("foopy3:80")); + } + // Retrying should put the first proxy on the retry list, even if there + // was no network error. + { + ProxyList list; + ProxyRetryInfoMap retry_info_map; + NetLogWithSource net_log; + ProxyServer proxy_server( + ProxyServer::FromURI("foopy1:80", ProxyServer::SCHEME_HTTP)); + std::vector<ProxyServer> bad_proxies; + bad_proxies.push_back(proxy_server); + list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); + list.UpdateRetryInfoOnFallback(&retry_info_map, + base::TimeDelta::FromSeconds(60), true, + bad_proxies, OK, net_log); + EXPECT_TRUE(retry_info_map.end() != retry_info_map.find("foopy1:80")); + EXPECT_THAT(retry_info_map[proxy_server.ToURI()].net_error, IsOk()); + EXPECT_TRUE(retry_info_map.end() == retry_info_map.find("foopy2:80")); + EXPECT_TRUE(retry_info_map.end() == retry_info_map.find("foopy3:80")); + } + // Including another bad proxy should put both the first and the specified + // proxy on the retry list. + { + ProxyList list; + ProxyRetryInfoMap retry_info_map; + NetLogWithSource net_log; + ProxyServer proxy_server = ProxyServer::FromURI("foopy3:80", + ProxyServer::SCHEME_HTTP); + std::vector<ProxyServer> bad_proxies; + bad_proxies.push_back(proxy_server); + list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); + list.UpdateRetryInfoOnFallback( + &retry_info_map, base::TimeDelta::FromSeconds(60), true, bad_proxies, + ERR_NAME_RESOLUTION_FAILED, net_log); + EXPECT_TRUE(retry_info_map.end() != retry_info_map.find("foopy1:80")); + EXPECT_EQ(ERR_NAME_RESOLUTION_FAILED, + retry_info_map[proxy_server.ToURI()].net_error); + EXPECT_TRUE(retry_info_map.end() == retry_info_map.find("foopy2:80")); + EXPECT_TRUE(retry_info_map.end() != retry_info_map.find("foopy3:80")); + } + // If the first proxy is DIRECT, nothing is added to the retry list, even + // if another bad proxy is specified. + { + ProxyList list; + ProxyRetryInfoMap retry_info_map; + NetLogWithSource net_log; + ProxyServer proxy_server = ProxyServer::FromURI("foopy2:80", + ProxyServer::SCHEME_HTTP); + std::vector<ProxyServer> bad_proxies; + bad_proxies.push_back(proxy_server); + list.SetFromPacString("DIRECT;PROXY foopy2:80;PROXY foopy3:80"); + list.UpdateRetryInfoOnFallback(&retry_info_map, + base::TimeDelta::FromSeconds(60), true, + bad_proxies, OK, net_log); + EXPECT_TRUE(retry_info_map.end() == retry_info_map.find("foopy2:80")); + EXPECT_TRUE(retry_info_map.end() == retry_info_map.find("foopy3:80")); + } + // If the bad proxy is already on the retry list, and the old retry info would + // cause the proxy to be retried later than the newly specified retry info, + // then the old retry info should be kept. + { + ProxyList list; + ProxyRetryInfoMap retry_info_map; + NetLogWithSource net_log; + list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); + + // First, mark the proxy as bad for 60 seconds. + list.UpdateRetryInfoOnFallback( + &retry_info_map, base::TimeDelta::FromSeconds(60), true, + std::vector<ProxyServer>(), ERR_PROXY_CONNECTION_FAILED, net_log); + // Next, mark the same proxy as bad for 1 second. This call should have no + // effect, since this would cause the bad proxy to be retried sooner than + // the existing retry info. + list.UpdateRetryInfoOnFallback(&retry_info_map, + base::TimeDelta::FromSeconds(1), false, + std::vector<ProxyServer>(), OK, net_log); + EXPECT_TRUE(retry_info_map.end() != retry_info_map.find("foopy1:80")); + EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, + retry_info_map["foopy1:80"].net_error); + EXPECT_TRUE(retry_info_map["foopy1:80"].try_while_bad); + EXPECT_EQ(base::TimeDelta::FromSeconds(60), + retry_info_map["foopy1:80"].current_delay); + EXPECT_GT(retry_info_map["foopy1:80"].bad_until, + base::TimeTicks::Now() + base::TimeDelta::FromSeconds(30)); + EXPECT_TRUE(retry_info_map.end() == retry_info_map.find("foopy2:80")); + EXPECT_TRUE(retry_info_map.end() == retry_info_map.find("foopy3:80")); + } + // If the bad proxy is already on the retry list, and the newly specified + // retry info would cause the proxy to be retried later than the old retry + // info, then the old retry info should be replaced with the new retry info. + { + ProxyList list; + ProxyRetryInfoMap retry_info_map; + NetLogWithSource net_log; + list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); + + // First, mark the proxy as bad for 1 second. + list.UpdateRetryInfoOnFallback(&retry_info_map, + base::TimeDelta::FromSeconds(1), false, + std::vector<ProxyServer>(), OK, net_log); + // Next, mark the same proxy as bad for 60 seconds. This call should replace + // the existing retry info with the new 60 second retry info. + list.UpdateRetryInfoOnFallback( + &retry_info_map, base::TimeDelta::FromSeconds(60), true, + std::vector<ProxyServer>(), ERR_PROXY_CONNECTION_FAILED, net_log); + + EXPECT_TRUE(retry_info_map.end() != retry_info_map.find("foopy1:80")); + EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, + retry_info_map["foopy1:80"].net_error); + EXPECT_TRUE(retry_info_map["foopy1:80"].try_while_bad); + EXPECT_EQ(base::TimeDelta::FromSeconds(60), + retry_info_map["foopy1:80"].current_delay); + EXPECT_GT(retry_info_map["foopy1:80"].bad_until, + base::TimeTicks::Now() + base::TimeDelta::FromSeconds(30)); + EXPECT_TRUE(retry_info_map.end() == retry_info_map.find("foopy2:80")); + EXPECT_TRUE(retry_info_map.end() == retry_info_map.find("foopy3:80")); + } +} + +} // anonymous namespace + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_resolver.h b/chromium/net/proxy_resolution/proxy_resolver.h new file mode 100644 index 00000000000..f89d34771b5 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver.h @@ -0,0 +1,58 @@ +// 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_RESOLUTION_PROXY_RESOLVER_H_ +#define NET_PROXY_RESOLUTION_PROXY_RESOLVER_H_ + +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "net/base/completion_callback.h" +#include "net/base/load_states.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/pac_file_data.h" +#include "url/gurl.h" + +namespace net { + +class NetLogWithSource; +class ProxyInfo; + +// Interface for "proxy resolvers". A ProxyResolver fills in a list of proxies +// to use for a particular URL. Generally the backend for a ProxyResolver is +// a PAC script, but it doesn't need to be. ProxyResolver can service multiple +// requests at a time. +class NET_EXPORT_PRIVATE ProxyResolver { + public: + class Request { + public: + virtual ~Request() {} // Cancels the request + virtual LoadState GetLoadState() = 0; + }; + + ProxyResolver() {} + + virtual ~ProxyResolver() {} + + // Gets a list of proxy servers to use for |url|. If the request will + // complete asynchronously returns ERR_IO_PENDING and notifies the result + // by running |callback|. If the result code is OK then + // the request was successful and |results| contains the proxy + // resolution information. In the case of asynchronous completion + // |*request| is written to. Call request_.reset() to cancel the request + virtual int GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(ProxyResolver); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_RESOLVER_H_ diff --git a/chromium/net/proxy_resolution/proxy_resolver_error_observer.h b/chromium/net/proxy_resolution/proxy_resolver_error_observer.h new file mode 100644 index 00000000000..e48bacc7ff9 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_error_observer.h @@ -0,0 +1,37 @@ +// 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_RESOLUTION_PROXY_RESOLVER_ERROR_OBSERVER_H_ +#define NET_PROXY_RESOLUTION_PROXY_RESOLVER_ERROR_OBSERVER_H_ + +#include "base/macros.h" +#include "base/strings/string16.h" +#include "net/base/net_export.h" + +namespace net { + +// Interface for observing JavaScript error messages from PAC scripts. +class NET_EXPORT_PRIVATE ProxyResolverErrorObserver { + public: + ProxyResolverErrorObserver() {} + virtual ~ProxyResolverErrorObserver() {} + + // Handler for when an error is encountered. |line_number| may be -1 + // if a line number is not applicable to this error. |error| is a message + // describing the error. + // + // Note on threading: This may get called from a worker thread. If the + // backing proxy resolver is ProxyResolverV8Tracing, then it will not + // be called concurrently, however it will be called from a different + // thread than the proxy resolver's origin thread. + virtual void OnPACScriptError(int line_number, + const base::string16& error) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(ProxyResolverErrorObserver); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_RESOLVER_ERROR_OBSERVER_H_ diff --git a/chromium/net/proxy_resolution/proxy_resolver_factory.cc b/chromium/net/proxy_resolution/proxy_resolver_factory.cc new file mode 100644 index 00000000000..24500c0fea9 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_factory.cc @@ -0,0 +1,18 @@ +// Copyright 2015 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_resolution/proxy_resolver_factory.h" + +#include "net/base/net_errors.h" +#include "net/proxy_resolution/proxy_resolver.h" + +namespace net { + +ProxyResolverFactory::ProxyResolverFactory(bool expects_pac_bytes) + : expects_pac_bytes_(expects_pac_bytes) { +} + +ProxyResolverFactory::~ProxyResolverFactory() = default; + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_resolver_factory.h b/chromium/net/proxy_resolution/proxy_resolver_factory.h new file mode 100644 index 00000000000..7a9b66c5d10 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_factory.h @@ -0,0 +1,61 @@ +// Copyright 2015 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_RESOLUTION_PROXY_RESOLVER_FACTORY_H_ +#define NET_PROXY_RESOLUTION_PROXY_RESOLVER_FACTORY_H_ + +#include <memory> +#include <set> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "net/base/completion_callback.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/pac_file_data.h" + +namespace net { + +class ProxyResolver; + +// ProxyResolverFactory is an interface for creating ProxyResolver instances. +class NET_EXPORT ProxyResolverFactory { + public: + // A handle to a request. Deleting it will cancel the request. + class Request { + public: + virtual ~Request() {} + }; + + // See |expects_pac_bytes()| for the meaning of |expects_pac_bytes|. + explicit ProxyResolverFactory(bool expects_pac_bytes); + + virtual ~ProxyResolverFactory(); + + // Creates a new ProxyResolver. If the request will complete asynchronously, + // it returns ERR_IO_PENDING and notifies the result by running |callback|. + // If the result is OK, then |resolver| contains the ProxyResolver. In the + // case of asynchronous completion |*request| is written to, and can be + // deleted to cancel the request. All requests in progress are cancelled if + // the ProxyResolverFactory is deleted. + virtual int CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const net::CompletionCallback& callback, + std::unique_ptr<Request>* request) = 0; + + // The PAC script backend can be specified to the ProxyResolverFactory either + // via URL, or via the javascript text itself. If |expects_pac_bytes| is true, + // then the ProxyResolverScriptData passed to CreateProxyResolver() should + // contain the actual script bytes rather than just the URL. + bool expects_pac_bytes() const { return expects_pac_bytes_; } + + private: + bool expects_pac_bytes_; + + DISALLOW_COPY_AND_ASSIGN(ProxyResolverFactory); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_RESOLVER_FACTORY_H_ diff --git a/chromium/net/proxy_resolution/proxy_resolver_mac.cc b/chromium/net/proxy_resolution/proxy_resolver_mac.cc new file mode 100644 index 00000000000..6d13cbd72af --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_mac.cc @@ -0,0 +1,356 @@ +// 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_resolution/proxy_resolver_mac.h" + +#include <CoreFoundation/CoreFoundation.h> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" +#include "net/base/net_errors.h" +#include "net/base/proxy_server.h" +#include "net/proxy_resolution/proxy_info.h" +#include "net/proxy_resolution/proxy_resolver.h" + +#if defined(OS_IOS) +#include <CFNetwork/CFProxySupport.h> +#else +#include <CoreServices/CoreServices.h> +#endif + +namespace net { + +namespace { + +// A lock shared by all ProxyResolverMac instances. It is used to synchronize +// the events of multiple CFNetworkExecuteProxyAutoConfigurationURL run loop +// sources. These events are: +// 1. Adding the source to the run loop. +// 2. Handling the source result. +// 3. Removing the source from the run loop. +static base::LazyInstance<base::Lock>::Leaky g_cfnetwork_pac_runloop_lock = + LAZY_INSTANCE_INITIALIZER; + +// Forward declaration of the callback function used by the +// SynchronizedRunLoopObserver class. +void RunLoopObserverCallBackFunc(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void* info); + +// Utility function to map a CFProxyType to a ProxyServer::Scheme. +// If the type is unknown, returns ProxyServer::SCHEME_INVALID. +ProxyServer::Scheme GetProxyServerScheme(CFStringRef proxy_type) { + if (CFEqual(proxy_type, kCFProxyTypeNone)) + return ProxyServer::SCHEME_DIRECT; + if (CFEqual(proxy_type, kCFProxyTypeHTTP)) + return ProxyServer::SCHEME_HTTP; + if (CFEqual(proxy_type, kCFProxyTypeHTTPS)) { + // The "HTTPS" on the Mac side here means "proxy applies to https://" URLs; + // the proxy itself is still expected to be an HTTP proxy. + return ProxyServer::SCHEME_HTTP; + } + if (CFEqual(proxy_type, kCFProxyTypeSOCKS)) { + // We can't tell whether this was v4 or v5. We will assume it is + // v5 since that is the only version OS X supports. + return ProxyServer::SCHEME_SOCKS5; + } + return ProxyServer::SCHEME_INVALID; +} + +// Callback for CFNetworkExecuteProxyAutoConfigurationURL. |client| is a pointer +// to a CFTypeRef. This stashes either |error| or |proxies| in that location. +void ResultCallback(void* client, CFArrayRef proxies, CFErrorRef error) { + DCHECK((proxies != NULL) == (error == NULL)); + + CFTypeRef* result_ptr = reinterpret_cast<CFTypeRef*>(client); + DCHECK(result_ptr != NULL); + DCHECK(*result_ptr == NULL); + + if (error != NULL) { + *result_ptr = CFRetain(error); + } else { + *result_ptr = CFRetain(proxies); + } + CFRunLoopStop(CFRunLoopGetCurrent()); +} + +#pragma mark - SynchronizedRunLoopObserver +// A run loop observer that guarantees that no two run loop sources protected +// by the same lock will be fired concurrently in different threads. +// The observer does not prevent the parallel execution of the sources but only +// synchronizes the run loop events associated with the sources. In the context +// of proxy resolver, the observer is used to synchronize the execution of the +// callbacks function that handles the result of +// CFNetworkExecuteProxyAutoConfigurationURL execution. +class SynchronizedRunLoopObserver final { + public: + // Creates the instance of an observer that will synchronize the sources + // using a given |lock|. + SynchronizedRunLoopObserver(base::Lock& lock); + // Destructor. + ~SynchronizedRunLoopObserver(); + // Adds the observer to the current run loop for a given run loop mode. + // This method should always be paired with |RemoveFromCurrentRunLoop|. + void AddToCurrentRunLoop(const CFStringRef mode); + // Removes the observer from the current run loop for a given run loop mode. + // This method should always be paired with |AddToCurrentRunLoop|. + void RemoveFromCurrentRunLoop(const CFStringRef mode); + // Callback function that is called when an observable run loop event occurs. + void RunLoopObserverCallBack(CFRunLoopObserverRef observer, + CFRunLoopActivity activity); + + private: + // Lock to use to synchronize the run loop sources. + base::Lock& lock_; + // Indicates whether the current observer holds the lock. It is used to + // avoid double locking and releasing. + bool lock_acquired_; + // The underlying CFRunLoopObserverRef structure wrapped by this instance. + base::ScopedCFTypeRef<CFRunLoopObserverRef> observer_; + // Validates that all methods of this class are executed on the same thread. + base::ThreadChecker thread_checker_; + DISALLOW_COPY_AND_ASSIGN(SynchronizedRunLoopObserver); +}; + +SynchronizedRunLoopObserver::SynchronizedRunLoopObserver(base::Lock& lock) + : lock_(lock), lock_acquired_(false) { + CFRunLoopObserverContext observer_context = {0, this, NULL, NULL, NULL}; + observer_.reset(CFRunLoopObserverCreate( + kCFAllocatorDefault, + kCFRunLoopBeforeSources | kCFRunLoopBeforeWaiting | kCFRunLoopExit, true, + 0, RunLoopObserverCallBackFunc, &observer_context)); +} + +SynchronizedRunLoopObserver::~SynchronizedRunLoopObserver() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!lock_acquired_); +} + +void SynchronizedRunLoopObserver::AddToCurrentRunLoop(const CFStringRef mode) { + DCHECK(thread_checker_.CalledOnValidThread()); + CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer_.get(), mode); +} + +void SynchronizedRunLoopObserver::RemoveFromCurrentRunLoop( + const CFStringRef mode) { + DCHECK(thread_checker_.CalledOnValidThread()); + CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer_.get(), mode); +} + +void SynchronizedRunLoopObserver::RunLoopObserverCallBack( + CFRunLoopObserverRef observer, + CFRunLoopActivity activity) { + DCHECK(thread_checker_.CalledOnValidThread()); + // Acquire the lock when a source has been signaled and going to be fired. + // In the context of the proxy resolver that happens when the proxy for a + // given URL has been resolved and the callback function that handles the + // result is going to be fired. + // Release the lock when all source events have been handled. + switch (activity) { + case kCFRunLoopBeforeSources: + if (!lock_acquired_) { + lock_.Acquire(); + lock_acquired_ = true; + } + break; + case kCFRunLoopBeforeWaiting: + case kCFRunLoopExit: + if (lock_acquired_) { + lock_acquired_ = false; + lock_.Release(); + } + break; + } +} + +void RunLoopObserverCallBackFunc(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void* info) { + // Forward the call to the instance of SynchronizedRunLoopObserver + // that is associated with the current CF run loop observer. + SynchronizedRunLoopObserver* observerInstance = + (SynchronizedRunLoopObserver*)info; + observerInstance->RunLoopObserverCallBack(observer, activity); +} + +#pragma mark - ProxyResolverMac +class ProxyResolverMac : public ProxyResolver { + public: + explicit ProxyResolverMac( + const scoped_refptr<ProxyResolverScriptData>& script_data); + ~ProxyResolverMac() override; + + // ProxyResolver methods: + int GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) override; + + private: + const scoped_refptr<ProxyResolverScriptData> script_data_; +}; + +ProxyResolverMac::ProxyResolverMac( + const scoped_refptr<ProxyResolverScriptData>& script_data) + : script_data_(script_data) { +} + +ProxyResolverMac::~ProxyResolverMac() {} + +// Gets the proxy information for a query URL from a PAC. Implementation +// inspired by http://developer.apple.com/samplecode/CFProxySupportTool/ +int ProxyResolverMac::GetProxyForURL(const GURL& query_url, + ProxyInfo* results, + const CompletionCallback& /*callback*/, + std::unique_ptr<Request>* /*request*/, + const NetLogWithSource& net_log) { + base::ScopedCFTypeRef<CFStringRef> query_ref( + base::SysUTF8ToCFStringRef(query_url.spec())); + base::ScopedCFTypeRef<CFURLRef> query_url_ref( + CFURLCreateWithString(kCFAllocatorDefault, query_ref.get(), NULL)); + if (!query_url_ref.get()) + return ERR_FAILED; + base::ScopedCFTypeRef<CFStringRef> pac_ref(base::SysUTF8ToCFStringRef( + script_data_->type() == ProxyResolverScriptData::TYPE_AUTO_DETECT + ? std::string() + : script_data_->url().spec())); + base::ScopedCFTypeRef<CFURLRef> pac_url_ref( + CFURLCreateWithString(kCFAllocatorDefault, pac_ref.get(), NULL)); + if (!pac_url_ref.get()) + return ERR_FAILED; + + // Work around <rdar://problem/5530166>. This dummy call to + // CFNetworkCopyProxiesForURL initializes some state within CFNetwork that is + // required by CFNetworkExecuteProxyAutoConfigurationURL. + + base::ScopedCFTypeRef<CFDictionaryRef> empty_dictionary( + CFDictionaryCreate(NULL, NULL, NULL, 0, NULL, NULL)); + CFArrayRef dummy_result = + CFNetworkCopyProxiesForURL(query_url_ref.get(), empty_dictionary); + if (dummy_result) + CFRelease(dummy_result); + + // We cheat here. We need to act as if we were synchronous, so we pump the + // runloop ourselves. Our caller moved us to a new thread anyway, so this is + // OK to do. (BTW, CFNetworkExecuteProxyAutoConfigurationURL returns a + // runloop source we need to release despite its name.) + + CFTypeRef result = NULL; + CFStreamClientContext context = { 0, &result, NULL, NULL, NULL }; + base::ScopedCFTypeRef<CFRunLoopSourceRef> runloop_source( + CFNetworkExecuteProxyAutoConfigurationURL( + pac_url_ref.get(), query_url_ref.get(), ResultCallback, &context)); + if (!runloop_source) + return ERR_FAILED; + + const CFStringRef private_runloop_mode = + CFSTR("org.chromium.ProxyResolverMac"); + + // Add the run loop observer to synchronize events of + // CFNetworkExecuteProxyAutoConfigurationURL sources. See the definition of + // |g_cfnetwork_pac_runloop_lock|. + SynchronizedRunLoopObserver observer(g_cfnetwork_pac_runloop_lock.Get()); + observer.AddToCurrentRunLoop(private_runloop_mode); + + // Make sure that no CFNetworkExecuteProxyAutoConfigurationURL sources + // are added to the run loop concurrently. + { + base::AutoLock lock(g_cfnetwork_pac_runloop_lock.Get()); + CFRunLoopAddSource(CFRunLoopGetCurrent(), runloop_source.get(), + private_runloop_mode); + } + + CFRunLoopRunInMode(private_runloop_mode, DBL_MAX, false); + + // Make sure that no CFNetworkExecuteProxyAutoConfigurationURL sources + // are removed from the run loop concurrently. + { + base::AutoLock lock(g_cfnetwork_pac_runloop_lock.Get()); + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop_source.get(), + private_runloop_mode); + } + observer.RemoveFromCurrentRunLoop(private_runloop_mode); + + DCHECK(result != NULL); + + if (CFGetTypeID(result) == CFErrorGetTypeID()) { + // TODO(avi): do something better than this + CFRelease(result); + return ERR_FAILED; + } + base::ScopedCFTypeRef<CFArrayRef> proxy_array_ref( + base::mac::CFCastStrict<CFArrayRef>(result)); + DCHECK(proxy_array_ref != NULL); + + // This string will be an ordered list of <proxy-uri> entries, separated by + // semi-colons. It is the format that ProxyInfo::UseNamedProxy() expects. + // proxy-uri = [<proxy-scheme>"://"]<proxy-host>":"<proxy-port> + // (This also includes entries for direct connection, as "direct://"). + std::string proxy_uri_list; + + CFIndex proxy_array_count = CFArrayGetCount(proxy_array_ref.get()); + for (CFIndex i = 0; i < proxy_array_count; ++i) { + CFDictionaryRef proxy_dictionary = base::mac::CFCastStrict<CFDictionaryRef>( + CFArrayGetValueAtIndex(proxy_array_ref.get(), i)); + DCHECK(proxy_dictionary != NULL); + + // The dictionary may have the following keys: + // - kCFProxyTypeKey : The type of the proxy + // - kCFProxyHostNameKey + // - kCFProxyPortNumberKey : The meat we're after. + // - kCFProxyUsernameKey + // - kCFProxyPasswordKey : Despite the existence of these keys in the + // documentation, they're never populated. Even if a + // username/password were to be set in the network + // proxy system preferences, we'd need to fetch it + // from the Keychain ourselves. CFProxy is such a + // tease. + // - kCFProxyAutoConfigurationURLKey : If the PAC file specifies another + // PAC file, I'm going home. + + CFStringRef proxy_type = base::mac::GetValueFromDictionary<CFStringRef>( + proxy_dictionary, kCFProxyTypeKey); + ProxyServer proxy_server = ProxyServer::FromDictionary( + GetProxyServerScheme(proxy_type), + proxy_dictionary, + kCFProxyHostNameKey, + kCFProxyPortNumberKey); + if (!proxy_server.is_valid()) + continue; + + if (!proxy_uri_list.empty()) + proxy_uri_list += ";"; + proxy_uri_list += proxy_server.ToURI(); + } + + if (!proxy_uri_list.empty()) + results->UseNamedProxy(proxy_uri_list); + // Else do nothing (results is already guaranteed to be in the default state). + + return OK; +} + +} // namespace + +ProxyResolverFactoryMac::ProxyResolverFactoryMac() + : ProxyResolverFactory(false /*expects_pac_bytes*/) { +} + +int ProxyResolverFactoryMac::CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback, + std::unique_ptr<Request>* request) { + resolver->reset(new ProxyResolverMac(pac_script)); + return OK; +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_resolver_mac.h b/chromium/net/proxy_resolution/proxy_resolver_mac.h new file mode 100644 index 00000000000..866bb1a4e6c --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_mac.h @@ -0,0 +1,36 @@ +// 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_RESOLUTION_PROXY_RESOLVER_MAC_H_ +#define NET_PROXY_RESOLUTION_PROXY_RESOLVER_MAC_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/proxy_resolver_factory.h" +#include "url/gurl.h" + +namespace net { + +// Implementation of ProxyResolverFactory that uses the Mac CFProxySupport to +// implement proxies. +// TODO(kapishnikov): make ProxyResolverMac async as per +// https://bugs.chromium.org/p/chromium/issues/detail?id=166387#c95 +class NET_EXPORT ProxyResolverFactoryMac : public ProxyResolverFactory { + public: + ProxyResolverFactoryMac(); + + int CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback, + std::unique_ptr<Request>* request) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ProxyResolverFactoryMac); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_RESOLVER_MAC_H_ diff --git a/chromium/net/proxy_resolution/proxy_resolver_v8.cc b/chromium/net/proxy_resolution/proxy_resolver_v8.cc new file mode 100644 index 00000000000..71072ea8e81 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_v8.cc @@ -0,0 +1,910 @@ +// Copyright (c) 2012 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_resolution/proxy_resolver_v8.h" + +#include <algorithm> +#include <cstdio> +#include <utility> + +#include "base/auto_reset.h" +#include "base/compiler_specific.h" +#include "base/debug/leak_annotations.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/strings/string_tokenizer.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_task_runner_handle.h" +#include "gin/array_buffer.h" +#include "gin/public/isolate_holder.h" +#include "gin/v8_initializer.h" +#include "net/base/ip_address.h" +#include "net/base/net_errors.h" +#include "net/proxy_resolution/pac_file_data.h" +#include "net/proxy_resolution/pac_js_library.h" +#include "net/proxy_resolution/proxy_info.h" +#include "url/gurl.h" +#include "url/url_canon.h" +#include "v8/include/v8.h" + +// Notes on the javascript environment: +// +// For the majority of the PAC utility functions, we use the same code +// as Firefox. See the javascript library that pac_js_library.h pulls in. +// +// In addition, we implement a subset of Microsoft's extensions to PAC. +// - myIpAddressEx() +// - dnsResolveEx() +// - isResolvableEx() +// - isInNetEx() +// - sortIpAddressList() +// +// It is worth noting that the original PAC specification does not describe +// the return values on failure. Consequently, there are compatibility +// differences between browsers on what to return on failure, which are +// illustrated below: +// +// --------------------+-------------+-------------------+-------------- +// | Firefox3 | InternetExplorer8 | --> Us <--- +// --------------------+-------------+-------------------+-------------- +// myIpAddress() | "127.0.0.1" | ??? | "127.0.0.1" +// dnsResolve() | null | false | null +// myIpAddressEx() | N/A | "" | "" +// sortIpAddressList() | N/A | false | false +// dnsResolveEx() | N/A | "" | "" +// isInNetEx() | N/A | false | false +// --------------------+-------------+-------------------+-------------- +// +// TODO(eroman): The cell above reading ??? means I didn't test it. +// +// Another difference is in how dnsResolve() and myIpAddress() are +// implemented -- whether they should restrict to IPv4 results, or +// include both IPv4 and IPv6. The following table illustrates the +// differences: +// +// --------------------+-------------+-------------------+-------------- +// | Firefox3 | InternetExplorer8 | --> Us <--- +// --------------------+-------------+-------------------+-------------- +// myIpAddress() | IPv4/IPv6 | IPv4 | IPv4 +// dnsResolve() | IPv4/IPv6 | IPv4 | IPv4 +// isResolvable() | IPv4/IPv6 | IPv4 | IPv4 +// myIpAddressEx() | N/A | IPv4/IPv6 | IPv4/IPv6 +// dnsResolveEx() | N/A | IPv4/IPv6 | IPv4/IPv6 +// sortIpAddressList() | N/A | IPv4/IPv6 | IPv4/IPv6 +// isResolvableEx() | N/A | IPv4/IPv6 | IPv4/IPv6 +// isInNetEx() | N/A | IPv4/IPv6 | IPv4/IPv6 +// -----------------+-------------+-------------------+-------------- + +namespace net { + +namespace { + +// Pseudo-name for the PAC script. +const char kPacResourceName[] = "proxy-pac-script.js"; +// Pseudo-name for the PAC utility script. +const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js"; + +// External string wrapper so V8 can access the UTF16 string wrapped by +// ProxyResolverScriptData. +class V8ExternalStringFromScriptData + : public v8::String::ExternalStringResource { + public: + explicit V8ExternalStringFromScriptData( + const scoped_refptr<ProxyResolverScriptData>& script_data) + : script_data_(script_data) {} + + const uint16_t* data() const override { + return reinterpret_cast<const uint16_t*>(script_data_->utf16().data()); + } + + size_t length() const override { return script_data_->utf16().size(); } + + private: + const scoped_refptr<ProxyResolverScriptData> script_data_; + DISALLOW_COPY_AND_ASSIGN(V8ExternalStringFromScriptData); +}; + +// External string wrapper so V8 can access a string literal. +class V8ExternalASCIILiteral + : public v8::String::ExternalOneByteStringResource { + public: + // |ascii| must be a NULL-terminated C string, and must remain valid + // throughout this object's lifetime. + V8ExternalASCIILiteral(const char* ascii, size_t length) + : ascii_(ascii), length_(length) { + DCHECK(base::IsStringASCII(ascii)); + } + + const char* data() const override { return ascii_; } + + size_t length() const override { return length_; } + + private: + const char* ascii_; + size_t length_; + DISALLOW_COPY_AND_ASSIGN(V8ExternalASCIILiteral); +}; + +// When creating a v8::String from a C++ string we have two choices: create +// a copy, or create a wrapper that shares the same underlying storage. +// For small strings it is better to just make a copy, whereas for large +// strings there are savings by sharing the storage. This number identifies +// the cutoff length for when to start wrapping rather than creating copies. +const size_t kMaxStringBytesForCopy = 256; + +// Converts a V8 String to a UTF8 std::string. +std::string V8StringToUTF8(v8::Local<v8::String> s) { + int len = s->Length(); + std::string result; + if (len > 0) + s->WriteUtf8(base::WriteInto(&result, len + 1)); + return result; +} + +// Converts a V8 String to a UTF16 base::string16. +base::string16 V8StringToUTF16(v8::Local<v8::String> s) { + int len = s->Length(); + base::string16 result; + // Note that the reinterpret cast is because on Windows string16 is an alias + // to wstring, and hence has character type wchar_t not uint16_t. + if (len > 0) { + s->Write(reinterpret_cast<uint16_t*>(base::WriteInto(&result, len + 1)), 0, + len); + } + return result; +} + +// Converts an ASCII std::string to a V8 string. +v8::Local<v8::String> ASCIIStringToV8String(v8::Isolate* isolate, + const std::string& s) { + DCHECK(base::IsStringASCII(s)); + return v8::String::NewFromUtf8(isolate, s.data(), v8::NewStringType::kNormal, + s.size()).ToLocalChecked(); +} + +// Converts a UTF16 base::string16 (warpped by a ProxyResolverScriptData) to a +// V8 string. +v8::Local<v8::String> ScriptDataToV8String( + v8::Isolate* isolate, const scoped_refptr<ProxyResolverScriptData>& s) { + if (s->utf16().size() * 2 <= kMaxStringBytesForCopy) { + return v8::String::NewFromTwoByte( + isolate, reinterpret_cast<const uint16_t*>(s->utf16().data()), + v8::NewStringType::kNormal, s->utf16().size()).ToLocalChecked(); + } + return v8::String::NewExternalTwoByte( + isolate, new V8ExternalStringFromScriptData(s)).ToLocalChecked(); +} + +// Converts an ASCII string literal to a V8 string. +v8::Local<v8::String> ASCIILiteralToV8String(v8::Isolate* isolate, + const char* ascii) { + DCHECK(base::IsStringASCII(ascii)); + size_t length = strlen(ascii); + if (length <= kMaxStringBytesForCopy) + return v8::String::NewFromUtf8(isolate, ascii, v8::NewStringType::kNormal, + length).ToLocalChecked(); + return v8::String::NewExternalOneByte( + isolate, new V8ExternalASCIILiteral(ascii, length)) + .ToLocalChecked(); +} + +// Stringizes a V8 object by calling its toString() method. Returns true +// on success. This may fail if the toString() throws an exception. +bool V8ObjectToUTF16String(v8::Local<v8::Value> object, + base::string16* utf16_result, + v8::Isolate* isolate) { + if (object.IsEmpty()) + return false; + + v8::HandleScope scope(isolate); + v8::Local<v8::String> str_object; + if (!object->ToString(isolate->GetCurrentContext()).ToLocal(&str_object)) + return false; + *utf16_result = V8StringToUTF16(str_object); + return true; +} + +// Extracts an hostname argument from |args|. On success returns true +// and fills |*hostname| with the result. +bool GetHostnameArgument(const v8::FunctionCallbackInfo<v8::Value>& args, + std::string* hostname) { + // The first argument should be a string. + if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString()) + return false; + + const base::string16 hostname_utf16 = + V8StringToUTF16(v8::Local<v8::String>::Cast(args[0])); + + // If the hostname is already in ASCII, simply return it as is. + if (base::IsStringASCII(hostname_utf16)) { + *hostname = base::UTF16ToASCII(hostname_utf16); + return true; + } + + // Otherwise try to convert it from IDN to punycode. + const int kInitialBufferSize = 256; + url::RawCanonOutputT<base::char16, kInitialBufferSize> punycode_output; + if (!url::IDNToASCII(hostname_utf16.data(), hostname_utf16.length(), + &punycode_output)) { + return false; + } + + // |punycode_output| should now be ASCII; convert it to a std::string. + // (We could use UTF16ToASCII() instead, but that requires an extra string + // copy. Since ASCII is a subset of UTF8 the following is equivalent). + bool success = base::UTF16ToUTF8(punycode_output.data(), + punycode_output.length(), + hostname); + DCHECK(success); + DCHECK(base::IsStringASCII(*hostname)); + return success; +} + +// Wrapper around an IP address that stores the original string as well as a +// corresponding parsed IPAddress. + +// This struct is used as a helper for sorting IP address strings - the IP +// literal is parsed just once and used as the sorting key, while also +// preserving the original IP literal string. +struct IPAddressSortingEntry { + IPAddressSortingEntry(const std::string& ip_string, + const IPAddress& ip_address) + : string_value(ip_string), ip_address(ip_address) {} + + // Used for sorting IP addresses in ascending order in SortIpAddressList(). + // IPv6 addresses are placed ahead of IPv4 addresses. + bool operator<(const IPAddressSortingEntry& rhs) const { + const IPAddress& ip1 = this->ip_address; + const IPAddress& ip2 = rhs.ip_address; + if (ip1.size() != ip2.size()) + return ip1.size() > ip2.size(); // IPv6 before IPv4. + return ip1 < ip2; // Ascending order. + } + + std::string string_value; + IPAddress ip_address; +}; + +// Handler for "sortIpAddressList(IpAddressList)". |ip_address_list| is a +// semi-colon delimited string containing IP addresses. +// |sorted_ip_address_list| is the resulting list of sorted semi-colon delimited +// IP addresses or an empty string if unable to sort the IP address list. +// Returns 'true' if the sorting was successful, and 'false' if the input was an +// empty string, a string of separators (";" in this case), or if any of the IP +// addresses in the input list failed to parse. +bool SortIpAddressList(const std::string& ip_address_list, + std::string* sorted_ip_address_list) { + sorted_ip_address_list->clear(); + + // Strip all whitespace (mimics IE behavior). + std::string cleaned_ip_address_list; + base::RemoveChars(ip_address_list, " \t", &cleaned_ip_address_list); + if (cleaned_ip_address_list.empty()) + return false; + + // Split-up IP addresses and store them in a vector. + std::vector<IPAddressSortingEntry> ip_vector; + IPAddress ip_address; + base::StringTokenizer str_tok(cleaned_ip_address_list, ";"); + while (str_tok.GetNext()) { + if (!ip_address.AssignFromIPLiteral(str_tok.token())) + return false; + ip_vector.push_back(IPAddressSortingEntry(str_tok.token(), ip_address)); + } + + if (ip_vector.empty()) // Can happen if we have something like + return false; // sortIpAddressList(";") or sortIpAddressList("; ;") + + DCHECK(!ip_vector.empty()); + + // Sort lists according to ascending numeric value. + if (ip_vector.size() > 1) + std::stable_sort(ip_vector.begin(), ip_vector.end()); + + // Return a semi-colon delimited list of sorted addresses (IPv6 followed by + // IPv4). + for (size_t i = 0; i < ip_vector.size(); ++i) { + if (i > 0) + *sorted_ip_address_list += ";"; + *sorted_ip_address_list += ip_vector[i].string_value; + } + return true; +} + +// Handler for "isInNetEx(ip_address, ip_prefix)". |ip_address| is a string +// containing an IPv4/IPv6 address, and |ip_prefix| is a string containg a +// slash-delimited IP prefix with the top 'n' bits specified in the bit +// field. This returns 'true' if the address is in the same subnet, and +// 'false' otherwise. Also returns 'false' if the prefix is in an incorrect +// format, or if an address and prefix of different types are used (e.g. IPv6 +// address and IPv4 prefix). +bool IsInNetEx(const std::string& ip_address, const std::string& ip_prefix) { + IPAddress address; + if (!address.AssignFromIPLiteral(ip_address)) + return false; + + IPAddress prefix; + size_t prefix_length_in_bits; + if (!ParseCIDRBlock(ip_prefix, &prefix, &prefix_length_in_bits)) + return false; + + // Both |address| and |prefix| must be of the same type (IPv4 or IPv6). + if (address.size() != prefix.size()) + return false; + + DCHECK((address.IsIPv4() && prefix.IsIPv4()) || + (address.IsIPv6() && prefix.IsIPv6())); + + return IPAddressMatchesPrefix(address, prefix, prefix_length_in_bits); +} + +// Consider only single component domains like 'foo' as plain host names. +bool IsPlainHostName(const std::string& hostname_utf8) { + if (hostname_utf8.find('.') != std::string::npos) + return false; + + // IPv6 literals might not contain any periods, however are not considered + // plain host names. + IPAddress unused; + return !unused.AssignFromIPLiteral(hostname_utf8); +} + +// All instances of ProxyResolverV8 share the same v8::Isolate. This isolate is +// created lazily the first time it is needed and lives until process shutdown. +// This creation might happen from any thread, as ProxyResolverV8 is typically +// run in a threadpool. +// +// TODO(eroman): The lazily created isolate is never freed. Instead it should be +// disposed once there are no longer any ProxyResolverV8 referencing it. +class SharedIsolateFactory { + public: + SharedIsolateFactory() : has_initialized_v8_(false) {} + + // Lazily creates a v8::Isolate, or returns the already created instance. + v8::Isolate* GetSharedIsolate() { + base::AutoLock lock(lock_); + + if (!holder_) { + // Do one-time initialization for V8. + if (!has_initialized_v8_) { +#ifdef V8_USE_EXTERNAL_STARTUP_DATA + gin::V8Initializer::LoadV8Snapshot(); + gin::V8Initializer::LoadV8Natives(); +#endif + + // The performance of the proxy resolver is limited by DNS resolution, + // and not V8, so tune down V8 to use as little memory as possible. + static const char kOptimizeForSize[] = "--optimize_for_size"; + v8::V8::SetFlagsFromString(kOptimizeForSize, strlen(kOptimizeForSize)); + static const char kNoOpt[] = "--noopt"; + v8::V8::SetFlagsFromString(kNoOpt, strlen(kNoOpt)); + + gin::IsolateHolder::Initialize( + gin::IsolateHolder::kNonStrictMode, + gin::IsolateHolder::kStableV8Extras, + gin::ArrayBufferAllocator::SharedInstance()); + + has_initialized_v8_ = true; + } + + holder_.reset(new gin::IsolateHolder(base::ThreadTaskRunnerHandle::Get(), + gin::IsolateHolder::kUseLocker)); + } + + return holder_->isolate(); + } + + v8::Isolate* GetSharedIsolateWithoutCreating() { + base::AutoLock lock(lock_); + return holder_ ? holder_->isolate() : NULL; + } + + private: + base::Lock lock_; + std::unique_ptr<gin::IsolateHolder> holder_; + bool has_initialized_v8_; + + DISALLOW_COPY_AND_ASSIGN(SharedIsolateFactory); +}; + +base::LazyInstance<SharedIsolateFactory>::Leaky g_isolate_factory = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// ProxyResolverV8::Context --------------------------------------------------- + +class ProxyResolverV8::Context { + public: + explicit Context(v8::Isolate* isolate) + : js_bindings_(nullptr), isolate_(isolate) { + DCHECK(isolate); + } + + ~Context() { + v8::Locker locked(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + + v8_this_.Reset(); + v8_context_.Reset(); + } + + JSBindings* js_bindings() { return js_bindings_; } + + int ResolveProxy(const GURL& query_url, + ProxyInfo* results, + JSBindings* bindings) { + DCHECK(bindings); + base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings); + v8::Locker locked(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope scope(isolate_); + + v8::Local<v8::Context> context = + v8::Local<v8::Context>::New(isolate_, v8_context_); + v8::Context::Scope function_scope(context); + + v8::Local<v8::Value> function; + int rv = GetFindProxyForURL(&function); + if (rv != OK) + return rv; + + v8::Local<v8::Value> argv[] = { + ASCIIStringToV8String(isolate_, query_url.spec()), + ASCIIStringToV8String(isolate_, query_url.HostNoBrackets()), + }; + + v8::TryCatch try_catch(isolate_); + v8::Local<v8::Value> ret; + if (!v8::Function::Cast(*function) + ->Call(context, context->Global(), arraysize(argv), argv) + .ToLocal(&ret)) { + DCHECK(try_catch.HasCaught()); + HandleError(try_catch.Message()); + return ERR_PAC_SCRIPT_FAILED; + } + + if (!ret->IsString()) { + js_bindings()->OnError( + -1, base::ASCIIToUTF16("FindProxyForURL() did not return a string.")); + return ERR_PAC_SCRIPT_FAILED; + } + + base::string16 ret_str = V8StringToUTF16(v8::Local<v8::String>::Cast(ret)); + + if (!base::IsStringASCII(ret_str)) { + // TODO(eroman): Rather than failing when a wide string is returned, we + // could extend the parsing to handle IDNA hostnames by + // converting them to ASCII punycode. + // crbug.com/47234 + base::string16 error_message = + base::ASCIIToUTF16("FindProxyForURL() returned a non-ASCII string " + "(crbug.com/47234): ") + ret_str; + js_bindings()->OnError(-1, error_message); + return ERR_PAC_SCRIPT_FAILED; + } + + results->UsePacString(base::UTF16ToASCII(ret_str)); + return OK; + } + + int InitV8(const scoped_refptr<ProxyResolverScriptData>& pac_script, + JSBindings* bindings) { + base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings); + v8::Locker locked(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope scope(isolate_); + + v8_this_.Reset(isolate_, v8::External::New(isolate_, this)); + v8::Local<v8::External> v8_this = + v8::Local<v8::External>::New(isolate_, v8_this_); + v8::Local<v8::ObjectTemplate> global_template = + v8::ObjectTemplate::New(isolate_); + + // Attach the javascript bindings. + v8::Local<v8::FunctionTemplate> alert_template = + v8::FunctionTemplate::New(isolate_, &AlertCallback, v8_this); + alert_template->RemovePrototype(); + global_template->Set(ASCIILiteralToV8String(isolate_, "alert"), + alert_template); + + v8::Local<v8::FunctionTemplate> my_ip_address_template = + v8::FunctionTemplate::New(isolate_, &MyIpAddressCallback, v8_this); + my_ip_address_template->RemovePrototype(); + global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddress"), + my_ip_address_template); + + v8::Local<v8::FunctionTemplate> dns_resolve_template = + v8::FunctionTemplate::New(isolate_, &DnsResolveCallback, v8_this); + dns_resolve_template->RemovePrototype(); + global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolve"), + dns_resolve_template); + + v8::Local<v8::FunctionTemplate> is_plain_host_name_template = + v8::FunctionTemplate::New(isolate_, &IsPlainHostNameCallback, v8_this); + is_plain_host_name_template->RemovePrototype(); + global_template->Set(ASCIILiteralToV8String(isolate_, "isPlainHostName"), + is_plain_host_name_template); + + // Microsoft's PAC extensions: + + v8::Local<v8::FunctionTemplate> dns_resolve_ex_template = + v8::FunctionTemplate::New(isolate_, &DnsResolveExCallback, v8_this); + dns_resolve_ex_template->RemovePrototype(); + global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolveEx"), + dns_resolve_ex_template); + + v8::Local<v8::FunctionTemplate> my_ip_address_ex_template = + v8::FunctionTemplate::New(isolate_, &MyIpAddressExCallback, v8_this); + my_ip_address_ex_template->RemovePrototype(); + global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddressEx"), + my_ip_address_ex_template); + + v8::Local<v8::FunctionTemplate> sort_ip_address_list_template = + v8::FunctionTemplate::New(isolate_, + &SortIpAddressListCallback, + v8_this); + sort_ip_address_list_template->RemovePrototype(); + global_template->Set(ASCIILiteralToV8String(isolate_, "sortIpAddressList"), + sort_ip_address_list_template); + + v8::Local<v8::FunctionTemplate> is_in_net_ex_template = + v8::FunctionTemplate::New(isolate_, &IsInNetExCallback, v8_this); + is_in_net_ex_template->RemovePrototype(); + global_template->Set(ASCIILiteralToV8String(isolate_, "isInNetEx"), + is_in_net_ex_template); + + v8_context_.Reset( + isolate_, v8::Context::New(isolate_, NULL, global_template)); + + v8::Local<v8::Context> context = + v8::Local<v8::Context>::New(isolate_, v8_context_); + v8::Context::Scope ctx(context); + + // Add the PAC utility functions to the environment. + // (This script should never fail, as it is a string literal!) + // Note that the two string literals are concatenated. + int rv = RunScript( + ASCIILiteralToV8String(isolate_, PAC_JS_LIBRARY PAC_JS_LIBRARY_EX), + kPacUtilityResourceName); + if (rv != OK) { + NOTREACHED(); + return rv; + } + + // Add the user's PAC code to the environment. + rv = + RunScript(ScriptDataToV8String(isolate_, pac_script), kPacResourceName); + if (rv != OK) + return rv; + + // At a minimum, the FindProxyForURL() function must be defined for this + // to be a legitimiate PAC script. + v8::Local<v8::Value> function; + return GetFindProxyForURL(&function); + } + + private: + int GetFindProxyForURL(v8::Local<v8::Value>* function) { + v8::Local<v8::Context> context = + v8::Local<v8::Context>::New(isolate_, v8_context_); + + v8::TryCatch try_catch(isolate_); + + if (!context->Global() + ->Get(context, ASCIILiteralToV8String(isolate_, "FindProxyForURL")) + .ToLocal(function)) { + DCHECK(try_catch.HasCaught()); + HandleError(try_catch.Message()); + } + + // The value should only be empty if an exception was thrown. Code + // defensively just in case. + DCHECK_EQ(function->IsEmpty(), try_catch.HasCaught()); + if (function->IsEmpty() || try_catch.HasCaught()) { + js_bindings()->OnError( + -1, + base::ASCIIToUTF16("Accessing FindProxyForURL threw an exception.")); + return ERR_PAC_SCRIPT_FAILED; + } + + if (!(*function)->IsFunction()) { + js_bindings()->OnError( + -1, base::ASCIIToUTF16( + "FindProxyForURL is undefined or not a function.")); + return ERR_PAC_SCRIPT_FAILED; + } + + return OK; + } + + // Handle an exception thrown by V8. + void HandleError(v8::Local<v8::Message> message) { + v8::Local<v8::Context> context = + v8::Local<v8::Context>::New(isolate_, v8_context_); + base::string16 error_message; + int line_number = -1; + + if (!message.IsEmpty()) { + auto maybe = message->GetLineNumber(context); + if (maybe.IsJust()) + line_number = maybe.FromJust(); + V8ObjectToUTF16String(message->Get(), &error_message, isolate_); + } + + js_bindings()->OnError(line_number, error_message); + } + + // Compiles and runs |script| in the current V8 context. + // Returns OK on success, otherwise an error code. + int RunScript(v8::Local<v8::String> script, const char* script_name) { + v8::Local<v8::Context> context = + v8::Local<v8::Context>::New(isolate_, v8_context_); + v8::TryCatch try_catch(isolate_); + + // Compile the script. + v8::ScriptOrigin origin = + v8::ScriptOrigin(ASCIILiteralToV8String(isolate_, script_name)); + v8::ScriptCompiler::Source script_source(script, origin); + v8::Local<v8::Script> code; + if (!v8::ScriptCompiler::Compile( + context, &script_source, v8::ScriptCompiler::kNoCompileOptions, + v8::ScriptCompiler::NoCacheReason::kNoCacheBecausePacScript) + .ToLocal(&code)) { + DCHECK(try_catch.HasCaught()); + HandleError(try_catch.Message()); + return ERR_PAC_SCRIPT_FAILED; + } + + // Execute. + auto result = code->Run(context); + if (result.IsEmpty()) { + DCHECK(try_catch.HasCaught()); + HandleError(try_catch.Message()); + return ERR_PAC_SCRIPT_FAILED; + } + + return OK; + } + + // V8 callback for when "alert()" is invoked by the PAC script. + static void AlertCallback(const v8::FunctionCallbackInfo<v8::Value>& args) { + Context* context = + static_cast<Context*>(v8::External::Cast(*args.Data())->Value()); + + // Like firefox we assume "undefined" if no argument was specified, and + // disregard any arguments beyond the first. + base::string16 message; + if (args.Length() == 0) { + message = base::ASCIIToUTF16("undefined"); + } else { + if (!V8ObjectToUTF16String(args[0], &message, args.GetIsolate())) + return; // toString() threw an exception. + } + + context->js_bindings()->Alert(message); + } + + // V8 callback for when "myIpAddress()" is invoked by the PAC script. + static void MyIpAddressCallback( + const v8::FunctionCallbackInfo<v8::Value>& args) { + DnsResolveCallbackHelper(args, JSBindings::MY_IP_ADDRESS); + } + + // V8 callback for when "myIpAddressEx()" is invoked by the PAC script. + static void MyIpAddressExCallback( + const v8::FunctionCallbackInfo<v8::Value>& args) { + DnsResolveCallbackHelper(args, JSBindings::MY_IP_ADDRESS_EX); + } + + // V8 callback for when "dnsResolve()" is invoked by the PAC script. + static void DnsResolveCallback( + const v8::FunctionCallbackInfo<v8::Value>& args) { + DnsResolveCallbackHelper(args, JSBindings::DNS_RESOLVE); + } + + // V8 callback for when "dnsResolveEx()" is invoked by the PAC script. + static void DnsResolveExCallback( + const v8::FunctionCallbackInfo<v8::Value>& args) { + DnsResolveCallbackHelper(args, JSBindings::DNS_RESOLVE_EX); + } + + // Shared code for implementing: + // - myIpAddress(), myIpAddressEx(), dnsResolve(), dnsResolveEx(). + static void DnsResolveCallbackHelper( + const v8::FunctionCallbackInfo<v8::Value>& args, + JSBindings::ResolveDnsOperation op) { + Context* context = + static_cast<Context*>(v8::External::Cast(*args.Data())->Value()); + + std::string hostname; + + // dnsResolve() and dnsResolveEx() need at least 1 argument. + if (op == JSBindings::DNS_RESOLVE || op == JSBindings::DNS_RESOLVE_EX) { + if (!GetHostnameArgument(args, &hostname)) { + if (op == JSBindings::DNS_RESOLVE) + args.GetReturnValue().SetNull(); + return; + } + } + + std::string result; + bool success; + bool terminate = false; + + { + v8::Unlocker unlocker(args.GetIsolate()); + success = context->js_bindings()->ResolveDns( + hostname, op, &result, &terminate); + } + + if (terminate) + args.GetIsolate()->TerminateExecution(); + + if (success) { + args.GetReturnValue().Set( + ASCIIStringToV8String(args.GetIsolate(), result)); + return; + } + + // Each function handles resolution errors differently. + switch (op) { + case JSBindings::DNS_RESOLVE: + args.GetReturnValue().SetNull(); + return; + case JSBindings::DNS_RESOLVE_EX: + args.GetReturnValue().SetEmptyString(); + return; + case JSBindings::MY_IP_ADDRESS: + args.GetReturnValue().Set( + ASCIILiteralToV8String(args.GetIsolate(), "127.0.0.1")); + return; + case JSBindings::MY_IP_ADDRESS_EX: + args.GetReturnValue().SetEmptyString(); + return; + } + + NOTREACHED(); + } + + // V8 callback for when "sortIpAddressList()" is invoked by the PAC script. + static void SortIpAddressListCallback( + const v8::FunctionCallbackInfo<v8::Value>& args) { + // We need at least one string argument. + if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString()) { + args.GetReturnValue().SetNull(); + return; + } + + std::string ip_address_list = + V8StringToUTF8(v8::Local<v8::String>::Cast(args[0])); + if (!base::IsStringASCII(ip_address_list)) { + args.GetReturnValue().SetNull(); + return; + } + std::string sorted_ip_address_list; + bool success = SortIpAddressList(ip_address_list, &sorted_ip_address_list); + if (!success) { + args.GetReturnValue().Set(false); + return; + } + args.GetReturnValue().Set( + ASCIIStringToV8String(args.GetIsolate(), sorted_ip_address_list)); + } + + // V8 callback for when "isInNetEx()" is invoked by the PAC script. + static void IsInNetExCallback( + const v8::FunctionCallbackInfo<v8::Value>& args) { + // We need at least 2 string arguments. + if (args.Length() < 2 || args[0].IsEmpty() || !args[0]->IsString() || + args[1].IsEmpty() || !args[1]->IsString()) { + args.GetReturnValue().SetNull(); + return; + } + + std::string ip_address = + V8StringToUTF8(v8::Local<v8::String>::Cast(args[0])); + if (!base::IsStringASCII(ip_address)) { + args.GetReturnValue().Set(false); + return; + } + std::string ip_prefix = + V8StringToUTF8(v8::Local<v8::String>::Cast(args[1])); + if (!base::IsStringASCII(ip_prefix)) { + args.GetReturnValue().Set(false); + return; + } + args.GetReturnValue().Set(IsInNetEx(ip_address, ip_prefix)); + } + + // V8 callback for when "isPlainHostName()" is invoked by the PAC script. + static void IsPlainHostNameCallback( + const v8::FunctionCallbackInfo<v8::Value>& args) { + // Need at least 1 string arguments. + if (args.Length() < 1 || args[0].IsEmpty() || !args[0]->IsString()) { + args.GetIsolate()->ThrowException( + v8::Exception::TypeError(ASCIIStringToV8String( + args.GetIsolate(), "Requires 1 string parameter"))); + return; + } + + std::string hostname_utf8 = + V8StringToUTF8(v8::Local<v8::String>::Cast(args[0])); + args.GetReturnValue().Set(IsPlainHostName(hostname_utf8)); + } + + mutable base::Lock lock_; + ProxyResolverV8::JSBindings* js_bindings_; + v8::Isolate* isolate_; + v8::Persistent<v8::External> v8_this_; + v8::Persistent<v8::Context> v8_context_; +}; + +// ProxyResolverV8 ------------------------------------------------------------ + +ProxyResolverV8::ProxyResolverV8(std::unique_ptr<Context> context) + : context_(std::move(context)) { + DCHECK(context_); +} + +ProxyResolverV8::~ProxyResolverV8() = default; + +int ProxyResolverV8::GetProxyForURL(const GURL& query_url, + ProxyInfo* results, + ProxyResolverV8::JSBindings* bindings) { + return context_->ResolveProxy(query_url, results, bindings); +} + +// static +int ProxyResolverV8::Create( + const scoped_refptr<ProxyResolverScriptData>& script_data, + ProxyResolverV8::JSBindings* js_bindings, + std::unique_ptr<ProxyResolverV8>* resolver) { + DCHECK(script_data.get()); + DCHECK(js_bindings); + + if (script_data->utf16().empty()) + return ERR_PAC_SCRIPT_FAILED; + + // Try parsing the PAC script. + std::unique_ptr<Context> context( + new Context(g_isolate_factory.Get().GetSharedIsolate())); + int rv = context->InitV8(script_data, js_bindings); + if (rv == OK) + resolver->reset(new ProxyResolverV8(std::move(context))); + return rv; +} + +// static +size_t ProxyResolverV8::GetTotalHeapSize() { + v8::Isolate* isolate = + g_isolate_factory.Get().GetSharedIsolateWithoutCreating(); + if (!isolate) + return 0; + + v8::Locker locked(isolate); + v8::Isolate::Scope isolate_scope(isolate); + v8::HeapStatistics heap_statistics; + isolate->GetHeapStatistics(&heap_statistics); + return heap_statistics.total_heap_size(); +} + +// static +size_t ProxyResolverV8::GetUsedHeapSize() { + v8::Isolate* isolate = + g_isolate_factory.Get().GetSharedIsolateWithoutCreating(); + if (!isolate) + return 0; + + v8::Locker locked(isolate); + v8::Isolate::Scope isolate_scope(isolate); + v8::HeapStatistics heap_statistics; + isolate->GetHeapStatistics(&heap_statistics); + return heap_statistics.used_heap_size(); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_resolver_v8.h b/chromium/net/proxy_resolution/proxy_resolver_v8.h new file mode 100644 index 00000000000..2b391c4df69 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_v8.h @@ -0,0 +1,86 @@ +// 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_RESOLUTION_PROXY_RESOLVER_V8_H_ +#define NET_PROXY_RESOLUTION_PROXY_RESOLVER_V8_H_ + +#include <stddef.h> + +#include <memory> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "net/base/net_export.h" + +class GURL; + +namespace net { +class ProxyInfo; +class ProxyResolverScriptData; + +// A synchronous ProxyResolver-like that uses V8 to evaluate PAC scripts. +class NET_EXPORT_PRIVATE ProxyResolverV8 { + public: + // Interface for the javascript bindings. + class NET_EXPORT_PRIVATE JSBindings { + public: + enum ResolveDnsOperation { + DNS_RESOLVE, + DNS_RESOLVE_EX, + MY_IP_ADDRESS, + MY_IP_ADDRESS_EX, + }; + + JSBindings() {} + + // Handler for "dnsResolve()", "dnsResolveEx()", "myIpAddress()", + // "myIpAddressEx()". Returns true on success and fills |*output| with the + // result. If |*terminate| is set to true, then the script execution will + // be aborted. Note that termination may not happen right away. + virtual bool ResolveDns(const std::string& host, + ResolveDnsOperation op, + std::string* output, + bool* terminate) = 0; + + // Handler for "alert(message)" + virtual void Alert(const base::string16& message) = 0; + + // Handler for when an error is encountered. |line_number| may be -1 + // if a line number is not applicable to this error. + virtual void OnError(int line_number, const base::string16& error) = 0; + + protected: + virtual ~JSBindings() {} + }; + + // Constructs a ProxyResolverV8. + static int Create(const scoped_refptr<ProxyResolverScriptData>& script_data, + JSBindings* bindings, + std::unique_ptr<ProxyResolverV8>* resolver); + + ~ProxyResolverV8(); + + int GetProxyForURL(const GURL& url, ProxyInfo* results, JSBindings* bindings); + + // Get total/used heap memory usage of all v8 instances used by the proxy + // resolver. + static size_t GetTotalHeapSize(); + static size_t GetUsedHeapSize(); + + private: + // Context holds the Javascript state for the PAC script. + class Context; + + explicit ProxyResolverV8(std::unique_ptr<Context> context); + + std::unique_ptr<Context> context_; + + DISALLOW_COPY_AND_ASSIGN(ProxyResolverV8); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_RESOLVER_V8_H_ diff --git a/chromium/net/proxy_resolution/proxy_resolver_v8_tracing.cc b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing.cc new file mode 100644 index 00000000000..fbd268e9cb5 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing.cc @@ -0,0 +1,1119 @@ +// Copyright (c) 2013 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_resolution/proxy_resolver_v8_tracing.h" + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/cancellation_flag.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "base/threading/thread_checker.h" +#include "base/threading/thread_restrictions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/trace_event/trace_event.h" +#include "net/base/address_list.h" +#include "net/base/net_errors.h" +#include "net/base/network_interfaces.h" +#include "net/base/trace_constants.h" +#include "net/dns/host_resolver.h" +#include "net/log/net_log_with_source.h" +#include "net/proxy_resolution/proxy_info.h" +#include "net/proxy_resolution/proxy_resolver_error_observer.h" +#include "net/proxy_resolution/proxy_resolver_v8.h" + +// The intent of this class is explained in the design document: +// https://docs.google.com/a/chromium.org/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPoMJdaBn9rKreAmGOdE/edit +// +// In a nutshell, PAC scripts are Javascript programs and may depend on +// network I/O, by calling functions like dnsResolve(). +// +// This is problematic since functions such as dnsResolve() will block the +// Javascript execution until the DNS result is availble, thereby stalling the +// PAC thread, which hurts the ability to process parallel proxy resolves. +// An obvious solution is to simply start more PAC threads, however this scales +// poorly, which hurts the ability to process parallel proxy resolves. +// +// The solution in ProxyResolverV8Tracing is to model PAC scripts as being +// deterministic, and depending only on the inputted URL. When the script +// issues a dnsResolve() for a yet unresolved hostname, the Javascript +// execution is "aborted", and then re-started once the DNS result is +// known. +namespace net { + +namespace { + +// Upper bound on how many *unique* DNS resolves a PAC script is allowed +// to make. This is a failsafe both for scripts that do a ridiculous +// number of DNS resolves, as well as scripts which are misbehaving +// under the tracing optimization. It is not expected to hit this normally. +const size_t kMaxUniqueResolveDnsPerExec = 20; + +// Approximate number of bytes to use for buffering alerts() and errors. +// This is a failsafe in case repeated executions of the script causes +// too much memory bloat. It is not expected for well behaved scripts to +// hit this. (In fact normal scripts should not even have alerts() or errors). +const size_t kMaxAlertsAndErrorsBytes = 2048; + +// The Job class is responsible for executing GetProxyForURL() and +// creating ProxyResolverV8 instances, since both of these operations share +// similar code. +// +// The DNS for these operations can operate in either blocking or +// non-blocking mode. Blocking mode is used as a fallback when the PAC script +// seems to be misbehaving under the tracing optimization. +// +// Note that this class runs on both the origin thread and a worker +// thread. Most methods are expected to be used exclusively on one thread +// or the other. +// +// The lifetime of Jobs does not exceed that of the ProxyResolverV8TracingImpl +// that spawned it. Destruction might happen on either the origin thread or the +// worker thread. +class Job : public base::RefCountedThreadSafe<Job>, + public ProxyResolverV8::JSBindings { + public: + struct Params { + Params( + const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner, + int* num_outstanding_callbacks) + : v8_resolver(nullptr), + worker_task_runner(worker_task_runner), + num_outstanding_callbacks(num_outstanding_callbacks) {} + + ProxyResolverV8* v8_resolver; + scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner; + int* num_outstanding_callbacks; + }; + // |params| is non-owned. It contains the parameters for this Job, and must + // outlive it. + Job(const Params* params, + std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings); + + // Called from origin thread. + void StartCreateV8Resolver( + const scoped_refptr<ProxyResolverScriptData>& script_data, + std::unique_ptr<ProxyResolverV8>* resolver, + const CompletionCallback& callback); + + // Called from origin thread. + void StartGetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback); + + // Called from origin thread. + void Cancel(); + + // Called from origin thread. + LoadState GetLoadState() const; + + private: + typedef std::map<std::string, std::string> DnsCache; + friend class base::RefCountedThreadSafe<Job>; + + enum Operation { + CREATE_V8_RESOLVER, + GET_PROXY_FOR_URL, + }; + + struct AlertOrError { + bool is_alert; + int line_number; + base::string16 message; + }; + + ~Job() override; + + void CheckIsOnWorkerThread() const; + void CheckIsOnOriginThread() const; + + void SetCallback(const CompletionCallback& callback); + void ReleaseCallback(); + + ProxyResolverV8* v8_resolver(); + const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner(); + HostResolver* host_resolver(); + + // Invokes the user's callback. + void NotifyCaller(int result); + void NotifyCallerOnOriginLoop(int result); + + void Start(Operation op, bool blocking_dns, + const CompletionCallback& callback); + + void ExecuteBlocking(); + void ExecuteNonBlocking(); + int ExecuteProxyResolver(); + + // Implementation of ProxyResolverv8::JSBindings + bool ResolveDns(const std::string& host, + ResolveDnsOperation op, + std::string* output, + bool* terminate) override; + void Alert(const base::string16& message) override; + void OnError(int line_number, const base::string16& error) override; + + bool ResolveDnsBlocking(const std::string& host, + ResolveDnsOperation op, + std::string* output); + + bool ResolveDnsNonBlocking(const std::string& host, + ResolveDnsOperation op, + std::string* output, + bool* terminate); + + bool PostDnsOperationAndWait(const std::string& host, + ResolveDnsOperation op, + bool* completed_synchronously) + WARN_UNUSED_RESULT; + + void DoDnsOperation(); + void OnDnsOperationComplete(int result); + + void ScheduleRestartWithBlockingDns(); + + bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op, + std::string* output, bool* return_value); + + void SaveDnsToLocalCache(const std::string& host, + ResolveDnsOperation op, + int net_error, + const AddressList& addresses); + + // Builds a RequestInfo to service the specified PAC DNS operation. + static HostResolver::RequestInfo MakeDnsRequestInfo(const std::string& host, + ResolveDnsOperation op); + + // Makes a key for looking up |host, op| in |dns_cache_|. Strings are used for + // convenience, to avoid defining custom comparators. + static std::string MakeDnsCacheKey(const std::string& host, + ResolveDnsOperation op); + + void HandleAlertOrError(bool is_alert, int line_number, + const base::string16& message); + void DispatchBufferedAlertsAndErrors(); + void DispatchAlertOrErrorOnOriginThread(bool is_alert, + int line_number, + const base::string16& message); + + // The thread which called into ProxyResolverV8TracingImpl, and on which the + // completion callback is expected to run. + scoped_refptr<base::SingleThreadTaskRunner> origin_runner_; + + // The Parameters for this Job. + // Initialized on origin thread and then accessed from both threads. + const Params* const params_; + + std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings_; + + // The callback to run (on the origin thread) when the Job finishes. + // Should only be accessed from origin thread. + CompletionCallback callback_; + + // Flag to indicate whether the request has been cancelled. + base::CancellationFlag cancelled_; + + // The operation that this Job is running. + // Initialized on origin thread and then accessed from both threads. + Operation operation_; + + // The DNS mode for this Job. + // Initialized on origin thread, mutated on worker thread, and accessed + // by both the origin thread and worker thread. + bool blocking_dns_; + + // Used to block the worker thread on a DNS operation taking place on the + // origin thread. + base::WaitableEvent event_; + + // Map of DNS operations completed so far. Written into on the origin thread + // and read on the worker thread. + DnsCache dns_cache_; + + // The job holds a reference to itself to ensure that it remains alive until + // either completion or cancellation. + scoped_refptr<Job> owned_self_reference_; + + // ------------------------------------------------------- + // State specific to CREATE_V8_RESOLVER. + // ------------------------------------------------------- + + scoped_refptr<ProxyResolverScriptData> script_data_; + std::unique_ptr<ProxyResolverV8>* resolver_out_; + + // ------------------------------------------------------- + // State specific to GET_PROXY_FOR_URL. + // ------------------------------------------------------- + + ProxyInfo* user_results_; // Owned by caller, lives on origin thread. + GURL url_; + ProxyInfo results_; + + // --------------------------------------------------------------------------- + // State for ExecuteNonBlocking() + // --------------------------------------------------------------------------- + // These variables are used exclusively on the worker thread and are only + // meaningful when executing inside of ExecuteNonBlocking(). + + // Whether this execution was abandoned due to a missing DNS dependency. + bool abandoned_; + + // Number of calls made to ResolveDns() by this execution. + int num_dns_; + + // Sequence of calls made to Alert() or OnError() by this execution. + std::vector<AlertOrError> alerts_and_errors_; + size_t alerts_and_errors_byte_cost_; // Approximate byte cost of the above. + + // Number of calls made to ResolveDns() by the PREVIOUS execution. + int last_num_dns_; + + // Whether the current execution needs to be restarted in blocking mode. + bool should_restart_with_blocking_dns_; + + // --------------------------------------------------------------------------- + // State for pending DNS request. + // --------------------------------------------------------------------------- + + // Handle to the outstanding request in the HostResolver, or NULL. + // This is mutated and used on the origin thread, however it may be read by + // the worker thread for some DCHECKS(). + std::unique_ptr<HostResolver::Request> pending_dns_; + + // Indicates if the outstanding DNS request completed synchronously. Written + // on the origin thread, and read by the worker thread. + bool pending_dns_completed_synchronously_; + + // These are the inputs to DoDnsOperation(). Written on the worker thread, + // read by the origin thread. + std::string pending_dns_host_; + ResolveDnsOperation pending_dns_op_; + + // This contains the resolved address list that DoDnsOperation() fills in. + // Used exclusively on the origin thread. + AddressList pending_dns_addresses_; +}; + +class ProxyResolverV8TracingImpl : public ProxyResolverV8Tracing { + public: + ProxyResolverV8TracingImpl(std::unique_ptr<base::Thread> thread, + std::unique_ptr<ProxyResolverV8> resolver, + std::unique_ptr<Job::Params> job_params); + + ~ProxyResolverV8TracingImpl() override; + + // ProxyResolverV8Tracing overrides. + void GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<ProxyResolver::Request>* request, + std::unique_ptr<Bindings> bindings) override; + + class RequestImpl : public ProxyResolver::Request { + public: + explicit RequestImpl(scoped_refptr<Job> job); + ~RequestImpl() override; + LoadState GetLoadState() override; + + private: + scoped_refptr<Job> job_; + }; + + private: + // The worker thread on which the ProxyResolverV8 will be run. + std::unique_ptr<base::Thread> thread_; + std::unique_ptr<ProxyResolverV8> v8_resolver_; + + std::unique_ptr<Job::Params> job_params_; + + // The number of outstanding (non-cancelled) jobs. + int num_outstanding_callbacks_; + + THREAD_CHECKER(thread_checker_); + + DISALLOW_COPY_AND_ASSIGN(ProxyResolverV8TracingImpl); +}; + +Job::Job(const Job::Params* params, + std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings) + : origin_runner_(base::ThreadTaskRunnerHandle::Get()), + params_(params), + bindings_(std::move(bindings)), + event_(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED), + last_num_dns_(0) { + CheckIsOnOriginThread(); +} + +void Job::StartCreateV8Resolver( + const scoped_refptr<ProxyResolverScriptData>& script_data, + std::unique_ptr<ProxyResolverV8>* resolver, + const CompletionCallback& callback) { + CheckIsOnOriginThread(); + + resolver_out_ = resolver; + script_data_ = script_data; + + // Script initialization uses blocking DNS since there isn't any + // advantage to using non-blocking mode here. That is because the + // parent ProxyResolutionService can't submit any ProxyResolve requests until + // initialization has completed successfully! + Start(CREATE_V8_RESOLVER, true /*blocking*/, callback); +} + +void Job::StartGetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback) { + CheckIsOnOriginThread(); + + url_ = url; + user_results_ = results; + + Start(GET_PROXY_FOR_URL, false /*non-blocking*/, callback); +} + +void Job::Cancel() { + CheckIsOnOriginThread(); + + // There are several possibilities to consider for cancellation: + // (a) The job has been posted to the worker thread, however script execution + // has not yet started. + // (b) The script is executing on the worker thread. + // (c) The script is executing on the worker thread, however is blocked inside + // of dnsResolve() waiting for a response from the origin thread. + // (d) Nothing is running on the worker thread, however the host resolver has + // a pending DNS request which upon completion will restart the script + // execution. + // (e) The worker thread has a pending task to restart execution, which was + // posted after the DNS dependency was resolved and saved to local cache. + // (f) The script execution completed entirely, and posted a task to the + // origin thread to notify the caller. + // (g) The job is already completed. + // + // |cancelled_| is read on both the origin thread and worker thread. The + // code that runs on the worker thread is littered with checks on + // |cancelled_| to break out early. + + // If the job already completed, there is nothing to be cancelled. + if (callback_.is_null()) + return; + + cancelled_.Set(); + + ReleaseCallback(); + + pending_dns_.reset(); + + // The worker thread might be blocked waiting for DNS. + event_.Signal(); + + bindings_.reset(); + owned_self_reference_ = NULL; +} + +LoadState Job::GetLoadState() const { + CheckIsOnOriginThread(); + + if (pending_dns_) + return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT; + + return LOAD_STATE_RESOLVING_PROXY_FOR_URL; +} + +Job::~Job() { + DCHECK(!pending_dns_); + DCHECK(callback_.is_null()); + DCHECK(!bindings_); +} + +void Job::CheckIsOnWorkerThread() const { + DCHECK(params_->worker_task_runner->BelongsToCurrentThread()); +} + +void Job::CheckIsOnOriginThread() const { + DCHECK(origin_runner_->BelongsToCurrentThread()); +} + +void Job::SetCallback(const CompletionCallback& callback) { + CheckIsOnOriginThread(); + DCHECK(callback_.is_null()); + (*params_->num_outstanding_callbacks)++; + callback_ = callback; +} + +void Job::ReleaseCallback() { + CheckIsOnOriginThread(); + DCHECK(!callback_.is_null()); + CHECK_GT(*params_->num_outstanding_callbacks, 0); + (*params_->num_outstanding_callbacks)--; + callback_.Reset(); + + // For good measure, clear this other user-owned pointer. + user_results_ = NULL; +} + +ProxyResolverV8* Job::v8_resolver() { + return params_->v8_resolver; +} + +const scoped_refptr<base::SingleThreadTaskRunner>& Job::worker_task_runner() { + return params_->worker_task_runner; +} + +HostResolver* Job::host_resolver() { + return bindings_->GetHostResolver(); +} + +void Job::NotifyCaller(int result) { + CheckIsOnWorkerThread(); + + origin_runner_->PostTask( + FROM_HERE, base::Bind(&Job::NotifyCallerOnOriginLoop, this, result)); +} + +void Job::NotifyCallerOnOriginLoop(int result) { + CheckIsOnOriginThread(); + + if (cancelled_.IsSet()) + return; + + DispatchBufferedAlertsAndErrors(); + + // This isn't the ordinary execution flow, however it is exercised by + // unit-tests. + if (cancelled_.IsSet()) + return; + + DCHECK(!callback_.is_null()); + DCHECK(!pending_dns_); + + if (operation_ == GET_PROXY_FOR_URL) { + *user_results_ = results_; + } + + CompletionCallback callback = callback_; + ReleaseCallback(); + callback.Run(result); + + bindings_.reset(); + owned_self_reference_ = NULL; +} + +void Job::Start(Operation op, + bool blocking_dns, + const CompletionCallback& callback) { + CheckIsOnOriginThread(); + + operation_ = op; + blocking_dns_ = blocking_dns; + SetCallback(callback); + + owned_self_reference_ = this; + + worker_task_runner()->PostTask( + FROM_HERE, blocking_dns_ ? base::Bind(&Job::ExecuteBlocking, this) + : base::Bind(&Job::ExecuteNonBlocking, this)); +} + +void Job::ExecuteBlocking() { + CheckIsOnWorkerThread(); + DCHECK(blocking_dns_); + + if (cancelled_.IsSet()) + return; + + NotifyCaller(ExecuteProxyResolver()); +} + +void Job::ExecuteNonBlocking() { + CheckIsOnWorkerThread(); + DCHECK(!blocking_dns_); + + if (cancelled_.IsSet()) + return; + + // Reset state for the current execution. + abandoned_ = false; + num_dns_ = 0; + alerts_and_errors_.clear(); + alerts_and_errors_byte_cost_ = 0; + should_restart_with_blocking_dns_ = false; + + int result = ExecuteProxyResolver(); + + if (should_restart_with_blocking_dns_) { + DCHECK(!blocking_dns_); + DCHECK(abandoned_); + blocking_dns_ = true; + ExecuteBlocking(); + return; + } + + if (abandoned_) + return; + + NotifyCaller(result); +} + +int Job::ExecuteProxyResolver() { + TRACE_EVENT0(kNetTracingCategory, "Job::ExecuteProxyResolver"); + int result = ERR_UNEXPECTED; // Initialized to silence warnings. + + switch (operation_) { + case CREATE_V8_RESOLVER: { + std::unique_ptr<ProxyResolverV8> resolver; + result = ProxyResolverV8::Create(script_data_, this, &resolver); + if (result == OK) + *resolver_out_ = std::move(resolver); + break; + } + case GET_PROXY_FOR_URL: { + result = v8_resolver()->GetProxyForURL( + url_, + // Important: Do not write directly into |user_results_|, since if the + // request were to be cancelled from the origin thread, must guarantee + // that |user_results_| is not accessed anymore. + &results_, this); + break; + } + } + + return result; +} + +bool Job::ResolveDns(const std::string& host, + ResolveDnsOperation op, + std::string* output, + bool* terminate) { + if (cancelled_.IsSet()) { + *terminate = true; + return false; + } + + if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) { + // a DNS resolve with an empty hostname is considered an error. + return false; + } + + return blocking_dns_ ? + ResolveDnsBlocking(host, op, output) : + ResolveDnsNonBlocking(host, op, output, terminate); +} + +void Job::Alert(const base::string16& message) { + HandleAlertOrError(true, -1, message); +} + +void Job::OnError(int line_number, const base::string16& error) { + HandleAlertOrError(false, line_number, error); +} + +bool Job::ResolveDnsBlocking(const std::string& host, + ResolveDnsOperation op, + std::string* output) { + CheckIsOnWorkerThread(); + + // Check if the DNS result for this host has already been cached. + bool rv; + if (GetDnsFromLocalCache(host, op, output, &rv)) { + // Yay, cache hit! + return rv; + } + + if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) { + // Safety net for scripts with unexpectedly many DNS calls. + // We will continue running to completion, but will fail every + // subsequent DNS request. + return false; + } + + if (!PostDnsOperationAndWait(host, op, NULL)) + return false; // Was cancelled. + + CHECK(GetDnsFromLocalCache(host, op, output, &rv)); + return rv; +} + +bool Job::ResolveDnsNonBlocking(const std::string& host, + ResolveDnsOperation op, + std::string* output, + bool* terminate) { + CheckIsOnWorkerThread(); + + if (abandoned_) { + // If this execution was already abandoned can fail right away. Only 1 DNS + // dependency will be traced at a time (for more predictable outcomes). + return false; + } + + num_dns_ += 1; + + // Check if the DNS result for this host has already been cached. + bool rv; + if (GetDnsFromLocalCache(host, op, output, &rv)) { + // Yay, cache hit! + return rv; + } + + if (num_dns_ <= last_num_dns_) { + // The sequence of DNS operations is different from last time! + ScheduleRestartWithBlockingDns(); + *terminate = true; + return false; + } + + if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) { + // Safety net for scripts with unexpectedly many DNS calls. + return false; + } + + DCHECK(!should_restart_with_blocking_dns_); + + bool completed_synchronously; + if (!PostDnsOperationAndWait(host, op, &completed_synchronously)) + return false; // Was cancelled. + + if (completed_synchronously) { + CHECK(GetDnsFromLocalCache(host, op, output, &rv)); + return rv; + } + + // Otherwise if the result was not in the cache, then a DNS request has + // been started. Abandon this invocation of FindProxyForURL(), it will be + // restarted once the DNS request completes. + abandoned_ = true; + *terminate = true; + last_num_dns_ = num_dns_; + return false; +} + +bool Job::PostDnsOperationAndWait(const std::string& host, + ResolveDnsOperation op, + bool* completed_synchronously) { + // Post the DNS request to the origin thread. + DCHECK(!pending_dns_); + pending_dns_host_ = host; + pending_dns_op_ = op; + origin_runner_->PostTask(FROM_HERE, base::Bind(&Job::DoDnsOperation, this)); + + event_.Wait(); + event_.Reset(); + + if (cancelled_.IsSet()) + return false; + + if (completed_synchronously) + *completed_synchronously = pending_dns_completed_synchronously_; + + return true; +} + +void Job::DoDnsOperation() { + CheckIsOnOriginThread(); + DCHECK(!pending_dns_); + + if (cancelled_.IsSet()) + return; + + std::unique_ptr<HostResolver::Request> dns_request; + int result = host_resolver()->Resolve( + MakeDnsRequestInfo(pending_dns_host_, pending_dns_op_), DEFAULT_PRIORITY, + &pending_dns_addresses_, base::Bind(&Job::OnDnsOperationComplete, this), + &dns_request, bindings_->GetNetLogWithSource()); + + pending_dns_completed_synchronously_ = result != ERR_IO_PENDING; + + // Check if the request was cancelled as a side-effect of calling into the + // HostResolver. This isn't the ordinary execution flow, however it is + // exercised by unit-tests. + if (cancelled_.IsSet()) + return; + + if (pending_dns_completed_synchronously_) { + OnDnsOperationComplete(result); + } else { + DCHECK(dns_request); + pending_dns_ = std::move(dns_request); + // OnDnsOperationComplete() will be called by host resolver on completion. + } + + if (!blocking_dns_) { + // The worker thread always blocks waiting to see if the result can be + // serviced from cache before restarting. + event_.Signal(); + } +} + +void Job::OnDnsOperationComplete(int result) { + CheckIsOnOriginThread(); + + DCHECK(!cancelled_.IsSet()); + DCHECK(pending_dns_completed_synchronously_ == (pending_dns_ == NULL)); + + SaveDnsToLocalCache(pending_dns_host_, pending_dns_op_, result, + pending_dns_addresses_); + pending_dns_.reset(); + + if (blocking_dns_) { + event_.Signal(); + return; + } + + if (!blocking_dns_ && !pending_dns_completed_synchronously_) { + // Restart. This time it should make more progress due to having + // cached items. + worker_task_runner()->PostTask(FROM_HERE, + base::Bind(&Job::ExecuteNonBlocking, this)); + } +} + +void Job::ScheduleRestartWithBlockingDns() { + CheckIsOnWorkerThread(); + + DCHECK(!should_restart_with_blocking_dns_); + DCHECK(!abandoned_); + DCHECK(!blocking_dns_); + + abandoned_ = true; + + // The restart will happen after ExecuteNonBlocking() finishes. + should_restart_with_blocking_dns_ = true; +} + +bool Job::GetDnsFromLocalCache(const std::string& host, + ResolveDnsOperation op, + std::string* output, + bool* return_value) { + CheckIsOnWorkerThread(); + + DnsCache::const_iterator it = dns_cache_.find(MakeDnsCacheKey(host, op)); + if (it == dns_cache_.end()) + return false; + + *output = it->second; + *return_value = !it->second.empty(); + return true; +} + +void Job::SaveDnsToLocalCache(const std::string& host, + ResolveDnsOperation op, + int net_error, + const AddressList& addresses) { + CheckIsOnOriginThread(); + + // Serialize the result into a string to save to the cache. + std::string cache_value; + if (net_error != OK) { + cache_value = std::string(); + } else if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) { + // dnsResolve() and myIpAddress() are expected to return a single IP + // address. + cache_value = addresses.front().ToStringWithoutPort(); + } else { + // The *Ex versions are expected to return a semi-colon separated list. + for (AddressList::const_iterator iter = addresses.begin(); + iter != addresses.end(); ++iter) { + if (!cache_value.empty()) + cache_value += ";"; + cache_value += iter->ToStringWithoutPort(); + } + } + + dns_cache_[MakeDnsCacheKey(host, op)] = cache_value; +} + +// static +HostResolver::RequestInfo Job::MakeDnsRequestInfo(const std::string& host, + ResolveDnsOperation op) { + HostPortPair host_port = HostPortPair(host, 80); + if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) { + host_port.set_host(GetHostName()); + } + + HostResolver::RequestInfo info(host_port); + // Flag myIpAddress requests. + if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) { + // TODO: Provide a RequestInfo construction mechanism that does not + // require a hostname and sets is_my_ip_address to true instead of this. + info.set_is_my_ip_address(true); + } + // The non-ex flavors are limited to IPv4 results. + if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) { + info.set_address_family(ADDRESS_FAMILY_IPV4); + } + + return info; +} + +std::string Job::MakeDnsCacheKey(const std::string& host, + ResolveDnsOperation op) { + return base::StringPrintf("%d:%s", op, host.c_str()); +} + +void Job::HandleAlertOrError(bool is_alert, + int line_number, + const base::string16& message) { + CheckIsOnWorkerThread(); + + if (cancelled_.IsSet()) + return; + + if (blocking_dns_) { + // In blocking DNS mode the events can be dispatched immediately. + origin_runner_->PostTask( + FROM_HERE, base::Bind(&Job::DispatchAlertOrErrorOnOriginThread, this, + is_alert, line_number, message)); + return; + } + + // Otherwise in nonblocking mode, buffer all the messages until + // the end. + + if (abandoned_) + return; + + alerts_and_errors_byte_cost_ += sizeof(AlertOrError) + message.size() * 2; + + // If there have been lots of messages, enqueing could be expensive on + // memory. Consider a script which does megabytes worth of alerts(). + // Avoid this by falling back to blocking mode. + if (alerts_and_errors_byte_cost_ > kMaxAlertsAndErrorsBytes) { + alerts_and_errors_.clear(); + ScheduleRestartWithBlockingDns(); + return; + } + + AlertOrError entry = {is_alert, line_number, message}; + alerts_and_errors_.push_back(entry); +} + +void Job::DispatchBufferedAlertsAndErrors() { + CheckIsOnOriginThread(); + for (size_t i = 0; i < alerts_and_errors_.size(); ++i) { + const AlertOrError& x = alerts_and_errors_[i]; + DispatchAlertOrErrorOnOriginThread(x.is_alert, x.line_number, x.message); + } +} + +void Job::DispatchAlertOrErrorOnOriginThread(bool is_alert, + int line_number, + const base::string16& message) { + CheckIsOnOriginThread(); + + if (cancelled_.IsSet()) + return; + + if (is_alert) { + // ------------------- + // alert + // ------------------- + VLOG(1) << "PAC-alert: " << message; + + bindings_->Alert(message); + } else { + // ------------------- + // error + // ------------------- + if (line_number == -1) + VLOG(1) << "PAC-error: " << message; + else + VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message; + + bindings_->OnError(line_number, message); + } +} + +ProxyResolverV8TracingImpl::ProxyResolverV8TracingImpl( + std::unique_ptr<base::Thread> thread, + std::unique_ptr<ProxyResolverV8> resolver, + std::unique_ptr<Job::Params> job_params) + : thread_(std::move(thread)), + v8_resolver_(std::move(resolver)), + job_params_(std::move(job_params)), + num_outstanding_callbacks_(0) { + job_params_->num_outstanding_callbacks = &num_outstanding_callbacks_; +} + +ProxyResolverV8TracingImpl::~ProxyResolverV8TracingImpl() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + // Note, all requests should have been cancelled. + CHECK_EQ(0, num_outstanding_callbacks_); + + // Join the worker thread. See http://crbug.com/69710. + base::ThreadRestrictions::ScopedAllowIO allow_io; + thread_.reset(); +} + +ProxyResolverV8TracingImpl::RequestImpl::RequestImpl(scoped_refptr<Job> job) + : job_(std::move(job)) {} + +ProxyResolverV8TracingImpl::RequestImpl::~RequestImpl() { + job_->Cancel(); +} + +LoadState ProxyResolverV8TracingImpl::RequestImpl::GetLoadState() { + return job_->GetLoadState(); +} + +void ProxyResolverV8TracingImpl::GetProxyForURL( + const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<ProxyResolver::Request>* request, + std::unique_ptr<Bindings> bindings) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!callback.is_null()); + + scoped_refptr<Job> job = new Job(job_params_.get(), std::move(bindings)); + + request->reset(new RequestImpl(job)); + + job->StartGetProxyForURL(url, results, callback); +} + + +class ProxyResolverV8TracingFactoryImpl : public ProxyResolverV8TracingFactory { + public: + ProxyResolverV8TracingFactoryImpl(); + ~ProxyResolverV8TracingFactoryImpl() override; + + void CreateProxyResolverV8Tracing( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings, + std::unique_ptr<ProxyResolverV8Tracing>* resolver, + const CompletionCallback& callback, + std::unique_ptr<ProxyResolverFactory::Request>* request) override; + + private: + class CreateJob; + + void RemoveJob(CreateJob* job); + + std::set<CreateJob*> jobs_; + + DISALLOW_COPY_AND_ASSIGN(ProxyResolverV8TracingFactoryImpl); +}; + +class ProxyResolverV8TracingFactoryImpl::CreateJob + : public ProxyResolverFactory::Request { + public: + CreateJob(ProxyResolverV8TracingFactoryImpl* factory, + std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings, + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolverV8Tracing>* resolver_out, + const CompletionCallback& callback) + : factory_(factory), + thread_(new base::Thread("Proxy Resolver")), + resolver_out_(resolver_out), + callback_(callback), + num_outstanding_callbacks_(0) { + // Start up the thread. + base::Thread::Options options; + options.timer_slack = base::TIMER_SLACK_MAXIMUM; + CHECK(thread_->StartWithOptions(options)); + job_params_.reset( + new Job::Params(thread_->task_runner(), &num_outstanding_callbacks_)); + create_resolver_job_ = new Job(job_params_.get(), std::move(bindings)); + create_resolver_job_->StartCreateV8Resolver( + pac_script, &v8_resolver_, + base::Bind( + &ProxyResolverV8TracingFactoryImpl::CreateJob::OnV8ResolverCreated, + base::Unretained(this))); + } + + ~CreateJob() override { + if (factory_) { + factory_->RemoveJob(this); + DCHECK(create_resolver_job_); + create_resolver_job_->Cancel(); + StopWorkerThread(); + } + DCHECK_EQ(0, num_outstanding_callbacks_); + } + + void FactoryDestroyed() { + factory_ = nullptr; + create_resolver_job_->Cancel(); + create_resolver_job_ = nullptr; + StopWorkerThread(); + } + + private: + void OnV8ResolverCreated(int error) { + DCHECK(factory_); + if (error == OK) { + job_params_->v8_resolver = v8_resolver_.get(); + resolver_out_->reset(new ProxyResolverV8TracingImpl( + std::move(thread_), std::move(v8_resolver_), std::move(job_params_))); + } else { + StopWorkerThread(); + } + + factory_->RemoveJob(this); + factory_ = nullptr; + create_resolver_job_ = nullptr; + callback_.Run(error); + } + + void StopWorkerThread() { + // Join the worker thread. See http://crbug.com/69710. + base::ThreadRestrictions::ScopedAllowIO allow_io; + thread_.reset(); + } + + ProxyResolverV8TracingFactoryImpl* factory_; + std::unique_ptr<base::Thread> thread_; + std::unique_ptr<Job::Params> job_params_; + scoped_refptr<Job> create_resolver_job_; + std::unique_ptr<ProxyResolverV8> v8_resolver_; + std::unique_ptr<ProxyResolverV8Tracing>* resolver_out_; + const CompletionCallback callback_; + int num_outstanding_callbacks_; + + DISALLOW_COPY_AND_ASSIGN(CreateJob); +}; + +ProxyResolverV8TracingFactoryImpl::ProxyResolverV8TracingFactoryImpl() = + default; + +ProxyResolverV8TracingFactoryImpl::~ProxyResolverV8TracingFactoryImpl() { + for (auto* job : jobs_) { + job->FactoryDestroyed(); + } +} + +void ProxyResolverV8TracingFactoryImpl::CreateProxyResolverV8Tracing( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings, + std::unique_ptr<ProxyResolverV8Tracing>* resolver, + const CompletionCallback& callback, + std::unique_ptr<ProxyResolverFactory::Request>* request) { + std::unique_ptr<CreateJob> job( + new CreateJob(this, std::move(bindings), pac_script, resolver, callback)); + jobs_.insert(job.get()); + *request = std::move(job); +} + +void ProxyResolverV8TracingFactoryImpl::RemoveJob( + ProxyResolverV8TracingFactoryImpl::CreateJob* job) { + size_t erased = jobs_.erase(job); + DCHECK_EQ(1u, erased); +} + +} // namespace + +// static +std::unique_ptr<ProxyResolverV8TracingFactory> +ProxyResolverV8TracingFactory::Create() { + return std::make_unique<ProxyResolverV8TracingFactoryImpl>(); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_resolver_v8_tracing.h b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing.h new file mode 100644 index 00000000000..e95812ec88c --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing.h @@ -0,0 +1,89 @@ +// Copyright (c) 2013 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_RESOLUTION_PROXY_RESOLVER_V8_TRACING_H_ +#define NET_PROXY_RESOLUTION_PROXY_RESOLVER_V8_TRACING_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/proxy_resolver.h" +#include "net/proxy_resolution/proxy_resolver_factory.h" + +namespace net { + +class HostResolver; +class NetLogWithSource; + +// ProxyResolverV8Tracing is a non-blocking proxy resolver. +class NET_EXPORT ProxyResolverV8Tracing { + public: + // Bindings is an interface used by ProxyResolverV8Tracing to delegate + // per-request functionality. Each instance will be destroyed on the origin + // thread of the ProxyResolverV8Tracing when the request completes or is + // cancelled. All methods will be invoked from the origin thread. + class Bindings { + public: + Bindings() {} + virtual ~Bindings() {} + + // Invoked in response to an alert() call by the PAC script. + virtual void Alert(const base::string16& message) = 0; + + // Invoked in response to an error in the PAC script. + virtual void OnError(int line_number, const base::string16& message) = 0; + + // Returns a HostResolver to use for DNS resolution. + virtual HostResolver* GetHostResolver() = 0; + + // Returns a NetLogWithSource to be passed to the HostResolver returned by + // GetHostResolver(). + virtual NetLogWithSource GetNetLogWithSource() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(Bindings); + }; + + virtual ~ProxyResolverV8Tracing() {} + + // Gets a list of proxy servers to use for |url|. This request always + // runs asynchronously and notifies the result by running |callback|. If the + // result code is OK then the request was successful and |results| contains + // the proxy resolution information. Request can be cancelled by resetting + // |*request|. + virtual void GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<ProxyResolver::Request>* request, + std::unique_ptr<Bindings> bindings) = 0; +}; + +// A factory for ProxyResolverV8Tracing instances. The default implementation, +// returned by Create(), creates ProxyResolverV8Tracing instances that execute +// ProxyResolverV8 on a single helper thread, and do some magic to avoid +// blocking in DNS. For more details see the design document: +// https://docs.google.com/a/google.com/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPoMJdaBn9rKreAmGOdE/edit?pli=1 +class NET_EXPORT ProxyResolverV8TracingFactory { + public: + ProxyResolverV8TracingFactory() {} + virtual ~ProxyResolverV8TracingFactory() = default; + + virtual void CreateProxyResolverV8Tracing( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings, + std::unique_ptr<ProxyResolverV8Tracing>* resolver, + const CompletionCallback& callback, + std::unique_ptr<ProxyResolverFactory::Request>* request) = 0; + + static std::unique_ptr<ProxyResolverV8TracingFactory> Create(); + + private: + DISALLOW_COPY_AND_ASSIGN(ProxyResolverV8TracingFactory); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_RESOLVER_V8_TRACING_H_ diff --git a/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_unittest.cc b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_unittest.cc new file mode 100644 index 00000000000..22e05463abe --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_unittest.cc @@ -0,0 +1,1070 @@ +// Copyright (c) 2013 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_resolution/proxy_resolver_v8_tracing.h" + +#include <string> +#include <utility> + +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_checker.h" +#include "base/values.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/dns/host_cache.h" +#include "net/dns/mock_host_resolver.h" +#include "net/log/net_log_with_source.h" +#include "net/proxy_resolution/proxy_info.h" +#include "net/test/event_waiter.h" +#include "net/test/gtest_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using net::test::IsError; +using net::test::IsOk; + +namespace net { + +namespace { + +class ProxyResolverV8TracingTest : public testing::Test { + public: + void TearDown() override { + // Drain any pending messages, which may be left over from cancellation. + // This way they get reliably run as part of the current test, rather than + // spilling into the next test's execution. + base::RunLoop().RunUntilIdle(); + } +}; + +scoped_refptr<ProxyResolverScriptData> LoadScriptData(const char* filename) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.AppendASCII("net"); + path = path.AppendASCII("data"); + path = path.AppendASCII("proxy_resolver_v8_tracing_unittest"); + path = path.AppendASCII(filename); + + // Try to read the file from disk. + std::string file_contents; + bool ok = base::ReadFileToString(path, &file_contents); + + // If we can't load the file from disk, something is misconfigured. + EXPECT_TRUE(ok) << "Failed to read file: " << path.value(); + + // Load the PAC script into the ProxyResolver. + return ProxyResolverScriptData::FromUTF8(file_contents); +} + +class MockBindings { + public: + explicit MockBindings(HostResolver* host_resolver) + : host_resolver_(host_resolver) {} + + void Alert(const base::string16& message) { + alerts_.push_back(base::UTF16ToASCII(message)); + } + void OnError(int line_number, const base::string16& error) { + waiter_.NotifyEvent(EVENT_ERROR); + errors_.push_back(std::make_pair(line_number, base::UTF16ToASCII(error))); + if (!error_callback_.is_null()) + error_callback_.Run(); + } + + HostResolver* host_resolver() { return host_resolver_; } + + std::vector<std::string> GetAlerts() { + return alerts_; + } + + std::vector<std::pair<int, std::string>> GetErrors() { + return errors_; + } + + void RunOnError(const base::Closure& callback) { + error_callback_ = callback; + waiter_.WaitForEvent(EVENT_ERROR); + } + + std::unique_ptr<ProxyResolverV8Tracing::Bindings> CreateBindings() { + return std::make_unique<ForwardingBindings>(this); + } + + private: + class ForwardingBindings : public ProxyResolverV8Tracing::Bindings { + public: + explicit ForwardingBindings(MockBindings* bindings) : bindings_(bindings) {} + + // ProxyResolverV8Tracing::Bindings overrides. + void Alert(const base::string16& message) override { + DCHECK(thread_checker_.CalledOnValidThread()); + bindings_->Alert(message); + } + + void OnError(int line_number, const base::string16& error) override { + DCHECK(thread_checker_.CalledOnValidThread()); + bindings_->OnError(line_number, error); + } + + NetLogWithSource GetNetLogWithSource() override { + DCHECK(thread_checker_.CalledOnValidThread()); + return NetLogWithSource(); + } + + HostResolver* GetHostResolver() override { + DCHECK(thread_checker_.CalledOnValidThread()); + return bindings_->host_resolver(); + } + + private: + MockBindings* bindings_; + base::ThreadChecker thread_checker_; + }; + + enum Event { + EVENT_ERROR, + }; + + std::vector<std::string> alerts_; + std::vector<std::pair<int, std::string>> errors_; + HostResolver* const host_resolver_; + base::Closure error_callback_; + EventWaiter<Event> waiter_; +}; + +std::unique_ptr<ProxyResolverV8Tracing> CreateResolver( + std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings, + const char* filename) { + std::unique_ptr<ProxyResolverV8Tracing> resolver; + std::unique_ptr<ProxyResolverV8TracingFactory> factory( + ProxyResolverV8TracingFactory::Create()); + TestCompletionCallback callback; + std::unique_ptr<ProxyResolverFactory::Request> request; + factory->CreateProxyResolverV8Tracing(LoadScriptData(filename), + std::move(bindings), &resolver, + callback.callback(), &request); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_TRUE(resolver); + return resolver; +} + +TEST_F(ProxyResolverV8TracingTest, Simple) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "simple.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + std::unique_ptr<ProxyResolver::Request> req; + + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, + mock_bindings.CreateBindings()); + + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ("foo:99", proxy_info.proxy_server().ToURI()); + + EXPECT_EQ(0u, host_resolver.num_resolve()); + + // There were no alerts or errors. + EXPECT_TRUE(mock_bindings.GetAlerts().empty()); + EXPECT_TRUE(mock_bindings.GetErrors().empty()); +} + +TEST_F(ProxyResolverV8TracingTest, JavascriptError) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "error.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + resolver->GetProxyForURL(GURL("http://throw-an-error/"), &proxy_info, + callback.callback(), &req, + mock_bindings.CreateBindings()); + + EXPECT_THAT(callback.WaitForResult(), IsError(ERR_PAC_SCRIPT_FAILED)); + + EXPECT_EQ(0u, host_resolver.num_resolve()); + + // Check the output -- there was 1 alert and 1 javascript error. + ASSERT_EQ(1u, mock_bindings.GetAlerts().size()); + EXPECT_EQ("Prepare to DIE!", mock_bindings.GetAlerts()[0]); + ASSERT_EQ(1u, mock_bindings.GetErrors().size()); + EXPECT_EQ(5, mock_bindings.GetErrors()[0].first); + EXPECT_EQ("Uncaught TypeError: Cannot read property 'split' of null", + mock_bindings.GetErrors()[0].second); +} + +TEST_F(ProxyResolverV8TracingTest, TooManyAlerts) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "too_many_alerts.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, + mock_bindings.CreateBindings()); + + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + // Iteration1 does a DNS resolve + // Iteration2 exceeds the alert buffer + // Iteration3 runs in blocking mode and completes + EXPECT_EQ("foo:3", proxy_info.proxy_server().ToURI()); + + EXPECT_EQ(1u, host_resolver.num_resolve()); + + // No errors. + EXPECT_TRUE(mock_bindings.GetErrors().empty()); + + // Check the alerts -- the script generated 50 alerts. + std::vector<std::string> alerts = mock_bindings.GetAlerts(); + ASSERT_EQ(50u, alerts.size()); + for (size_t i = 0; i < alerts.size(); i++) { + EXPECT_EQ("Gee, all these alerts are silly!", alerts[i]); + } +} + +// Verify that buffered alerts cannot grow unboundedly, even when the message is +// empty string. +TEST_F(ProxyResolverV8TracingTest, TooManyEmptyAlerts) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = CreateResolver( + mock_bindings.CreateBindings(), "too_many_empty_alerts.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, + mock_bindings.CreateBindings()); + + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ("foo:3", proxy_info.proxy_server().ToURI()); + + EXPECT_EQ(1u, host_resolver.num_resolve()); + + // No errors. + EXPECT_TRUE(mock_bindings.GetErrors().empty()); + + // Check the alerts -- the script generated 1000 alerts. + std::vector<std::string> alerts = mock_bindings.GetAlerts(); + ASSERT_EQ(1000u, alerts.size()); + for (size_t i = 0; i < alerts.size(); i++) { + EXPECT_EQ("", alerts[i]); + } +} + +// This test runs a PAC script that issues a sequence of DNS resolves. The test +// verifies the final result, and that the underlying DNS resolver received +// the correct set of queries. +TEST_F(ProxyResolverV8TracingTest, Dns) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + host_resolver.rules()->AddRuleForAddressFamily( + "host1", ADDRESS_FAMILY_IPV4, "166.155.144.44"); + host_resolver.rules() + ->AddIPLiteralRule("host1", "::1,192.168.1.1", std::string()); + host_resolver.rules()->AddSimulatedFailure("host2"); + host_resolver.rules()->AddRule("host3", "166.155.144.33"); + host_resolver.rules()->AddRule("host5", "166.155.144.55"); + host_resolver.rules()->AddSimulatedFailure("host6"); + host_resolver.rules()->AddRuleForAddressFamily( + "*", ADDRESS_FAMILY_IPV4, "122.133.144.155"); + host_resolver.rules()->AddRule("*", "133.122.100.200"); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "dns.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, + mock_bindings.CreateBindings()); + + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + // The test does 13 DNS resolution, however only 7 of them are unique. + EXPECT_EQ(7u, host_resolver.num_resolve()); + + const char* kExpectedResult = + "122.133.144.155-" // myIpAddress() + "null-" // dnsResolve('') + "__1_192.168.1.1-" // dnsResolveEx('host1') + "null-" // dnsResolve('host2') + "166.155.144.33-" // dnsResolve('host3') + "122.133.144.155-" // myIpAddress() + "166.155.144.33-" // dnsResolve('host3') + "__1_192.168.1.1-" // dnsResolveEx('host1') + "122.133.144.155-" // myIpAddress() + "null-" // dnsResolve('host2') + "-" // dnsResolveEx('host6') + "133.122.100.200-" // myIpAddressEx() + "166.155.144.44" // dnsResolve('host1') + ":99"; + + EXPECT_EQ(kExpectedResult, proxy_info.proxy_server().ToURI()); + + // No errors. + EXPECT_TRUE(mock_bindings.GetErrors().empty()); + + // The script generated 1 alert. + ASSERT_EQ(1u, mock_bindings.GetAlerts().size()); + EXPECT_EQ("iteration: 7", mock_bindings.GetAlerts()[0]); +} + +// This test runs a PAC script that does "myIpAddress()" followed by +// "dnsResolve()". This requires 2 restarts. However once the HostResolver's +// cache is warmed, subsequent calls should take 0 restarts. +TEST_F(ProxyResolverV8TracingTest, DnsChecksCache) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + host_resolver.rules()->AddRule("foopy", "166.155.144.11"); + host_resolver.rules()->AddRule("*", "122.133.144.155"); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "simple_dns.js"); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + resolver->GetProxyForURL(GURL("http://foopy/req1"), &proxy_info, + callback1.callback(), &req, + mock_bindings.CreateBindings()); + + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + + // The test does 2 DNS resolutions. + EXPECT_EQ(2u, host_resolver.num_resolve()); + + // The first request took 2 restarts, hence on g_iteration=3. + EXPECT_EQ("166.155.144.11:3", proxy_info.proxy_server().ToURI()); + + std::unique_ptr<ProxyResolver::Request> req2; + resolver->GetProxyForURL(GURL("http://foopy/req2"), &proxy_info, + callback2.callback(), &req2, + mock_bindings.CreateBindings()); + + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + + EXPECT_EQ(4u, host_resolver.num_resolve()); + + // This time no restarts were required, so g_iteration incremented by 1. + EXPECT_EQ("166.155.144.11:4", proxy_info.proxy_server().ToURI()); + + // There were no alerts or errors. + EXPECT_TRUE(mock_bindings.GetAlerts().empty()); + EXPECT_TRUE(mock_bindings.GetErrors().empty()); +} + +// This test runs a weird PAC script that was designed to defeat the DNS tracing +// optimization. The proxy resolver should detect the inconsistency and +// fall-back to synchronous mode execution. +TEST_F(ProxyResolverV8TracingTest, FallBackToSynchronous1) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + host_resolver.rules()->AddRule("host1", "166.155.144.11"); + host_resolver.rules()->AddRule("crazy4", "133.199.111.4"); + host_resolver.rules()->AddRule("*", "122.133.144.155"); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "global_sideffects1.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, + mock_bindings.CreateBindings()); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + // The script itself only does 2 DNS resolves per execution, however it + // constructs the hostname using a global counter which changes on each + // invocation. + EXPECT_EQ(3u, host_resolver.num_resolve()); + + EXPECT_EQ("166.155.144.11-133.199.111.4:100", + proxy_info.proxy_server().ToURI()); + + // No errors. + EXPECT_TRUE(mock_bindings.GetErrors().empty()); + + ASSERT_EQ(1u, mock_bindings.GetAlerts().size()); + EXPECT_EQ("iteration: 4", mock_bindings.GetAlerts()[0]); +} + +// This test runs a weird PAC script that was designed to defeat the DNS tracing +// optimization. The proxy resolver should detect the inconsistency and +// fall-back to synchronous mode execution. +TEST_F(ProxyResolverV8TracingTest, FallBackToSynchronous2) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + host_resolver.rules()->AddRule("host1", "166.155.144.11"); + host_resolver.rules()->AddRule("host2", "166.155.144.22"); + host_resolver.rules()->AddRule("host3", "166.155.144.33"); + host_resolver.rules()->AddRule("host4", "166.155.144.44"); + host_resolver.rules()->AddRule("*", "122.133.144.155"); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "global_sideffects2.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, + mock_bindings.CreateBindings()); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ(3u, host_resolver.num_resolve()); + + EXPECT_EQ("166.155.144.44:100", proxy_info.proxy_server().ToURI()); + + // There were no alerts or errors. + EXPECT_TRUE(mock_bindings.GetAlerts().empty()); + EXPECT_TRUE(mock_bindings.GetErrors().empty()); +} + +// This test runs a weird PAC script that yields a never ending sequence +// of DNS resolves when restarting. Running it will hit the maximum +// DNS resolves per request limit (20) after which every DNS resolve will +// fail. +TEST_F(ProxyResolverV8TracingTest, InfiniteDNSSequence) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + host_resolver.rules()->AddRule("host*", "166.155.144.11"); + host_resolver.rules()->AddRule("*", "122.133.144.155"); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "global_sideffects3.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, + mock_bindings.CreateBindings()); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ(20u, host_resolver.num_resolve()); + + EXPECT_EQ( + "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" + "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" + "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" + "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" + "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" + "null:21", proxy_info.proxy_server().ToURI()); + + // No errors. + EXPECT_TRUE(mock_bindings.GetErrors().empty()); + + // 1 alert. + EXPECT_EQ(1u, mock_bindings.GetAlerts().size()); + EXPECT_EQ("iteration: 21", mock_bindings.GetAlerts()[0]); +} + +// This test runs a weird PAC script that yields a never ending sequence +// of DNS resolves when restarting. Running it will hit the maximum +// DNS resolves per request limit (20) after which every DNS resolve will +// fail. +TEST_F(ProxyResolverV8TracingTest, InfiniteDNSSequence2) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + host_resolver.rules()->AddRule("host*", "166.155.144.11"); + host_resolver.rules()->AddRule("*", "122.133.144.155"); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "global_sideffects4.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, + mock_bindings.CreateBindings()); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ(20u, host_resolver.num_resolve()); + + EXPECT_EQ("null21:34", proxy_info.proxy_server().ToURI()); + + // No errors. + EXPECT_TRUE(mock_bindings.GetErrors().empty()); + + // 1 alert. + EXPECT_EQ(1u, mock_bindings.GetAlerts().size()); + EXPECT_EQ("iteration: 21", mock_bindings.GetAlerts()[0]); +} + +void DnsDuringInitHelper(bool synchronous_host_resolver) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + host_resolver.set_synchronous_mode(synchronous_host_resolver); + + host_resolver.rules()->AddRule("host1", "91.13.12.1"); + host_resolver.rules()->AddRule("host2", "91.13.12.2"); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "dns_during_init.js"); + + // Initialization did 2 dnsResolves. + EXPECT_EQ(2u, host_resolver.num_resolve()); + + host_resolver.rules()->ClearRules(); + host_resolver.GetHostCache()->clear(); + + host_resolver.rules()->AddRule("host1", "145.88.13.3"); + host_resolver.rules()->AddRule("host2", "137.89.8.45"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, + mock_bindings.CreateBindings()); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + // Fetched host1 and host2 again, since the ones done during initialization + // should not have been cached. + EXPECT_EQ(4u, host_resolver.num_resolve()); + + EXPECT_EQ("91.13.12.1-91.13.12.2-145.88.13.3-137.89.8.45:99", + proxy_info.proxy_server().ToURI()); + + // 2 alerts. + ASSERT_EQ(2u, mock_bindings.GetAlerts().size()); + EXPECT_EQ("Watsup", mock_bindings.GetAlerts()[0]); + EXPECT_EQ("Watsup2", mock_bindings.GetAlerts()[1]); +} + +// Tests a PAC script which does DNS resolves during initialization. +TEST_F(ProxyResolverV8TracingTest, DnsDuringInit) { + // Test with both both a host resolver that always completes asynchronously, + // and then again with one that completes synchronously. + DnsDuringInitHelper(false); + DnsDuringInitHelper(true); +} + +void CrashCallback(int) { + // Be extra sure that if the callback ever gets invoked, the test will fail. + CHECK(false); +} + +// Start some requests, cancel them all, and then destroy the resolver. +// Note the execution order for this test can vary. Since multiple +// threads are involved, the cancellation may be received a different +// times. +TEST_F(ProxyResolverV8TracingTest, CancelAll) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + host_resolver.rules()->AddSimulatedFailure("*"); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "dns.js"); + + const size_t kNumRequests = 5; + ProxyInfo proxy_info[kNumRequests]; + std::unique_ptr<ProxyResolver::Request> request[kNumRequests]; + + for (size_t i = 0; i < kNumRequests; ++i) { + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info[i], + base::Bind(&CrashCallback), &request[i], + mock_bindings.CreateBindings()); + } + + for (size_t i = 0; i < kNumRequests; ++i) { + request[i].reset(); + } +} + +// Note the execution order for this test can vary. Since multiple +// threads are involved, the cancellation may be received a different +// times. +TEST_F(ProxyResolverV8TracingTest, CancelSome) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + host_resolver.rules()->AddSimulatedFailure("*"); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "dns.js"); + + ProxyInfo proxy_info1; + ProxyInfo proxy_info2; + std::unique_ptr<ProxyResolver::Request> request1; + std::unique_ptr<ProxyResolver::Request> request2; + TestCompletionCallback callback; + + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info1, + base::Bind(&CrashCallback), &request1, + mock_bindings.CreateBindings()); + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info2, + callback.callback(), &request2, + mock_bindings.CreateBindings()); + + request1.reset(); + + EXPECT_THAT(callback.WaitForResult(), IsOk()); +} + +// Cancel a request after it has finished running on the worker thread, and has +// posted a task the completion task back to origin thread. +TEST_F(ProxyResolverV8TracingTest, CancelWhilePendingCompletionTask) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + host_resolver.rules()->AddSimulatedFailure("*"); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "error.js"); + + ProxyInfo proxy_info1; + ProxyInfo proxy_info2; + std::unique_ptr<ProxyResolver::Request> request1; + std::unique_ptr<ProxyResolver::Request> request2; + TestCompletionCallback callback; + + resolver->GetProxyForURL(GURL("http://throw-an-error/"), &proxy_info1, + base::Bind(&CrashCallback), &request1, + mock_bindings.CreateBindings()); + + // Wait until the first request has finished running on the worker thread. + // Cancel the first request, while it is running its completion task on + // the origin thread. Reset deletes Request opject which cancels the request. + mock_bindings.RunOnError( + base::Bind(&std::unique_ptr<ProxyResolver::Request>::reset, + base::Unretained(&request1), nullptr)); + + // Start another request, to make sure it is able to complete. + resolver->GetProxyForURL(GURL("http://i-have-no-idea-what-im-doing/"), + &proxy_info2, callback.callback(), &request2, + mock_bindings.CreateBindings()); + + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ("i-approve-this-message:42", proxy_info2.proxy_server().ToURI()); +} + +// This implementation of HostResolver allows blocking until a resolve request +// has been received. The resolve requests it receives will never be completed. +class BlockableHostResolver : public HostResolver { + public: + BlockableHostResolver() + : num_cancelled_requests_(0), waiting_for_resolve_(false) {} + + int Resolve(const RequestInfo& info, + RequestPriority priority, + AddressList* addresses, + const CompletionCallback& callback, + std::unique_ptr<Request>* out_req, + const NetLogWithSource& net_log) override { + EXPECT_FALSE(callback.is_null()); + EXPECT_TRUE(out_req); + + if (!action_.is_null()) + action_.Run(); + + // Indicate to the caller that a request was received. + EXPECT_TRUE(waiting_for_resolve_); + base::RunLoop::QuitCurrentWhenIdleDeprecated(); + + // This line is intentionally after action_.Run(), since one of the + // tests does a cancellation inside of Resolve(), and it is more + // interesting if *out_req hasn't been written yet at that point. + out_req->reset(new RequestImpl(this)); + + // Return ERR_IO_PENDING as this request will NEVER be completed. + // Expectation is for the caller to later cancel the request. + return ERR_IO_PENDING; + } + + int ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const NetLogWithSource& net_log) override { + NOTREACHED(); + return ERR_DNS_CACHE_MISS; + } + + int ResolveStaleFromCache(const RequestInfo& info, + AddressList* addresses, + HostCache::EntryStaleness* stale_info, + const NetLogWithSource& net_log) override { + NOTREACHED(); + return ERR_DNS_CACHE_MISS; + } + + bool HasCached(base::StringPiece hostname, + HostCache::Entry::Source* source_out, + HostCache::EntryStaleness* stale_out) const override { + NOTIMPLEMENTED(); + return false; + } + + void IncreaseNumOfCancelledRequests() { num_cancelled_requests_++; } + + void SetAction(const base::Callback<void(void)>& action) { + action_ = action; + } + + // Waits until Resolve() has been called. + void WaitUntilRequestIsReceived() { + waiting_for_resolve_ = true; + base::RunLoop().Run(); + DCHECK(waiting_for_resolve_); + waiting_for_resolve_ = false; + } + + int num_cancelled_requests() const { + return num_cancelled_requests_; + } + + private: + class RequestImpl : public HostResolver::Request { + public: + RequestImpl(BlockableHostResolver* resolver) : resolver_(resolver) {} + + ~RequestImpl() override { + if (resolver_) + resolver_->IncreaseNumOfCancelledRequests(); + } + + void ChangeRequestPriority(RequestPriority priority) override {} + + private: + BlockableHostResolver* resolver_; + + DISALLOW_COPY_AND_ASSIGN(RequestImpl); + }; + + int num_cancelled_requests_; + bool waiting_for_resolve_; + base::Callback<void(void)> action_; +}; + +// This cancellation test exercises a more predictable cancellation codepath -- +// when the request has an outstanding DNS request in flight. +TEST_F(ProxyResolverV8TracingTest, CancelWhileOutstandingNonBlockingDns) { + BlockableHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "dns.js"); + + ProxyInfo proxy_info1; + ProxyInfo proxy_info2; + std::unique_ptr<ProxyResolver::Request> request1; + std::unique_ptr<ProxyResolver::Request> request2; + + resolver->GetProxyForURL(GURL("http://foo/req1"), &proxy_info1, + base::Bind(&CrashCallback), &request1, + mock_bindings.CreateBindings()); + + host_resolver.WaitUntilRequestIsReceived(); + + resolver->GetProxyForURL(GURL("http://foo/req2"), &proxy_info2, + base::Bind(&CrashCallback), &request2, + mock_bindings.CreateBindings()); + + host_resolver.WaitUntilRequestIsReceived(); + + request1.reset(); + request2.reset(); + + EXPECT_EQ(2, host_resolver.num_cancelled_requests()); + + // After leaving this scope, the ProxyResolver is destroyed. + // This should not cause any problems, as the outstanding work + // should have been cancelled. +} + +void CancelRequestAndPause(std::unique_ptr<ProxyResolver::Request>* request) { + request->reset(); + + // Sleep for a little bit. This makes it more likely for the worker + // thread to have returned from its call, and serves as a regression + // test for http://crbug.com/173373. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(30)); +} + +// In non-blocking mode, the worker thread actually does block for +// a short time to see if the result is in the DNS cache. Test +// cancellation while the worker thread is waiting on this event. +TEST_F(ProxyResolverV8TracingTest, CancelWhileBlockedInNonBlockingDns) { + BlockableHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "dns.js"); + + ProxyInfo proxy_info; + std::unique_ptr<ProxyResolver::Request> request; + + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + base::Bind(&CrashCallback), &request, + mock_bindings.CreateBindings()); + + host_resolver.SetAction(base::Bind(CancelRequestAndPause, &request)); + + host_resolver.WaitUntilRequestIsReceived(); +} + +// Cancel the request while there is a pending DNS request, however before +// the request is sent to the host resolver. +TEST_F(ProxyResolverV8TracingTest, CancelWhileBlockedInNonBlockingDns2) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "dns.js"); + + ProxyInfo proxy_info; + std::unique_ptr<ProxyResolver::Request> request; + + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + base::Bind(&CrashCallback), &request, + mock_bindings.CreateBindings()); + + // Wait a bit, so the DNS task has hopefully been posted. The test will + // work whatever the delay is here, but it is most useful if the delay + // is large enough to allow a task to be posted back. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + request.reset(); + + EXPECT_EQ(0u, host_resolver.num_resolve()); +} + +TEST_F(ProxyResolverV8TracingTest, + CancelCreateResolverWhileOutstandingBlockingDns) { + BlockableHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + std::unique_ptr<ProxyResolverV8TracingFactory> factory( + ProxyResolverV8TracingFactory::Create()); + std::unique_ptr<ProxyResolverV8Tracing> resolver; + std::unique_ptr<ProxyResolverFactory::Request> request; + factory->CreateProxyResolverV8Tracing( + LoadScriptData("dns_during_init.js"), mock_bindings.CreateBindings(), + &resolver, base::Bind(&CrashCallback), &request); + + host_resolver.WaitUntilRequestIsReceived(); + + request.reset(); + EXPECT_EQ(1, host_resolver.num_cancelled_requests()); +} + +TEST_F(ProxyResolverV8TracingTest, DeleteFactoryWhileOutstandingBlockingDns) { + BlockableHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + std::unique_ptr<ProxyResolverV8Tracing> resolver; + std::unique_ptr<ProxyResolverFactory::Request> request; + { + std::unique_ptr<ProxyResolverV8TracingFactory> factory( + ProxyResolverV8TracingFactory::Create()); + + factory->CreateProxyResolverV8Tracing( + LoadScriptData("dns_during_init.js"), mock_bindings.CreateBindings(), + &resolver, base::Bind(&CrashCallback), &request); + host_resolver.WaitUntilRequestIsReceived(); + } + EXPECT_EQ(1, host_resolver.num_cancelled_requests()); +} + +TEST_F(ProxyResolverV8TracingTest, ErrorLoadingScript) { + BlockableHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + std::unique_ptr<ProxyResolverV8TracingFactory> factory( + ProxyResolverV8TracingFactory::Create()); + std::unique_ptr<ProxyResolverV8Tracing> resolver; + std::unique_ptr<ProxyResolverFactory::Request> request; + TestCompletionCallback callback; + factory->CreateProxyResolverV8Tracing( + LoadScriptData("error_on_load.js"), mock_bindings.CreateBindings(), + &resolver, callback.callback(), &request); + + EXPECT_THAT(callback.WaitForResult(), IsError(ERR_PAC_SCRIPT_FAILED)); + EXPECT_FALSE(resolver); +} + +// This tests that the execution of a PAC script is terminated when the DNS +// dependencies are missing. If the test fails, then it will hang. +TEST_F(ProxyResolverV8TracingTest, Terminate) { + MockCachingHostResolver host_resolver; + MockBindings mock_bindings(&host_resolver); + + host_resolver.rules()->AddRule("host1", "182.111.0.222"); + host_resolver.rules()->AddRule("host2", "111.33.44.55"); + + std::unique_ptr<ProxyResolverV8Tracing> resolver = + CreateResolver(mock_bindings.CreateBindings(), "terminate.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + resolver->GetProxyForURL(GURL("http://foopy/req1"), &proxy_info, + callback.callback(), &req, + mock_bindings.CreateBindings()); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + // The test does 2 DNS resolutions. + EXPECT_EQ(2u, host_resolver.num_resolve()); + + EXPECT_EQ("foopy:3", proxy_info.proxy_server().ToURI()); + + // No errors or alerts. + EXPECT_TRUE(mock_bindings.GetErrors().empty()); + EXPECT_TRUE(mock_bindings.GetAlerts().empty()); +} + +// Tests that multiple instances of ProxyResolverV8Tracing can coexist and run +// correctly at the same time. This is relevant because at the moment (time +// this test was written) each ProxyResolverV8Tracing creates its own thread to +// run V8 on, however each thread is operating on the same v8::Isolate. +TEST_F(ProxyResolverV8TracingTest, MultipleResolvers) { + // ------------------------ + // Setup resolver0 + // ------------------------ + MockHostResolver host_resolver0; + MockBindings mock_bindings0(&host_resolver0); + host_resolver0.rules()->AddRuleForAddressFamily( + "host1", ADDRESS_FAMILY_IPV4, "166.155.144.44"); + host_resolver0.rules() + ->AddIPLiteralRule("host1", "::1,192.168.1.1", std::string()); + host_resolver0.rules()->AddSimulatedFailure("host2"); + host_resolver0.rules()->AddRule("host3", "166.155.144.33"); + host_resolver0.rules()->AddRule("host5", "166.155.144.55"); + host_resolver0.rules()->AddSimulatedFailure("host6"); + host_resolver0.rules()->AddRuleForAddressFamily( + "*", ADDRESS_FAMILY_IPV4, "122.133.144.155"); + host_resolver0.rules()->AddRule("*", "133.122.100.200"); + std::unique_ptr<ProxyResolverV8Tracing> resolver0 = + CreateResolver(mock_bindings0.CreateBindings(), "dns.js"); + + // ------------------------ + // Setup resolver1 + // ------------------------ + std::unique_ptr<ProxyResolverV8Tracing> resolver1 = + CreateResolver(mock_bindings0.CreateBindings(), "dns.js"); + + // ------------------------ + // Setup resolver2 + // ------------------------ + std::unique_ptr<ProxyResolverV8Tracing> resolver2 = + CreateResolver(mock_bindings0.CreateBindings(), "simple.js"); + + // ------------------------ + // Setup resolver3 + // ------------------------ + MockHostResolver host_resolver3; + MockBindings mock_bindings3(&host_resolver3); + host_resolver3.rules()->AddRule("foo", "166.155.144.33"); + std::unique_ptr<ProxyResolverV8Tracing> resolver3 = + CreateResolver(mock_bindings3.CreateBindings(), "simple_dns.js"); + + // ------------------------ + // Queue up work for each resolver (which will be running in parallel). + // ------------------------ + + ProxyResolverV8Tracing* resolver[] = { + resolver0.get(), resolver1.get(), resolver2.get(), resolver3.get(), + }; + + const size_t kNumResolvers = arraysize(resolver); + const size_t kNumIterations = 20; + const size_t kNumResults = kNumResolvers * kNumIterations; + TestCompletionCallback callback[kNumResults]; + ProxyInfo proxy_info[kNumResults]; + std::unique_ptr<ProxyResolver::Request> request[kNumResults]; + + for (size_t i = 0; i < kNumResults; ++i) { + size_t resolver_i = i % kNumResolvers; + resolver[resolver_i]->GetProxyForURL( + GURL("http://foo/"), &proxy_info[i], callback[i].callback(), + &request[i], resolver_i == 3 ? mock_bindings3.CreateBindings() + : mock_bindings0.CreateBindings()); + } + + // ------------------------ + // Verify all of the results. + // ------------------------ + + const char* kExpectedForDnsJs = + "122.133.144.155-" // myIpAddress() + "null-" // dnsResolve('') + "__1_192.168.1.1-" // dnsResolveEx('host1') + "null-" // dnsResolve('host2') + "166.155.144.33-" // dnsResolve('host3') + "122.133.144.155-" // myIpAddress() + "166.155.144.33-" // dnsResolve('host3') + "__1_192.168.1.1-" // dnsResolveEx('host1') + "122.133.144.155-" // myIpAddress() + "null-" // dnsResolve('host2') + "-" // dnsResolveEx('host6') + "133.122.100.200-" // myIpAddressEx() + "166.155.144.44" // dnsResolve('host1') + ":99"; + + for (size_t i = 0; i < kNumResults; ++i) { + size_t resolver_i = i % kNumResolvers; + EXPECT_THAT(callback[i].WaitForResult(), IsOk()); + + std::string proxy_uri = proxy_info[i].proxy_server().ToURI(); + + if (resolver_i == 0 || resolver_i == 1) { + EXPECT_EQ(kExpectedForDnsJs, proxy_uri); + } else if (resolver_i == 2) { + EXPECT_EQ("foo:99", proxy_uri); + } else if (resolver_i == 3) { + EXPECT_EQ("166.155.144.33:", + proxy_uri.substr(0, proxy_uri.find(':') + 1)); + } else { + NOTREACHED(); + } + } +} + +} // namespace + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper.cc b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper.cc new file mode 100644 index 00000000000..abe633816c1 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper.cc @@ -0,0 +1,189 @@ +// Copyright 2015 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_resolution/proxy_resolver_v8_tracing_wrapper.h" + +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/values.h" +#include "net/base/net_errors.h" +#include "net/log/net_log.h" +#include "net/log/net_log_capture_mode.h" +#include "net/log/net_log_event_type.h" +#include "net/log/net_log_parameters_callback.h" +#include "net/log/net_log_with_source.h" +#include "net/proxy_resolution/proxy_resolver_error_observer.h" + +namespace net { + +namespace { + +// Returns event parameters for a PAC error message (line number + message). +std::unique_ptr<base::Value> NetLogErrorCallback( + int line_number, + const base::string16* message, + NetLogCaptureMode /* capture_mode */) { + std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + dict->SetInteger("line_number", line_number); + dict->SetString("message", *message); + return std::move(dict); +} + +class BindingsImpl : public ProxyResolverV8Tracing::Bindings { + public: + BindingsImpl(ProxyResolverErrorObserver* error_observer, + HostResolver* host_resolver, + NetLog* net_log, + const NetLogWithSource& net_log_with_source) + : error_observer_(error_observer), + host_resolver_(host_resolver), + net_log_(net_log), + net_log_with_source_(net_log_with_source) {} + + // ProxyResolverV8Tracing::Bindings overrides. + void Alert(const base::string16& message) override { + // Send to the NetLog. + LogEventToCurrentRequestAndGlobally( + NetLogEventType::PAC_JAVASCRIPT_ALERT, + NetLog::StringCallback("message", &message)); + } + + void OnError(int line_number, const base::string16& message) override { + // Send the error to the NetLog. + LogEventToCurrentRequestAndGlobally( + NetLogEventType::PAC_JAVASCRIPT_ERROR, + base::Bind(&NetLogErrorCallback, line_number, &message)); + if (error_observer_) + error_observer_->OnPACScriptError(line_number, message); + } + + HostResolver* GetHostResolver() override { return host_resolver_; } + + NetLogWithSource GetNetLogWithSource() override { + return net_log_with_source_; + } + + private: + void LogEventToCurrentRequestAndGlobally( + NetLogEventType type, + const NetLogParametersCallback& parameters_callback) { + net_log_with_source_.AddEvent(type, parameters_callback); + + // Emit to the global NetLog event stream. + if (net_log_) + net_log_->AddGlobalEntry(type, parameters_callback); + } + + ProxyResolverErrorObserver* error_observer_; + HostResolver* host_resolver_; + NetLog* net_log_; + NetLogWithSource net_log_with_source_; +}; + +class ProxyResolverV8TracingWrapper : public ProxyResolver { + public: + ProxyResolverV8TracingWrapper( + std::unique_ptr<ProxyResolverV8Tracing> resolver_impl, + NetLog* net_log, + HostResolver* host_resolver, + std::unique_ptr<ProxyResolverErrorObserver> error_observer); + + int GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) override; + + private: + std::unique_ptr<ProxyResolverV8Tracing> resolver_impl_; + NetLog* net_log_; + HostResolver* host_resolver_; + std::unique_ptr<ProxyResolverErrorObserver> error_observer_; + + DISALLOW_COPY_AND_ASSIGN(ProxyResolverV8TracingWrapper); +}; + +ProxyResolverV8TracingWrapper::ProxyResolverV8TracingWrapper( + std::unique_ptr<ProxyResolverV8Tracing> resolver_impl, + NetLog* net_log, + HostResolver* host_resolver, + std::unique_ptr<ProxyResolverErrorObserver> error_observer) + : resolver_impl_(std::move(resolver_impl)), + net_log_(net_log), + host_resolver_(host_resolver), + error_observer_(std::move(error_observer)) {} + +int ProxyResolverV8TracingWrapper::GetProxyForURL( + const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) { + resolver_impl_->GetProxyForURL( + url, results, callback, request, + std::make_unique<BindingsImpl>(error_observer_.get(), host_resolver_, + net_log_, net_log)); + return ERR_IO_PENDING; +} + +} // namespace + +ProxyResolverFactoryV8TracingWrapper::ProxyResolverFactoryV8TracingWrapper( + HostResolver* host_resolver, + NetLog* net_log, + const base::Callback<std::unique_ptr<ProxyResolverErrorObserver>()>& + error_observer_factory) + : ProxyResolverFactory(true), + factory_impl_(ProxyResolverV8TracingFactory::Create()), + host_resolver_(host_resolver), + net_log_(net_log), + error_observer_factory_(error_observer_factory) {} + +ProxyResolverFactoryV8TracingWrapper::~ProxyResolverFactoryV8TracingWrapper() = + default; + +int ProxyResolverFactoryV8TracingWrapper::CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback, + std::unique_ptr<Request>* request) { + std::unique_ptr<std::unique_ptr<ProxyResolverV8Tracing>> v8_resolver( + new std::unique_ptr<ProxyResolverV8Tracing>); + std::unique_ptr<ProxyResolverErrorObserver> error_observer = + error_observer_factory_.Run(); + // Note: Argument evaluation order is unspecified, so make copies before + // passing |v8_resolver| and |error_observer|. + std::unique_ptr<ProxyResolverV8Tracing>* v8_resolver_local = + v8_resolver.get(); + ProxyResolverErrorObserver* error_observer_local = error_observer.get(); + factory_impl_->CreateProxyResolverV8Tracing( + pac_script, + std::make_unique<BindingsImpl>(error_observer_local, host_resolver_, + net_log_, NetLogWithSource()), + v8_resolver_local, + base::Bind(&ProxyResolverFactoryV8TracingWrapper::OnProxyResolverCreated, + base::Unretained(this), base::Passed(&v8_resolver), resolver, + callback, base::Passed(&error_observer)), + request); + return ERR_IO_PENDING; +} + +void ProxyResolverFactoryV8TracingWrapper::OnProxyResolverCreated( + std::unique_ptr<std::unique_ptr<ProxyResolverV8Tracing>> v8_resolver, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback, + std::unique_ptr<ProxyResolverErrorObserver> error_observer, + int error) { + if (error == OK) { + resolver->reset(new ProxyResolverV8TracingWrapper( + std::move(*v8_resolver), net_log_, host_resolver_, + std::move(error_observer))); + } + callback.Run(error); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper.h b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper.h new file mode 100644 index 00000000000..e5a170b0651 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper.h @@ -0,0 +1,66 @@ +// Copyright 2015 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_RESOLUTION_PROXY_RESOLVER_V8_TRACING_WRAPPER_H_ +#define NET_PROXY_RESOLUTION_PROXY_RESOLVER_V8_TRACING_WRAPPER_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/proxy_resolver.h" +#include "net/proxy_resolution/proxy_resolver_factory.h" +#include "net/proxy_resolution/proxy_resolver_v8_tracing.h" + +namespace net { + +class HostResolver; +class NetLog; +class ProxyResolverErrorObserver; + +// A wrapper for ProxyResolverV8TracingFactory that implements the +// ProxyResolverFactory interface. +class NET_EXPORT ProxyResolverFactoryV8TracingWrapper + : public ProxyResolverFactory { + public: + // Note that |host_resolver| and |net_log| are expected to outlive |this| and + // any ProxyResolver instances created using |this|. |error_observer_factory| + // will be invoked once per CreateProxyResolver() call to create a + // ProxyResolverErrorObserver to be used by the ProxyResolver instance + // returned by that call. + ProxyResolverFactoryV8TracingWrapper( + HostResolver* host_resolver, + NetLog* net_log, + const base::Callback<std::unique_ptr<ProxyResolverErrorObserver>()>& + error_observer_factory); + ~ProxyResolverFactoryV8TracingWrapper() override; + + // ProxyResolverFactory override. + int CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback, + std::unique_ptr<Request>* request) override; + + private: + void OnProxyResolverCreated( + std::unique_ptr<std::unique_ptr<ProxyResolverV8Tracing>> v8_resolver, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback, + std::unique_ptr<ProxyResolverErrorObserver> error_observer, + int error); + + std::unique_ptr<ProxyResolverV8TracingFactory> factory_impl_; + HostResolver* const host_resolver_; + NetLog* const net_log_; + const base::Callback<std::unique_ptr<ProxyResolverErrorObserver>()> + error_observer_factory_; + + DISALLOW_COPY_AND_ASSIGN(ProxyResolverFactoryV8TracingWrapper); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_RESOLVER_V8_TRACING_WRAPPER_H_ diff --git a/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper_unittest.cc b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper_unittest.cc new file mode 100644 index 00000000000..44acb35fd12 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper_unittest.cc @@ -0,0 +1,1192 @@ +// Copyright (c) 2013 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_resolution/proxy_resolver_v8_tracing_wrapper.h" + +#include <string> + +#include "base/files/file_util.h" +#include "base/memory/ptr_util.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "base/values.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/dns/host_cache.h" +#include "net/dns/mock_host_resolver.h" +#include "net/log/net_log_event_type.h" +#include "net/log/net_log_with_source.h" +#include "net/log/test_net_log.h" +#include "net/log/test_net_log_entry.h" +#include "net/log/test_net_log_util.h" +#include "net/proxy_resolution/proxy_info.h" +#include "net/proxy_resolution/proxy_resolver_error_observer.h" +#include "net/test/event_waiter.h" +#include "net/test/gtest_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using net::test::IsError; +using net::test::IsOk; + +namespace net { + +namespace { + +class ProxyResolverV8TracingWrapperTest : public testing::Test { + public: + void TearDown() override { + // Drain any pending messages, which may be left over from cancellation. + // This way they get reliably run as part of the current test, rather than + // spilling into the next test's execution. + base::RunLoop().RunUntilIdle(); + } +}; + +scoped_refptr<ProxyResolverScriptData> LoadScriptData(const char* filename) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.AppendASCII("net"); + path = path.AppendASCII("data"); + path = path.AppendASCII("proxy_resolver_v8_tracing_unittest"); + path = path.AppendASCII(filename); + + // Try to read the file from disk. + std::string file_contents; + bool ok = base::ReadFileToString(path, &file_contents); + + // If we can't load the file from disk, something is misconfigured. + EXPECT_TRUE(ok) << "Failed to read file: " << path.value(); + + // Load the PAC script into the ProxyResolver. + return ProxyResolverScriptData::FromUTF8(file_contents); +} + +std::unique_ptr<ProxyResolverErrorObserver> ReturnErrorObserver( + std::unique_ptr<ProxyResolverErrorObserver> error_observer) { + return error_observer; +} + +std::unique_ptr<ProxyResolver> CreateResolver( + NetLog* net_log, + HostResolver* host_resolver, + std::unique_ptr<ProxyResolverErrorObserver> error_observer, + const char* filename) { + std::unique_ptr<ProxyResolver> resolver; + ProxyResolverFactoryV8TracingWrapper factory( + host_resolver, net_log, + base::Bind(&ReturnErrorObserver, base::Passed(&error_observer))); + TestCompletionCallback callback; + std::unique_ptr<ProxyResolverFactory::Request> request; + int rv = factory.CreateProxyResolver(LoadScriptData(filename), &resolver, + callback.callback(), &request); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_TRUE(resolver); + return resolver; +} + +class MockErrorObserver : public ProxyResolverErrorObserver { + public: + void OnPACScriptError(int line_number, const base::string16& error) override { + output += base::StringPrintf("Error: line %d: %s\n", line_number, + base::UTF16ToASCII(error).c_str()); + waiter_.NotifyEvent(EVENT_ERROR); + if (!error_callback_.is_null()) + error_callback_.Run(); + } + + std::string GetOutput() { + return output; + } + + void RunOnError(const base::Closure& callback) { + error_callback_ = callback; + waiter_.WaitForEvent(EVENT_ERROR); + } + + private: + enum Event { + EVENT_ERROR, + }; + std::string output; + + base::Closure error_callback_; + EventWaiter<Event> waiter_; +}; + +TEST_F(ProxyResolverV8TracingWrapperTest, Simple) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + std::unique_ptr<ProxyResolver> resolver = CreateResolver( + &log, &host_resolver, base::WrapUnique(error_observer), "simple.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, request_log.bound()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ("foo:99", proxy_info.proxy_server().ToURI()); + + EXPECT_EQ(0u, host_resolver.num_resolve()); + + // There were no errors. + EXPECT_EQ("", error_observer->GetOutput()); + + // Check the NetLogs -- nothing was logged. + EXPECT_EQ(0u, log.GetSize()); + EXPECT_EQ(0u, request_log.GetSize()); +} + +TEST_F(ProxyResolverV8TracingWrapperTest, JavascriptError) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + std::unique_ptr<ProxyResolver> resolver = CreateResolver( + &log, &host_resolver, base::WrapUnique(error_observer), "error.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://throw-an-error/"), &proxy_info, + callback.callback(), &req, request_log.bound()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsError(ERR_PAC_SCRIPT_FAILED)); + + EXPECT_EQ(0u, host_resolver.num_resolve()); + + EXPECT_EQ( + "Error: line 5: Uncaught TypeError: Cannot read property 'split' " + "of null\n", + error_observer->GetOutput()); + + // Check the NetLogs -- there was 1 alert and 1 javascript error, and they + // were output to both the global log, and per-request log. + TestNetLogEntry::List entries_list[2]; + log.GetEntries(&entries_list[0]); + request_log.GetEntries(&entries_list[1]); + + for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) { + const TestNetLogEntry::List& entries = entries_list[list_i]; + EXPECT_EQ(2u, entries.size()); + EXPECT_TRUE(LogContainsEvent(entries, 0, + NetLogEventType::PAC_JAVASCRIPT_ALERT, + NetLogEventPhase::NONE)); + EXPECT_TRUE(LogContainsEvent(entries, 1, + NetLogEventType::PAC_JAVASCRIPT_ERROR, + NetLogEventPhase::NONE)); + + EXPECT_EQ("{\"message\":\"Prepare to DIE!\"}", entries[0].GetParamsJson()); + EXPECT_EQ( + "{\"line_number\":5,\"message\":\"Uncaught TypeError: Cannot " + "read property 'split' of null\"}", + entries[1].GetParamsJson()); + } +} + +TEST_F(ProxyResolverV8TracingWrapperTest, TooManyAlerts) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + std::unique_ptr<ProxyResolver> resolver = + CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer), + "too_many_alerts.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, request_log.bound()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + // Iteration1 does a DNS resolve + // Iteration2 exceeds the alert buffer + // Iteration3 runs in blocking mode and completes + EXPECT_EQ("foo:3", proxy_info.proxy_server().ToURI()); + + EXPECT_EQ(1u, host_resolver.num_resolve()); + + // No errors. + EXPECT_EQ("", error_observer->GetOutput()); + + // Check the NetLogs -- the script generated 50 alerts, which were mirrored + // to both the global and per-request logs. + TestNetLogEntry::List entries_list[2]; + log.GetEntries(&entries_list[0]); + request_log.GetEntries(&entries_list[1]); + + for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) { + const TestNetLogEntry::List& entries = entries_list[list_i]; + EXPECT_EQ(50u, entries.size()); + for (size_t i = 0; i < entries.size(); ++i) { + ASSERT_TRUE(LogContainsEvent(entries, i, + NetLogEventType::PAC_JAVASCRIPT_ALERT, + NetLogEventPhase::NONE)); + } + } +} + +// Verify that buffered alerts cannot grow unboundedly, even when the message is +// empty string. +TEST_F(ProxyResolverV8TracingWrapperTest, TooManyEmptyAlerts) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + std::unique_ptr<ProxyResolver> resolver = + CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer), + "too_many_empty_alerts.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, request_log.bound()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ("foo:3", proxy_info.proxy_server().ToURI()); + + EXPECT_EQ(1u, host_resolver.num_resolve()); + + // No errors. + EXPECT_EQ("", error_observer->GetOutput()); + + // Check the NetLogs -- the script generated 50 alerts, which were mirrored + // to both the global and per-request logs. + TestNetLogEntry::List entries_list[2]; + log.GetEntries(&entries_list[0]); + request_log.GetEntries(&entries_list[1]); + + for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) { + const TestNetLogEntry::List& entries = entries_list[list_i]; + EXPECT_EQ(1000u, entries.size()); + for (size_t i = 0; i < entries.size(); ++i) { + ASSERT_TRUE(LogContainsEvent(entries, i, + NetLogEventType::PAC_JAVASCRIPT_ALERT, + NetLogEventPhase::NONE)); + } + } +} + +// This test runs a PAC script that issues a sequence of DNS resolves. The test +// verifies the final result, and that the underlying DNS resolver received +// the correct set of queries. +TEST_F(ProxyResolverV8TracingWrapperTest, Dns) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + host_resolver.rules()->AddRuleForAddressFamily("host1", ADDRESS_FAMILY_IPV4, + "166.155.144.44"); + host_resolver.rules()->AddIPLiteralRule("host1", "::1,192.168.1.1", + std::string()); + host_resolver.rules()->AddSimulatedFailure("host2"); + host_resolver.rules()->AddRule("host3", "166.155.144.33"); + host_resolver.rules()->AddRule("host5", "166.155.144.55"); + host_resolver.rules()->AddSimulatedFailure("host6"); + host_resolver.rules()->AddRuleForAddressFamily("*", ADDRESS_FAMILY_IPV4, + "122.133.144.155"); + host_resolver.rules()->AddRule("*", "133.122.100.200"); + + std::unique_ptr<ProxyResolver> resolver = CreateResolver( + &log, &host_resolver, base::WrapUnique(error_observer), "dns.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, request_log.bound()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + // The test does 13 DNS resolution, however only 7 of them are unique. + EXPECT_EQ(7u, host_resolver.num_resolve()); + + const char* kExpectedResult = + "122.133.144.155-" // myIpAddress() + "null-" // dnsResolve('') + "__1_192.168.1.1-" // dnsResolveEx('host1') + "null-" // dnsResolve('host2') + "166.155.144.33-" // dnsResolve('host3') + "122.133.144.155-" // myIpAddress() + "166.155.144.33-" // dnsResolve('host3') + "__1_192.168.1.1-" // dnsResolveEx('host1') + "122.133.144.155-" // myIpAddress() + "null-" // dnsResolve('host2') + "-" // dnsResolveEx('host6') + "133.122.100.200-" // myIpAddressEx() + "166.155.144.44" // dnsResolve('host1') + ":99"; + + EXPECT_EQ(kExpectedResult, proxy_info.proxy_server().ToURI()); + + // No errors. + EXPECT_EQ("", error_observer->GetOutput()); + + // Check the NetLogs -- the script generated 1 alert, mirrored to both + // the per-request and global logs. + TestNetLogEntry::List entries_list[2]; + log.GetEntries(&entries_list[0]); + request_log.GetEntries(&entries_list[1]); + + for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) { + const TestNetLogEntry::List& entries = entries_list[list_i]; + EXPECT_EQ(1u, entries.size()); + EXPECT_TRUE(LogContainsEvent(entries, 0, + NetLogEventType::PAC_JAVASCRIPT_ALERT, + NetLogEventPhase::NONE)); + EXPECT_EQ("{\"message\":\"iteration: 7\"}", entries[0].GetParamsJson()); + } +} + +// This test runs a PAC script that does "myIpAddress()" followed by +// "dnsResolve()". This requires 2 restarts. However once the HostResolver's +// cache is warmed, subsequent calls should take 0 restarts. +TEST_F(ProxyResolverV8TracingWrapperTest, DnsChecksCache) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + host_resolver.rules()->AddRule("foopy", "166.155.144.11"); + host_resolver.rules()->AddRule("*", "122.133.144.155"); + + std::unique_ptr<ProxyResolver> resolver = CreateResolver( + &log, &host_resolver, base::WrapUnique(error_observer), "simple_dns.js"); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://foopy/req1"), &proxy_info, + callback1.callback(), &req, request_log.bound()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + + // The test does 2 DNS resolutions. + EXPECT_EQ(2u, host_resolver.num_resolve()); + + // The first request took 2 restarts, hence on g_iteration=3. + EXPECT_EQ("166.155.144.11:3", proxy_info.proxy_server().ToURI()); + + std::unique_ptr<ProxyResolver::Request> req2; + rv = resolver->GetProxyForURL(GURL("http://foopy/req2"), &proxy_info, + callback2.callback(), &req2, + request_log.bound()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + + EXPECT_EQ(4u, host_resolver.num_resolve()); + + // This time no restarts were required, so g_iteration incremented by 1. + EXPECT_EQ("166.155.144.11:4", proxy_info.proxy_server().ToURI()); + + // No errors. + EXPECT_EQ("", error_observer->GetOutput()); + + EXPECT_EQ(0u, log.GetSize()); + EXPECT_EQ(0u, request_log.GetSize()); +} + +// This test runs a weird PAC script that was designed to defeat the DNS tracing +// optimization. The proxy resolver should detect the inconsistency and +// fall-back to synchronous mode execution. +TEST_F(ProxyResolverV8TracingWrapperTest, FallBackToSynchronous1) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + host_resolver.rules()->AddRule("host1", "166.155.144.11"); + host_resolver.rules()->AddRule("crazy4", "133.199.111.4"); + host_resolver.rules()->AddRule("*", "122.133.144.155"); + + std::unique_ptr<ProxyResolver> resolver = + CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer), + "global_sideffects1.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, request_log.bound()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + // The script itself only does 2 DNS resolves per execution, however it + // constructs the hostname using a global counter which changes on each + // invocation. + EXPECT_EQ(3u, host_resolver.num_resolve()); + + EXPECT_EQ("166.155.144.11-133.199.111.4:100", + proxy_info.proxy_server().ToURI()); + + // No errors. + EXPECT_EQ("", error_observer->GetOutput()); + + // Check the NetLogs -- the script generated 1 alert, mirrored to both + // the per-request and global logs. + TestNetLogEntry::List entries_list[2]; + log.GetEntries(&entries_list[0]); + request_log.GetEntries(&entries_list[1]); + + for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) { + const TestNetLogEntry::List& entries = entries_list[list_i]; + EXPECT_EQ(1u, entries.size()); + EXPECT_TRUE(LogContainsEvent(entries, 0, + NetLogEventType::PAC_JAVASCRIPT_ALERT, + NetLogEventPhase::NONE)); + EXPECT_EQ("{\"message\":\"iteration: 4\"}", entries[0].GetParamsJson()); + } +} + +// This test runs a weird PAC script that was designed to defeat the DNS tracing +// optimization. The proxy resolver should detect the inconsistency and +// fall-back to synchronous mode execution. +TEST_F(ProxyResolverV8TracingWrapperTest, FallBackToSynchronous2) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + host_resolver.rules()->AddRule("host1", "166.155.144.11"); + host_resolver.rules()->AddRule("host2", "166.155.144.22"); + host_resolver.rules()->AddRule("host3", "166.155.144.33"); + host_resolver.rules()->AddRule("host4", "166.155.144.44"); + host_resolver.rules()->AddRule("*", "122.133.144.155"); + + std::unique_ptr<ProxyResolver> resolver = + CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer), + "global_sideffects2.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, request_log.bound()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ(3u, host_resolver.num_resolve()); + + EXPECT_EQ("166.155.144.44:100", proxy_info.proxy_server().ToURI()); + + // No errors. + EXPECT_EQ("", error_observer->GetOutput()); + + // Check the NetLogs -- nothing was logged. + EXPECT_EQ(0u, log.GetSize()); + EXPECT_EQ(0u, request_log.GetSize()); +} + +// This test runs a weird PAC script that yields a never ending sequence +// of DNS resolves when restarting. Running it will hit the maximum +// DNS resolves per request limit (20) after which every DNS resolve will +// fail. +TEST_F(ProxyResolverV8TracingWrapperTest, InfiniteDNSSequence) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + host_resolver.rules()->AddRule("host*", "166.155.144.11"); + host_resolver.rules()->AddRule("*", "122.133.144.155"); + + std::unique_ptr<ProxyResolver> resolver = + CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer), + "global_sideffects3.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + + callback.callback(), &req, request_log.bound()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ(20u, host_resolver.num_resolve()); + + EXPECT_EQ( + "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" + "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" + "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" + "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" + "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" + "null:21", + proxy_info.proxy_server().ToURI()); + + // No errors. + EXPECT_EQ("", error_observer->GetOutput()); + + // Check the NetLogs -- 1 alert was logged. + EXPECT_EQ(1u, log.GetSize()); + EXPECT_EQ(1u, request_log.GetSize()); +} + +// This test runs a weird PAC script that yields a never ending sequence +// of DNS resolves when restarting. Running it will hit the maximum +// DNS resolves per request limit (20) after which every DNS resolve will +// fail. +TEST_F(ProxyResolverV8TracingWrapperTest, InfiniteDNSSequence2) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + host_resolver.rules()->AddRule("host*", "166.155.144.11"); + host_resolver.rules()->AddRule("*", "122.133.144.155"); + + std::unique_ptr<ProxyResolver> resolver = + CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer), + "global_sideffects4.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, request_log.bound()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ(20u, host_resolver.num_resolve()); + + EXPECT_EQ("null21:34", proxy_info.proxy_server().ToURI()); + + // No errors. + EXPECT_EQ("", error_observer->GetOutput()); + + // Check the NetLogs -- 1 alert was logged. + EXPECT_EQ(1u, log.GetSize()); + EXPECT_EQ(1u, request_log.GetSize()); +} + +void DnsDuringInitHelper(bool synchronous_host_resolver) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + host_resolver.set_synchronous_mode(synchronous_host_resolver); + MockErrorObserver* error_observer = new MockErrorObserver; + + host_resolver.rules()->AddRule("host1", "91.13.12.1"); + host_resolver.rules()->AddRule("host2", "91.13.12.2"); + + std::unique_ptr<ProxyResolver> resolver = + CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer), + "dns_during_init.js"); + + // Initialization did 2 dnsResolves. + EXPECT_EQ(2u, host_resolver.num_resolve()); + + host_resolver.rules()->ClearRules(); + host_resolver.GetHostCache()->clear(); + + host_resolver.rules()->AddRule("host1", "145.88.13.3"); + host_resolver.rules()->AddRule("host2", "137.89.8.45"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + callback.callback(), &req, request_log.bound()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + // Fetched host1 and host2 again, since the ones done during initialization + // should not have been cached. + EXPECT_EQ(4u, host_resolver.num_resolve()); + + EXPECT_EQ("91.13.12.1-91.13.12.2-145.88.13.3-137.89.8.45:99", + proxy_info.proxy_server().ToURI()); + + // Check the NetLogs -- the script generated 2 alerts during initialization. + EXPECT_EQ(0u, request_log.GetSize()); + TestNetLogEntry::List entries; + log.GetEntries(&entries); + + ASSERT_EQ(2u, entries.size()); + EXPECT_TRUE(LogContainsEvent(entries, 0, + NetLogEventType::PAC_JAVASCRIPT_ALERT, + NetLogEventPhase::NONE)); + EXPECT_TRUE(LogContainsEvent(entries, 1, + NetLogEventType::PAC_JAVASCRIPT_ALERT, + NetLogEventPhase::NONE)); + + EXPECT_EQ("{\"message\":\"Watsup\"}", entries[0].GetParamsJson()); + EXPECT_EQ("{\"message\":\"Watsup2\"}", entries[1].GetParamsJson()); +} + +// Tests a PAC script which does DNS resolves during initialization. +TEST_F(ProxyResolverV8TracingWrapperTest, DnsDuringInit) { + // Test with both both a host resolver that always completes asynchronously, + // and then again with one that completes synchronously. + DnsDuringInitHelper(false); + DnsDuringInitHelper(true); +} + +void CrashCallback(int) { + // Be extra sure that if the callback ever gets invoked, the test will fail. + CHECK(false); +} + +// Start some requests, cancel them all, and then destroy the resolver. +// Note the execution order for this test can vary. Since multiple +// threads are involved, the cancellation may be received a different +// times. +TEST_F(ProxyResolverV8TracingWrapperTest, CancelAll) { + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + host_resolver.rules()->AddSimulatedFailure("*"); + + std::unique_ptr<ProxyResolver> resolver = CreateResolver( + nullptr, &host_resolver, base::WrapUnique(error_observer), "dns.js"); + + const size_t kNumRequests = 5; + ProxyInfo proxy_info[kNumRequests]; + std::unique_ptr<ProxyResolver::Request> request[kNumRequests]; + + for (size_t i = 0; i < kNumRequests; ++i) { + int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info[i], + base::Bind(&CrashCallback), &request[i], + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + } + + for (size_t i = 0; i < kNumRequests; ++i) { + request[i].reset(); + } +} + +// Note the execution order for this test can vary. Since multiple +// threads are involved, the cancellation may be received a different +// times. +TEST_F(ProxyResolverV8TracingWrapperTest, CancelSome) { + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + host_resolver.rules()->AddSimulatedFailure("*"); + + std::unique_ptr<ProxyResolver> resolver = CreateResolver( + nullptr, &host_resolver, base::WrapUnique(error_observer), "dns.js"); + + ProxyInfo proxy_info1; + ProxyInfo proxy_info2; + std::unique_ptr<ProxyResolver::Request> request1; + std::unique_ptr<ProxyResolver::Request> request2; + TestCompletionCallback callback; + + int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info1, + base::Bind(&CrashCallback), &request1, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info2, + callback.callback(), &request2, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + request1.reset(); + + EXPECT_THAT(callback.WaitForResult(), IsOk()); +} + +// Cancel a request after it has finished running on the worker thread, and has +// posted a task the completion task back to origin thread. +TEST_F(ProxyResolverV8TracingWrapperTest, CancelWhilePendingCompletionTask) { + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + host_resolver.rules()->AddSimulatedFailure("*"); + + std::unique_ptr<ProxyResolver> resolver = CreateResolver( + nullptr, &host_resolver, base::WrapUnique(error_observer), "error.js"); + + ProxyInfo proxy_info1; + ProxyInfo proxy_info2; + std::unique_ptr<ProxyResolver::Request> request1; + std::unique_ptr<ProxyResolver::Request> request2; + TestCompletionCallback callback; + + int rv = resolver->GetProxyForURL(GURL("http://throw-an-error/"), + &proxy_info1, base::Bind(&CrashCallback), + &request1, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Wait until the first request has finished running on the worker thread. + // Cancel the first request, while it has a pending completion task on + // the origin thread. Reset deletes Request object which cancels the request. + error_observer->RunOnError( + base::Bind(&std::unique_ptr<ProxyResolver::Request>::reset, + base::Unretained(&request1), nullptr)); + + // Start another request, to make sure it is able to complete. + rv = resolver->GetProxyForURL(GURL("http://i-have-no-idea-what-im-doing/"), + &proxy_info2, callback.callback(), &request2, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + EXPECT_EQ("i-approve-this-message:42", proxy_info2.proxy_server().ToURI()); +} + +// This implementation of HostResolver allows blocking until a resolve request +// has been received. The resolve requests it receives will never be completed. +class BlockableHostResolver : public HostResolver { + public: + BlockableHostResolver() + : num_cancelled_requests_(0), waiting_for_resolve_(false) {} + + int Resolve(const RequestInfo& info, + RequestPriority priority, + AddressList* addresses, + const CompletionCallback& callback, + std::unique_ptr<Request>* out_req, + const NetLogWithSource& net_log) override { + EXPECT_FALSE(callback.is_null()); + EXPECT_TRUE(out_req); + + if (!action_.is_null()) + action_.Run(); + + // Indicate to the caller that a request was received. + EXPECT_TRUE(waiting_for_resolve_); + base::RunLoop::QuitCurrentWhenIdleDeprecated(); + + // This line is intentionally after action_.Run(), since one of the + // tests does a cancellation inside of Resolve(), and it is more + // interesting if *out_req hasn't been written yet at that point. + out_req->reset(new RequestImpl(this)); + + // Return ERR_IO_PENDING as this request will NEVER be completed. + // Expectation is for the caller to later cancel the request. + return ERR_IO_PENDING; + } + + int ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const NetLogWithSource& net_log) override { + NOTREACHED(); + return ERR_DNS_CACHE_MISS; + } + + int ResolveStaleFromCache(const RequestInfo& info, + AddressList* addresses, + HostCache::EntryStaleness* stale_info, + const NetLogWithSource& net_log) override { + NOTREACHED(); + return ERR_DNS_CACHE_MISS; + } + + bool HasCached(base::StringPiece hostname, + HostCache::Entry::Source* source_out, + HostCache::EntryStaleness* stale_out) const override { + NOTIMPLEMENTED(); + return false; + } + + void IncreaseNumOfCancelledRequests() { num_cancelled_requests_++; } + + void SetAction(const base::Callback<void(void)>& action) { action_ = action; } + + // Waits until Resolve() has been called. + void WaitUntilRequestIsReceived() { + waiting_for_resolve_ = true; + base::RunLoop().Run(); + DCHECK(waiting_for_resolve_); + waiting_for_resolve_ = false; + } + + int num_cancelled_requests() const { return num_cancelled_requests_; } + + private: + class RequestImpl : public HostResolver::Request { + public: + RequestImpl(BlockableHostResolver* resolver) : resolver_(resolver) {} + + ~RequestImpl() override { + if (resolver_) + resolver_->IncreaseNumOfCancelledRequests(); + } + + void ChangeRequestPriority(RequestPriority priority) override {} + + private: + BlockableHostResolver* resolver_; + + DISALLOW_COPY_AND_ASSIGN(RequestImpl); + }; + + int num_cancelled_requests_; + bool waiting_for_resolve_; + base::Callback<void(void)> action_; +}; + +// This cancellation test exercises a more predictable cancellation codepath -- +// when the request has an outstanding DNS request in flight. +TEST_F(ProxyResolverV8TracingWrapperTest, + CancelWhileOutstandingNonBlockingDns) { + BlockableHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + std::unique_ptr<ProxyResolver> resolver = CreateResolver( + nullptr, &host_resolver, base::WrapUnique(error_observer), "dns.js"); + + ProxyInfo proxy_info1; + ProxyInfo proxy_info2; + std::unique_ptr<ProxyResolver::Request> request1; + std::unique_ptr<ProxyResolver::Request> request2; + + int rv = resolver->GetProxyForURL(GURL("http://foo/req1"), &proxy_info1, + base::Bind(&CrashCallback), &request1, + NetLogWithSource()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + host_resolver.WaitUntilRequestIsReceived(); + + rv = resolver->GetProxyForURL(GURL("http://foo/req2"), &proxy_info2, + base::Bind(&CrashCallback), &request2, + NetLogWithSource()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + host_resolver.WaitUntilRequestIsReceived(); + + request1.reset(); + request2.reset(); + + EXPECT_EQ(2, host_resolver.num_cancelled_requests()); + + // After leaving this scope, the ProxyResolver is destroyed. + // This should not cause any problems, as the outstanding work + // should have been cancelled. +} + +void CancelRequestAndPause(std::unique_ptr<ProxyResolver::Request>* request) { + request->reset(); + + // Sleep for a little bit. This makes it more likely for the worker + // thread to have returned from its call, and serves as a regression + // test for http://crbug.com/173373. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(30)); +} + +// In non-blocking mode, the worker thread actually does block for +// a short time to see if the result is in the DNS cache. Test +// cancellation while the worker thread is waiting on this event. +TEST_F(ProxyResolverV8TracingWrapperTest, CancelWhileBlockedInNonBlockingDns) { + BlockableHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + std::unique_ptr<ProxyResolver> resolver = CreateResolver( + nullptr, &host_resolver, base::WrapUnique(error_observer), "dns.js"); + + ProxyInfo proxy_info; + std::unique_ptr<ProxyResolver::Request> request; + + int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + base::Bind(&CrashCallback), &request, + NetLogWithSource()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + host_resolver.SetAction(base::Bind(CancelRequestAndPause, &request)); + + host_resolver.WaitUntilRequestIsReceived(); +} + +// Cancel the request while there is a pending DNS request, however before +// the request is sent to the host resolver. +TEST_F(ProxyResolverV8TracingWrapperTest, CancelWhileBlockedInNonBlockingDns2) { + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + std::unique_ptr<ProxyResolver> resolver = CreateResolver( + nullptr, &host_resolver, base::WrapUnique(error_observer), "dns.js"); + + ProxyInfo proxy_info; + std::unique_ptr<ProxyResolver::Request> request; + + int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, + base::Bind(&CrashCallback), &request, + NetLogWithSource()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Wait a bit, so the DNS task has hopefully been posted. The test will + // work whatever the delay is here, but it is most useful if the delay + // is large enough to allow a task to be posted back. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + request.reset(); + + EXPECT_EQ(0u, host_resolver.num_resolve()); +} + +TEST_F(ProxyResolverV8TracingWrapperTest, + CancelCreateResolverWhileOutstandingBlockingDns) { + BlockableHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + ProxyResolverFactoryV8TracingWrapper factory( + &host_resolver, nullptr, + base::Bind(&ReturnErrorObserver, + base::Passed(base::WrapUnique(error_observer)))); + + std::unique_ptr<ProxyResolver> resolver; + std::unique_ptr<ProxyResolverFactory::Request> request; + int rv = factory.CreateProxyResolver(LoadScriptData("dns_during_init.js"), + &resolver, base::Bind(&CrashCallback), + &request); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + host_resolver.WaitUntilRequestIsReceived(); + + request.reset(); + EXPECT_EQ(1, host_resolver.num_cancelled_requests()); +} + +TEST_F(ProxyResolverV8TracingWrapperTest, + DeleteFactoryWhileOutstandingBlockingDns) { + BlockableHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + std::unique_ptr<ProxyResolver> resolver; + std::unique_ptr<ProxyResolverFactory::Request> request; + { + ProxyResolverFactoryV8TracingWrapper factory( + &host_resolver, nullptr, + base::Bind(&ReturnErrorObserver, + base::Passed(base::WrapUnique(error_observer)))); + + int rv = factory.CreateProxyResolver(LoadScriptData("dns_during_init.js"), + &resolver, base::Bind(&CrashCallback), + &request); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + host_resolver.WaitUntilRequestIsReceived(); + } + EXPECT_EQ(1, host_resolver.num_cancelled_requests()); +} + +TEST_F(ProxyResolverV8TracingWrapperTest, ErrorLoadingScript) { + BlockableHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + ProxyResolverFactoryV8TracingWrapper factory( + &host_resolver, nullptr, + base::Bind(&ReturnErrorObserver, + base::Passed(base::WrapUnique(error_observer)))); + + std::unique_ptr<ProxyResolver> resolver; + std::unique_ptr<ProxyResolverFactory::Request> request; + TestCompletionCallback callback; + int rv = + factory.CreateProxyResolver(LoadScriptData("error_on_load.js"), &resolver, + callback.callback(), &request); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsError(ERR_PAC_SCRIPT_FAILED)); + EXPECT_FALSE(resolver); +} + +// This tests that the execution of a PAC script is terminated when the DNS +// dependencies are missing. If the test fails, then it will hang. +TEST_F(ProxyResolverV8TracingWrapperTest, Terminate) { + TestNetLog log; + BoundTestNetLog request_log; + MockCachingHostResolver host_resolver; + MockErrorObserver* error_observer = new MockErrorObserver; + + host_resolver.rules()->AddRule("host1", "182.111.0.222"); + host_resolver.rules()->AddRule("host2", "111.33.44.55"); + + std::unique_ptr<ProxyResolver> resolver = CreateResolver( + &log, &host_resolver, base::WrapUnique(error_observer), "terminate.js"); + + TestCompletionCallback callback; + ProxyInfo proxy_info; + + std::unique_ptr<ProxyResolver::Request> req; + int rv = + resolver->GetProxyForURL(GURL("http://foopy/req1"), &proxy_info, + callback.callback(), &req, request_log.bound()); + + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + // The test does 2 DNS resolutions. + EXPECT_EQ(2u, host_resolver.num_resolve()); + + EXPECT_EQ("foopy:3", proxy_info.proxy_server().ToURI()); + + // No errors. + EXPECT_EQ("", error_observer->GetOutput()); + + EXPECT_EQ(0u, log.GetSize()); + EXPECT_EQ(0u, request_log.GetSize()); +} + +// Tests that multiple instances of ProxyResolverV8TracingWrapper can coexist +// and run correctly at the same time. This is relevant because at the moment +// (time this test was written) each ProxyResolverV8TracingWrapper creates its +// own thread to run V8 on, however each thread is operating on the same +// v8::Isolate. +TEST_F(ProxyResolverV8TracingWrapperTest, MultipleResolvers) { + // ------------------------ + // Setup resolver0 + // ------------------------ + MockHostResolver host_resolver0; + host_resolver0.rules()->AddRuleForAddressFamily("host1", ADDRESS_FAMILY_IPV4, + "166.155.144.44"); + host_resolver0.rules()->AddIPLiteralRule("host1", "::1,192.168.1.1", + std::string()); + host_resolver0.rules()->AddSimulatedFailure("host2"); + host_resolver0.rules()->AddRule("host3", "166.155.144.33"); + host_resolver0.rules()->AddRule("host5", "166.155.144.55"); + host_resolver0.rules()->AddSimulatedFailure("host6"); + host_resolver0.rules()->AddRuleForAddressFamily("*", ADDRESS_FAMILY_IPV4, + "122.133.144.155"); + host_resolver0.rules()->AddRule("*", "133.122.100.200"); + std::unique_ptr<ProxyResolver> resolver0 = + CreateResolver(nullptr, &host_resolver0, + std::make_unique<MockErrorObserver>(), "dns.js"); + + // ------------------------ + // Setup resolver1 + // ------------------------ + std::unique_ptr<ProxyResolver> resolver1 = + CreateResolver(nullptr, &host_resolver0, + std::make_unique<MockErrorObserver>(), "dns.js"); + + // ------------------------ + // Setup resolver2 + // ------------------------ + std::unique_ptr<ProxyResolver> resolver2 = + CreateResolver(nullptr, &host_resolver0, + std::make_unique<MockErrorObserver>(), "simple.js"); + + // ------------------------ + // Setup resolver3 + // ------------------------ + MockHostResolver host_resolver3; + host_resolver3.rules()->AddRule("foo", "166.155.144.33"); + std::unique_ptr<ProxyResolver> resolver3 = + CreateResolver(nullptr, &host_resolver3, + std::make_unique<MockErrorObserver>(), "simple_dns.js"); + + // ------------------------ + // Queue up work for each resolver (which will be running in parallel). + // ------------------------ + + ProxyResolver* resolver[] = { + resolver0.get(), resolver1.get(), resolver2.get(), resolver3.get(), + }; + + const size_t kNumResolvers = arraysize(resolver); + const size_t kNumIterations = 20; + const size_t kNumResults = kNumResolvers * kNumIterations; + TestCompletionCallback callback[kNumResults]; + ProxyInfo proxy_info[kNumResults]; + std::unique_ptr<ProxyResolver::Request> request[kNumResults]; + + for (size_t i = 0; i < kNumResults; ++i) { + size_t resolver_i = i % kNumResolvers; + int rv = resolver[resolver_i]->GetProxyForURL( + GURL("http://foo/"), &proxy_info[i], callback[i].callback(), + &request[i], NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + } + + // ------------------------ + // Verify all of the results. + // ------------------------ + + const char* kExpectedForDnsJs = + "122.133.144.155-" // myIpAddress() + "null-" // dnsResolve('') + "__1_192.168.1.1-" // dnsResolveEx('host1') + "null-" // dnsResolve('host2') + "166.155.144.33-" // dnsResolve('host3') + "122.133.144.155-" // myIpAddress() + "166.155.144.33-" // dnsResolve('host3') + "__1_192.168.1.1-" // dnsResolveEx('host1') + "122.133.144.155-" // myIpAddress() + "null-" // dnsResolve('host2') + "-" // dnsResolveEx('host6') + "133.122.100.200-" // myIpAddressEx() + "166.155.144.44" // dnsResolve('host1') + ":99"; + + for (size_t i = 0; i < kNumResults; ++i) { + size_t resolver_i = i % kNumResolvers; + EXPECT_THAT(callback[i].WaitForResult(), IsOk()); + + std::string proxy_uri = proxy_info[i].proxy_server().ToURI(); + + if (resolver_i == 0 || resolver_i == 1) { + EXPECT_EQ(kExpectedForDnsJs, proxy_uri); + } else if (resolver_i == 2) { + EXPECT_EQ("foo:99", proxy_uri); + } else if (resolver_i == 3) { + EXPECT_EQ("166.155.144.33:", + proxy_uri.substr(0, proxy_uri.find(':') + 1)); + } else { + NOTREACHED(); + } + } +} + +} // namespace + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_resolver_v8_unittest.cc b/chromium/net/proxy_resolution/proxy_resolver_v8_unittest.cc new file mode 100644 index 00000000000..2acc464a2b9 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_v8_unittest.cc @@ -0,0 +1,544 @@ +// Copyright (c) 2012 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_resolution/proxy_resolver_v8.h" +#include "base/compiler_specific.h" +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/completion_callback.h" +#include "net/base/net_errors.h" +#include "net/proxy_resolution/pac_file_data.h" +#include "net/proxy_resolution/proxy_info.h" +#include "net/test/gtest_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using net::test::IsError; +using net::test::IsOk; + +namespace net { +namespace { + +// Javascript bindings for ProxyResolverV8, which returns mock values. +// Each time one of the bindings is called into, we push the input into a +// list, for later verification. +class MockJSBindings : public ProxyResolverV8::JSBindings { + public: + MockJSBindings() + : my_ip_address_count(0), + my_ip_address_ex_count(0), + should_terminate(false) {} + + void Alert(const base::string16& message) override { + VLOG(1) << "PAC-alert: " << message; // Helpful when debugging. + alerts.push_back(base::UTF16ToUTF8(message)); + } + + bool ResolveDns(const std::string& host, + ResolveDnsOperation op, + std::string* output, + bool* terminate) override { + *terminate = should_terminate; + + if (op == MY_IP_ADDRESS) { + my_ip_address_count++; + *output = my_ip_address_result; + return !my_ip_address_result.empty(); + } + + if (op == MY_IP_ADDRESS_EX) { + my_ip_address_ex_count++; + *output = my_ip_address_ex_result; + return !my_ip_address_ex_result.empty(); + } + + if (op == DNS_RESOLVE) { + dns_resolves.push_back(host); + *output = dns_resolve_result; + return !dns_resolve_result.empty(); + } + + if (op == DNS_RESOLVE_EX) { + dns_resolves_ex.push_back(host); + *output = dns_resolve_ex_result; + return !dns_resolve_ex_result.empty(); + } + + CHECK(false); + return false; + } + + void OnError(int line_number, const base::string16& message) override { + // Helpful when debugging. + VLOG(1) << "PAC-error: [" << line_number << "] " << message; + + errors.push_back(base::UTF16ToUTF8(message)); + errors_line_number.push_back(line_number); + } + + // Mock values to return. + std::string my_ip_address_result; + std::string my_ip_address_ex_result; + std::string dns_resolve_result; + std::string dns_resolve_ex_result; + + // Inputs we got called with. + std::vector<std::string> alerts; + std::vector<std::string> errors; + std::vector<int> errors_line_number; + std::vector<std::string> dns_resolves; + std::vector<std::string> dns_resolves_ex; + int my_ip_address_count; + int my_ip_address_ex_count; + + // Whether ResolveDns() should terminate script execution. + bool should_terminate; +}; + +class ProxyResolverV8Test : public testing::Test { + public: + // Creates a ProxyResolverV8 using the PAC script contained in |filename|. If + // called more than once, the previous ProxyResolverV8 is deleted. + int CreateResolver(const char* filename) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.AppendASCII("net"); + path = path.AppendASCII("data"); + path = path.AppendASCII("proxy_resolver_v8_unittest"); + path = path.AppendASCII(filename); + + // Try to read the file from disk. + std::string file_contents; + bool ok = base::ReadFileToString(path, &file_contents); + + // If we can't load the file from disk, something is misconfigured. + if (!ok) { + LOG(ERROR) << "Failed to read file: " << path.value(); + return ERR_FAILED; + } + + // Create the ProxyResolver using the PAC script. + return ProxyResolverV8::Create( + ProxyResolverScriptData::FromUTF8(file_contents), bindings(), + &resolver_); + } + + ProxyResolverV8& resolver() { + DCHECK(resolver_); + return *resolver_; + } + + MockJSBindings* bindings() { return &js_bindings_; } + + private: + MockJSBindings js_bindings_; + std::unique_ptr<ProxyResolverV8> resolver_; +}; + +// Doesn't really matter what these values are for many of the tests. +const GURL kQueryUrl("http://www.google.com"); +const GURL kPacUrl; + +TEST_F(ProxyResolverV8Test, Direct) { + ASSERT_THAT(CreateResolver("direct.js"), IsOk()); + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + EXPECT_THAT(result, IsOk()); + EXPECT_TRUE(proxy_info.is_direct()); + + EXPECT_EQ(0U, bindings()->alerts.size()); + EXPECT_EQ(0U, bindings()->errors.size()); +} + +TEST_F(ProxyResolverV8Test, ReturnEmptyString) { + ASSERT_THAT(CreateResolver("return_empty_string.js"), IsOk()); + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + EXPECT_THAT(result, IsOk()); + EXPECT_TRUE(proxy_info.is_direct()); + + EXPECT_EQ(0U, bindings()->alerts.size()); + EXPECT_EQ(0U, bindings()->errors.size()); +} + +TEST_F(ProxyResolverV8Test, Basic) { + ASSERT_THAT(CreateResolver("passthrough.js"), IsOk()); + + // The "FindProxyForURL" of this PAC script simply concatenates all of the + // arguments into a pseudo-host. The purpose of this test is to verify that + // the correct arguments are being passed to FindProxyForURL(). + { + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(GURL("http://query.com/path"), + &proxy_info, bindings()); + EXPECT_THAT(result, IsOk()); + EXPECT_EQ("http.query.com.path.query.com:80", + proxy_info.proxy_server().ToURI()); + } + { + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(GURL("ftp://query.com:90/path"), + &proxy_info, bindings()); + EXPECT_THAT(result, IsOk()); + // Note that FindProxyForURL(url, host) does not expect |host| to contain + // the port number. + EXPECT_EQ("ftp.query.com.90.path.query.com:80", + proxy_info.proxy_server().ToURI()); + + EXPECT_EQ(0U, bindings()->alerts.size()); + EXPECT_EQ(0U, bindings()->errors.size()); + } +} + +TEST_F(ProxyResolverV8Test, BadReturnType) { + // These are the filenames of PAC scripts which each return a non-string + // types for FindProxyForURL(). They should all fail with + // ERR_PAC_SCRIPT_FAILED. + static const char* const filenames[] = { + "return_undefined.js", + "return_integer.js", + "return_function.js", + "return_object.js", + // TODO(eroman): Should 'null' be considered equivalent to "DIRECT" ? + "return_null.js"}; + + for (size_t i = 0; i < arraysize(filenames); ++i) { + ASSERT_THAT(CreateResolver(filenames[i]), IsOk()); + + MockJSBindings bindings; + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, &bindings); + + EXPECT_THAT(result, IsError(ERR_PAC_SCRIPT_FAILED)); + + EXPECT_EQ(0U, bindings.alerts.size()); + ASSERT_EQ(1U, bindings.errors.size()); + EXPECT_EQ("FindProxyForURL() did not return a string.", bindings.errors[0]); + EXPECT_EQ(-1, bindings.errors_line_number[0]); + } +} + +// Try using a PAC script which defines no "FindProxyForURL" function. +TEST_F(ProxyResolverV8Test, NoEntryPoint) { + EXPECT_THAT(CreateResolver("no_entrypoint.js"), + IsError(ERR_PAC_SCRIPT_FAILED)); + + ASSERT_EQ(1U, bindings()->errors.size()); + EXPECT_EQ("FindProxyForURL is undefined or not a function.", + bindings()->errors[0]); + EXPECT_EQ(-1, bindings()->errors_line_number[0]); +} + +// Try loading a malformed PAC script. +TEST_F(ProxyResolverV8Test, ParseError) { + EXPECT_THAT(CreateResolver("missing_close_brace.js"), + IsError(ERR_PAC_SCRIPT_FAILED)); + + EXPECT_EQ(0U, bindings()->alerts.size()); + + // We get one error during compilation. + ASSERT_EQ(1U, bindings()->errors.size()); + + EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input", + bindings()->errors[0]); + EXPECT_EQ(5, bindings()->errors_line_number[0]); +} + +// Run a PAC script several times, which has side-effects. +TEST_F(ProxyResolverV8Test, SideEffects) { + ASSERT_THAT(CreateResolver("side_effects.js"), IsOk()); + + // The PAC script increments a counter each time we invoke it. + for (int i = 0; i < 3; ++i) { + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + EXPECT_THAT(result, IsOk()); + EXPECT_EQ(base::StringPrintf("sideffect_%d:80", i), + proxy_info.proxy_server().ToURI()); + } + + // Reload the script -- the javascript environment should be reset, hence + // the counter starts over. + ASSERT_THAT(CreateResolver("side_effects.js"), IsOk()); + + for (int i = 0; i < 3; ++i) { + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + EXPECT_THAT(result, IsOk()); + EXPECT_EQ(base::StringPrintf("sideffect_%d:80", i), + proxy_info.proxy_server().ToURI()); + } +} + +// Execute a PAC script which throws an exception in FindProxyForURL. +TEST_F(ProxyResolverV8Test, UnhandledException) { + ASSERT_THAT(CreateResolver("unhandled_exception.js"), IsOk()); + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + EXPECT_THAT(result, IsError(ERR_PAC_SCRIPT_FAILED)); + + EXPECT_EQ(0U, bindings()->alerts.size()); + ASSERT_EQ(1U, bindings()->errors.size()); + EXPECT_EQ("Uncaught ReferenceError: undefined_variable is not defined", + bindings()->errors[0]); + EXPECT_EQ(3, bindings()->errors_line_number[0]); +} + +// Execute a PAC script which throws an exception when first accessing +// FindProxyForURL +TEST_F(ProxyResolverV8Test, ExceptionAccessingFindProxyForURLDuringInit) { + EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, + CreateResolver("exception_findproxyforurl_during_init.js")); + + ASSERT_EQ(2U, bindings()->errors.size()); + EXPECT_EQ("Uncaught crash!", bindings()->errors[0]); + EXPECT_EQ(9, bindings()->errors_line_number[0]); + EXPECT_EQ("Accessing FindProxyForURL threw an exception.", + bindings()->errors[1]); + EXPECT_EQ(-1, bindings()->errors_line_number[1]); +} + +// Execute a PAC script which throws an exception during the second access to +// FindProxyForURL +TEST_F(ProxyResolverV8Test, ExceptionAccessingFindProxyForURLDuringResolve) { + ASSERT_THAT(CreateResolver("exception_findproxyforurl_during_resolve.js"), + IsOk()); + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + EXPECT_THAT(result, IsError(ERR_PAC_SCRIPT_FAILED)); + + ASSERT_EQ(2U, bindings()->errors.size()); + EXPECT_EQ("Uncaught crash!", bindings()->errors[0]); + EXPECT_EQ(17, bindings()->errors_line_number[0]); + EXPECT_EQ("Accessing FindProxyForURL threw an exception.", + bindings()->errors[1]); + EXPECT_EQ(-1, bindings()->errors_line_number[1]); +} + +TEST_F(ProxyResolverV8Test, ReturnUnicode) { + ASSERT_THAT(CreateResolver("return_unicode.js"), IsOk()); + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + // The result from this resolve was unparseable, because it + // wasn't ASCII. + EXPECT_THAT(result, IsError(ERR_PAC_SCRIPT_FAILED)); +} + +// Test the PAC library functions that we expose in the JS environment. +TEST_F(ProxyResolverV8Test, JavascriptLibrary) { + ASSERT_THAT(CreateResolver("pac_library_unittest.js"), IsOk()); + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + // If the javascript side of this unit-test fails, it will throw a javascript + // exception. Otherwise it will return "PROXY success:80". + EXPECT_THAT(result, IsOk()); + EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI()); + + EXPECT_EQ(0U, bindings()->alerts.size()); + EXPECT_EQ(0U, bindings()->errors.size()); +} + +// Test marshalling/un-marshalling of values between C++/V8. +TEST_F(ProxyResolverV8Test, V8Bindings) { + ASSERT_THAT(CreateResolver("bindings.js"), IsOk()); + bindings()->dns_resolve_result = "127.0.0.1"; + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + EXPECT_THAT(result, IsOk()); + EXPECT_TRUE(proxy_info.is_direct()); + + EXPECT_EQ(0U, bindings()->errors.size()); + + // Alert was called 5 times. + ASSERT_EQ(5U, bindings()->alerts.size()); + EXPECT_EQ("undefined", bindings()->alerts[0]); + EXPECT_EQ("null", bindings()->alerts[1]); + EXPECT_EQ("undefined", bindings()->alerts[2]); + EXPECT_EQ("[object Object]", bindings()->alerts[3]); + EXPECT_EQ("exception from calling toString()", bindings()->alerts[4]); + + // DnsResolve was called 8 times, however only 2 of those were string + // parameters. (so 6 of them failed immediately). + ASSERT_EQ(2U, bindings()->dns_resolves.size()); + EXPECT_EQ("", bindings()->dns_resolves[0]); + EXPECT_EQ("arg1", bindings()->dns_resolves[1]); + + // MyIpAddress was called two times. + EXPECT_EQ(2, bindings()->my_ip_address_count); + + // MyIpAddressEx was called once. + EXPECT_EQ(1, bindings()->my_ip_address_ex_count); + + // DnsResolveEx was called 2 times. + ASSERT_EQ(2U, bindings()->dns_resolves_ex.size()); + EXPECT_EQ("is_resolvable", bindings()->dns_resolves_ex[0]); + EXPECT_EQ("foobar", bindings()->dns_resolves_ex[1]); +} + +// Test calling a binding (myIpAddress()) from the script's global scope. +// http://crbug.com/40026 +TEST_F(ProxyResolverV8Test, BindingCalledDuringInitialization) { + ASSERT_THAT(CreateResolver("binding_from_global.js"), IsOk()); + + // myIpAddress() got called during initialization of the script. + EXPECT_EQ(1, bindings()->my_ip_address_count); + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + EXPECT_THAT(result, IsOk()); + EXPECT_FALSE(proxy_info.is_direct()); + EXPECT_EQ("127.0.0.1:80", proxy_info.proxy_server().ToURI()); + + // Check that no other bindings were called. + EXPECT_EQ(0U, bindings()->errors.size()); + ASSERT_EQ(0U, bindings()->alerts.size()); + ASSERT_EQ(0U, bindings()->dns_resolves.size()); + EXPECT_EQ(0, bindings()->my_ip_address_ex_count); + ASSERT_EQ(0U, bindings()->dns_resolves_ex.size()); +} + +// Try loading a PAC script that ends with a comment and has no terminal +// newline. This should not cause problems with the PAC utility functions +// that we add to the script's environment. +// http://crbug.com/22864 +TEST_F(ProxyResolverV8Test, EndsWithCommentNoNewline) { + ASSERT_THAT(CreateResolver("ends_with_comment.js"), IsOk()); + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + EXPECT_THAT(result, IsOk()); + EXPECT_FALSE(proxy_info.is_direct()); + EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI()); +} + +// Try loading a PAC script that ends with a statement and has no terminal +// newline. This should not cause problems with the PAC utility functions +// that we add to the script's environment. +// http://crbug.com/22864 +TEST_F(ProxyResolverV8Test, EndsWithStatementNoNewline) { + ASSERT_THAT(CreateResolver("ends_with_statement_no_semicolon.js"), IsOk()); + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + EXPECT_THAT(result, IsOk()); + EXPECT_FALSE(proxy_info.is_direct()); + EXPECT_EQ("success:3", proxy_info.proxy_server().ToURI()); +} + +// Test the return values from myIpAddress(), myIpAddressEx(), dnsResolve(), +// dnsResolveEx(), isResolvable(), isResolvableEx(), when the the binding +// returns empty string (failure). This simulates the return values from +// those functions when the underlying DNS resolution fails. +TEST_F(ProxyResolverV8Test, DNSResolutionFailure) { + ASSERT_THAT(CreateResolver("dns_fail.js"), IsOk()); + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + EXPECT_THAT(result, IsOk()); + EXPECT_FALSE(proxy_info.is_direct()); + EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI()); +} + +TEST_F(ProxyResolverV8Test, DNSResolutionOfInternationDomainName) { + ASSERT_THAT(CreateResolver("international_domain_names.js"), IsOk()); + + // Execute FindProxyForURL(). + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL(kQueryUrl, &proxy_info, bindings()); + + EXPECT_THAT(result, IsOk()); + EXPECT_TRUE(proxy_info.is_direct()); + + // Check that the international domain name was converted to punycode + // before passing it onto the bindings layer. + ASSERT_EQ(1u, bindings()->dns_resolves.size()); + EXPECT_EQ("xn--bcher-kva.ch", bindings()->dns_resolves[0]); + + ASSERT_EQ(1u, bindings()->dns_resolves_ex.size()); + EXPECT_EQ("xn--bcher-kva.ch", bindings()->dns_resolves_ex[0]); +} + +// Test that when resolving a URL which contains an IPv6 string literal, the +// brackets are removed from the host before passing it down to the PAC script. +// If we don't do this, then subsequent calls to dnsResolveEx(host) will be +// doomed to fail since it won't correspond with a valid name. +TEST_F(ProxyResolverV8Test, IPv6HostnamesNotBracketed) { + ASSERT_THAT(CreateResolver("resolve_host.js"), IsOk()); + + ProxyInfo proxy_info; + int result = resolver().GetProxyForURL( + GURL("http://[abcd::efff]:99/watsupdawg"), &proxy_info, bindings()); + + EXPECT_THAT(result, IsOk()); + EXPECT_TRUE(proxy_info.is_direct()); + + // We called dnsResolveEx() exactly once, by passing through the "host" + // argument to FindProxyForURL(). The brackets should have been stripped. + ASSERT_EQ(1U, bindings()->dns_resolves_ex.size()); + EXPECT_EQ("abcd::efff", bindings()->dns_resolves_ex[0]); +} + +// Test that terminating a script within DnsResolve() leads to eventual +// termination of the script. Also test that repeatedly calling terminate is +// safe, and running the script again after termination still works. +TEST_F(ProxyResolverV8Test, Terminate) { + ASSERT_THAT(CreateResolver("terminate.js"), IsOk()); + + // Terminate script execution upon reaching dnsResolve(). Note that + // termination may not take effect right away (so the subsequent dnsResolve() + // and alert() may be run). + bindings()->should_terminate = true; + + ProxyInfo proxy_info; + int result = + resolver().GetProxyForURL(GURL("http://hang/"), &proxy_info, bindings()); + + // The script execution was terminated. + EXPECT_THAT(result, IsError(ERR_PAC_SCRIPT_FAILED)); + + EXPECT_EQ(1U, bindings()->dns_resolves.size()); + EXPECT_GE(2U, bindings()->dns_resolves_ex.size()); + EXPECT_GE(1U, bindings()->alerts.size()); + + EXPECT_EQ(1U, bindings()->errors.size()); + + // Termination shows up as an uncaught exception without any message. + EXPECT_EQ("", bindings()->errors[0]); + + bindings()->errors.clear(); + + // Try running the script again, this time with a different input which won't + // cause a termination+hang. + result = resolver().GetProxyForURL(GURL("http://kittens/"), &proxy_info, + bindings()); + + EXPECT_THAT(result, IsOk()); + EXPECT_EQ(0u, bindings()->errors.size()); + EXPECT_EQ("kittens:88", proxy_info.proxy_server().ToURI()); +} + +} // namespace +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_resolver_winhttp.cc b/chromium/net/proxy_resolution/proxy_resolver_winhttp.cc new file mode 100644 index 00000000000..ebe5fb8dca2 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_winhttp.cc @@ -0,0 +1,215 @@ +// 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_resolution/proxy_resolver_winhttp.h" + +#include <windows.h> +#include <winhttp.h> + +#include "base/macros.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/net_errors.h" +#include "net/proxy_resolution/proxy_info.h" +#include "net/proxy_resolution/proxy_resolver.h" +#include "url/gurl.h" + +using base::TimeDelta; +using base::TimeTicks; + +namespace net { +namespace { + +static void FreeInfo(WINHTTP_PROXY_INFO* info) { + if (info->lpszProxy) + GlobalFree(info->lpszProxy); + if (info->lpszProxyBypass) + GlobalFree(info->lpszProxyBypass); +} + +static Error WinHttpErrorToNetError(DWORD win_http_error) { + switch (win_http_error) { + case ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR: + case ERROR_WINHTTP_INTERNAL_ERROR: + case ERROR_WINHTTP_INCORRECT_HANDLE_TYPE: + return ERR_FAILED; + case ERROR_WINHTTP_LOGIN_FAILURE: + return ERR_PROXY_AUTH_UNSUPPORTED; + case ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT: + return ERR_PAC_SCRIPT_FAILED; + case ERROR_WINHTTP_INVALID_URL: + case ERROR_WINHTTP_OPERATION_CANCELLED: + case ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT: + case ERROR_WINHTTP_UNRECOGNIZED_SCHEME: + return ERR_PAC_STATUS_NOT_OK; + case ERROR_NOT_ENOUGH_MEMORY: + return ERR_INSUFFICIENT_RESOURCES; + default: + return ERR_FAILED; + } +} + +class ProxyResolverWinHttp : public ProxyResolver { + public: + ProxyResolverWinHttp( + const scoped_refptr<ProxyResolverScriptData>& script_data); + ~ProxyResolverWinHttp() override; + + // ProxyResolver implementation: + int GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& /*callback*/, + std::unique_ptr<Request>* /*request*/, + const NetLogWithSource& /*net_log*/) override; + + private: + bool OpenWinHttpSession(); + void CloseWinHttpSession(); + + // Proxy configuration is cached on the session handle. + HINTERNET session_handle_; + + const GURL pac_url_; + + DISALLOW_COPY_AND_ASSIGN(ProxyResolverWinHttp); +}; + +ProxyResolverWinHttp::ProxyResolverWinHttp( + const scoped_refptr<ProxyResolverScriptData>& script_data) + : session_handle_(NULL), + pac_url_(script_data->type() == ProxyResolverScriptData::TYPE_AUTO_DETECT + ? GURL("http://wpad/wpad.dat") + : script_data->url()) { +} + +ProxyResolverWinHttp::~ProxyResolverWinHttp() { + CloseWinHttpSession(); +} + +int ProxyResolverWinHttp::GetProxyForURL(const GURL& query_url, + ProxyInfo* results, + const CompletionCallback& /*callback*/, + std::unique_ptr<Request>* /*request*/, + const NetLogWithSource& /*net_log*/) { + // If we don't have a WinHTTP session, then create a new one. + if (!session_handle_ && !OpenWinHttpSession()) + return ERR_FAILED; + + // If we have been given an empty PAC url, then use auto-detection. + // + // NOTE: We just use DNS-based auto-detection here like Firefox. We do this + // to avoid WinHTTP's auto-detection code, which while more featureful (it + // supports DHCP based auto-detection) also appears to have issues. + // + WINHTTP_AUTOPROXY_OPTIONS options = {0}; + options.fAutoLogonIfChallenged = FALSE; + options.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; + base::string16 pac_url16 = base::ASCIIToUTF16(pac_url_.spec()); + options.lpszAutoConfigUrl = pac_url16.c_str(); + + WINHTTP_PROXY_INFO info = {0}; + DCHECK(session_handle_); + + // Per http://msdn.microsoft.com/en-us/library/aa383153(VS.85).aspx, it is + // necessary to first try resolving with fAutoLogonIfChallenged set to false. + // Otherwise, we fail over to trying it with a value of true. This way we + // get good performance in the case where WinHTTP uses an out-of-process + // resolver. This is important for Vista and Win2k3. + BOOL ok = WinHttpGetProxyForUrl(session_handle_, + base::ASCIIToUTF16(query_url.spec()).c_str(), + &options, &info); + if (!ok) { + if (ERROR_WINHTTP_LOGIN_FAILURE == GetLastError()) { + options.fAutoLogonIfChallenged = TRUE; + ok = WinHttpGetProxyForUrl( + session_handle_, base::ASCIIToUTF16(query_url.spec()).c_str(), + &options, &info); + } + if (!ok) { + DWORD error = GetLastError(); + // If we got here because of RPC timeout during out of process PAC + // resolution, no further requests on this session are going to work. + if (ERROR_WINHTTP_TIMEOUT == error || + ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR == error) { + CloseWinHttpSession(); + } + return WinHttpErrorToNetError(error); + } + } + + int rv = OK; + + switch (info.dwAccessType) { + case WINHTTP_ACCESS_TYPE_NO_PROXY: + results->UseDirect(); + break; + case WINHTTP_ACCESS_TYPE_NAMED_PROXY: + // According to MSDN: + // + // The proxy server list contains one or more of the following strings + // separated by semicolons or whitespace. + // + // ([<scheme>=][<scheme>"://"]<server>[":"<port>]) + // + // Based on this description, ProxyInfo::UseNamedProxy() isn't + // going to handle all the variations (in particular <scheme>=). + // + // However in practice, it seems that WinHTTP is simply returning + // things like "foopy1:80;foopy2:80". It strips out the non-HTTP + // proxy types, and stops the list when PAC encounters a "DIRECT". + // So UseNamedProxy() should work OK. + results->UseNamedProxy(base::UTF16ToASCII(info.lpszProxy)); + break; + default: + NOTREACHED(); + rv = ERR_FAILED; + } + + FreeInfo(&info); + return rv; +} + +bool ProxyResolverWinHttp::OpenWinHttpSession() { + DCHECK(!session_handle_); + session_handle_ = WinHttpOpen(NULL, + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + if (!session_handle_) + return false; + + // Since this session handle will never be used for WinHTTP connections, + // these timeouts don't really mean much individually. However, WinHTTP's + // out of process PAC resolution will use a combined (sum of all timeouts) + // value to wait for an RPC reply. + BOOL rv = WinHttpSetTimeouts(session_handle_, 10000, 10000, 5000, 5000); + DCHECK(rv); + + return true; +} + +void ProxyResolverWinHttp::CloseWinHttpSession() { + if (session_handle_) { + WinHttpCloseHandle(session_handle_); + session_handle_ = NULL; + } +} + +} // namespace + +ProxyResolverFactoryWinHttp::ProxyResolverFactoryWinHttp() + : ProxyResolverFactory(false /*expects_pac_bytes*/) { +} + +int ProxyResolverFactoryWinHttp::CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback, + std::unique_ptr<Request>* request) { + resolver->reset(new ProxyResolverWinHttp(pac_script)); + return OK; +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_resolver_winhttp.h b/chromium/net/proxy_resolution/proxy_resolver_winhttp.h new file mode 100644 index 00000000000..663e76ec330 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_resolver_winhttp.h @@ -0,0 +1,35 @@ +// 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_RESOLUTION_PROXY_RESOLVER_WINHTTP_H_ +#define NET_PROXY_RESOLUTION_PROXY_RESOLVER_WINHTTP_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "net/base/net_export.h" +#include "net/proxy_resolution/proxy_resolver_factory.h" +#include "url/gurl.h" + +namespace net { + +// An implementation of ProxyResolverFactory that uses WinHTTP and the system +// proxy settings. +class NET_EXPORT_PRIVATE ProxyResolverFactoryWinHttp + : public ProxyResolverFactory { + public: + ProxyResolverFactoryWinHttp(); + + int CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const CompletionCallback& callback, + std::unique_ptr<Request>* request) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ProxyResolverFactoryWinHttp); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_RESOLVER_WINHTTP_H_ diff --git a/chromium/net/proxy_resolution/proxy_retry_info.h b/chromium/net/proxy_resolution/proxy_retry_info.h new file mode 100644 index 00000000000..23d5d75e306 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_retry_info.h @@ -0,0 +1,40 @@ +// Copyright (c) 2006-2008 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_RESOLUTION_PROXY_RETRY_INFO_H_ +#define NET_PROXY_RESOLUTION_PROXY_RETRY_INFO_H_ + +#include <map> + +#include "base/time/time.h" + +namespace net { + +// Contains the information about when to retry a proxy server. +struct ProxyRetryInfo { + ProxyRetryInfo() : try_while_bad(true), net_error(0) {} + + // We should not retry until this time. + base::TimeTicks bad_until; + + // This is the current delay. If the proxy is still bad, we need to increase + // this delay. + base::TimeDelta current_delay; + + // True if this proxy should be considered even if still bad. + bool try_while_bad; + + // The network error received when this proxy failed, or |OK| if the proxy + // was added to the retry list for a non-network related reason. (e.g. local + // policy). + int net_error; +}; + +// Map of proxy servers with the associated RetryInfo structures. +// The key is a proxy URI string [<scheme>"://"]<host>":"<port>. +typedef std::map<std::string, ProxyRetryInfo> ProxyRetryInfoMap; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_RETRY_INFO_H_ diff --git a/chromium/net/proxy_resolution/proxy_server_unittest.cc b/chromium/net/proxy_resolution/proxy_server_unittest.cc new file mode 100644 index 00000000000..67fadd6d07a --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_server_unittest.cc @@ -0,0 +1,338 @@ +// Copyright (c) 2010 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 "base/macros.h" +#include "net/base/proxy_server.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +// Test the creation of ProxyServer using ProxyServer::FromURI, which parses +// inputs of the form [<scheme>"://"]<host>[":"<port>]. Verify that each part +// was labelled correctly, and the accessors all give the right data. +TEST(ProxyServerTest, FromURI) { + const struct { + const char* const input_uri; + const char* const expected_uri; + ProxyServer::Scheme expected_scheme; + const char* const expected_host; + int expected_port; + const char* const expected_pac_string; + } tests[] = { + // HTTP proxy URIs: + {"foopy:10", // No scheme. + "foopy:10", + ProxyServer::SCHEME_HTTP, + "foopy", + 10, + "PROXY foopy:10"}, + {"http://foopy", // No port. + "foopy:80", + ProxyServer::SCHEME_HTTP, + "foopy", + 80, + "PROXY foopy:80"}, + {"http://foopy:10", + "foopy:10", + ProxyServer::SCHEME_HTTP, + "foopy", + 10, + "PROXY foopy:10"}, + + // IPv6 HTTP proxy URIs: + {"[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:10", // No scheme. + "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:10", + ProxyServer::SCHEME_HTTP, + "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", + 10, + "PROXY [FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:10"}, + {"http://[3ffe:2a00:100:7031::1]", // No port. + "[3ffe:2a00:100:7031::1]:80", + ProxyServer::SCHEME_HTTP, + "3ffe:2a00:100:7031::1", + 80, + "PROXY [3ffe:2a00:100:7031::1]:80"}, + {"http://[::192.9.5.5]", + "[::192.9.5.5]:80", + ProxyServer::SCHEME_HTTP, + "::192.9.5.5", + 80, + "PROXY [::192.9.5.5]:80"}, + {"http://[::FFFF:129.144.52.38]:80", + "[::FFFF:129.144.52.38]:80", + ProxyServer::SCHEME_HTTP, + "::FFFF:129.144.52.38", + 80, + "PROXY [::FFFF:129.144.52.38]:80"}, + + // SOCKS4 proxy URIs: + {"socks4://foopy", // No port. + "socks4://foopy:1080", + ProxyServer::SCHEME_SOCKS4, + "foopy", + 1080, + "SOCKS foopy:1080"}, + {"socks4://foopy:10", + "socks4://foopy:10", + ProxyServer::SCHEME_SOCKS4, + "foopy", + 10, + "SOCKS foopy:10"}, + + // SOCKS5 proxy URIs + {"socks5://foopy", // No port. + "socks5://foopy:1080", + ProxyServer::SCHEME_SOCKS5, + "foopy", + 1080, + "SOCKS5 foopy:1080"}, + {"socks5://foopy:10", + "socks5://foopy:10", + ProxyServer::SCHEME_SOCKS5, + "foopy", + 10, + "SOCKS5 foopy:10"}, + + // SOCKS proxy URIs (should default to SOCKS5) + {"socks://foopy", // No port. + "socks5://foopy:1080", + ProxyServer::SCHEME_SOCKS5, + "foopy", + 1080, + "SOCKS5 foopy:1080"}, + {"socks://foopy:10", + "socks5://foopy:10", + ProxyServer::SCHEME_SOCKS5, + "foopy", + 10, + "SOCKS5 foopy:10"}, + + // HTTPS proxy URIs: + {"https://foopy", // No port + "https://foopy:443", + ProxyServer::SCHEME_HTTPS, + "foopy", + 443, + "HTTPS foopy:443"}, + {"https://foopy:10", // Non-standard port + "https://foopy:10", + ProxyServer::SCHEME_HTTPS, + "foopy", + 10, + "HTTPS foopy:10"}, + {"https://1.2.3.4:10", // IP Address + "https://1.2.3.4:10", + ProxyServer::SCHEME_HTTPS, + "1.2.3.4", + 10, + "HTTPS 1.2.3.4:10"}, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + ProxyServer uri = + ProxyServer::FromURI(tests[i].input_uri, ProxyServer::SCHEME_HTTP); + EXPECT_TRUE(uri.is_valid()); + EXPECT_FALSE(uri.is_direct()); + EXPECT_EQ(tests[i].expected_uri, uri.ToURI()); + EXPECT_EQ(tests[i].expected_scheme, uri.scheme()); + EXPECT_EQ(tests[i].expected_host, uri.host_port_pair().host()); + EXPECT_EQ(tests[i].expected_port, uri.host_port_pair().port()); + EXPECT_EQ(tests[i].expected_pac_string, uri.ToPacString()); + } +} + +TEST(ProxyServerTest, DefaultConstructor) { + ProxyServer proxy_server; + EXPECT_FALSE(proxy_server.is_valid()); +} + +// Test parsing of the special URI form "direct://". Analagous to the "DIRECT" +// entry in a PAC result. +TEST(ProxyServerTest, Direct) { + ProxyServer uri = ProxyServer::FromURI("direct://", ProxyServer::SCHEME_HTTP); + EXPECT_TRUE(uri.is_valid()); + EXPECT_TRUE(uri.is_direct()); + EXPECT_EQ("direct://", uri.ToURI()); + EXPECT_EQ("DIRECT", uri.ToPacString()); +} + +// Test parsing some invalid inputs. +TEST(ProxyServerTest, Invalid) { + const char* const tests[] = { + "", + " ", + "dddf:", // not a valid port + "dddd:d", // not a valid port + "http://", // not a valid host/port. + "direct://xyz", // direct is not allowed a host/port. + "http:/", // ambiguous, but will fail because of bad port. + "http:", // ambiguous, but will fail because of bad port. + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + ProxyServer uri = ProxyServer::FromURI(tests[i], ProxyServer::SCHEME_HTTP); + EXPECT_FALSE(uri.is_valid()); + EXPECT_FALSE(uri.is_direct()); + EXPECT_FALSE(uri.is_http()); + EXPECT_FALSE(uri.is_socks()); + } +} + +// Test that LWS (SP | HT) is disregarded from the ends. +TEST(ProxyServerTest, Whitespace) { + const char* const tests[] = { + " foopy:80", + "foopy:80 \t", + " \tfoopy:80 ", + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + ProxyServer uri = ProxyServer::FromURI(tests[i], ProxyServer::SCHEME_HTTP); + EXPECT_EQ("foopy:80", uri.ToURI()); + } +} + +// Test parsing a ProxyServer from a PAC representation. +TEST(ProxyServerTest, FromPACString) { + const struct { + const char* const input_pac; + const char* const expected_uri; + } tests[] = { + { + "PROXY foopy:10", + "foopy:10", + }, + { + " PROXY foopy:10 ", + "foopy:10", + }, + { + "pRoXy foopy:10", + "foopy:10", + }, + { + "PROXY foopy", // No port. + "foopy:80", + }, + { + "socks foopy", + "socks4://foopy:1080", + }, + { + "socks4 foopy", + "socks4://foopy:1080", + }, + { + "socks5 foopy", + "socks5://foopy:1080", + }, + { + "socks5 foopy:11", + "socks5://foopy:11", + }, + { + " direct ", + "direct://", + }, + { + "https foopy", + "https://foopy:443", + }, + { + "https foopy:10", + "https://foopy:10", + }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + ProxyServer uri = ProxyServer::FromPacString(tests[i].input_pac); + EXPECT_TRUE(uri.is_valid()); + EXPECT_EQ(tests[i].expected_uri, uri.ToURI()); + } +} + +// Test parsing a ProxyServer from an invalid PAC representation. +TEST(ProxyServerTest, FromPACStringInvalid) { + const char* const tests[] = { + "PROXY", // missing host/port. + "HTTPS", // missing host/port. + "SOCKS", // missing host/port. + "DIRECT foopy:10", // direct cannot have host/port. + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + ProxyServer uri = ProxyServer::FromPacString(tests[i]); + EXPECT_FALSE(uri.is_valid()); + } +} + +TEST(ProxyServerTest, ComparatorAndEquality) { + struct { + // Inputs. + const char* const server1; + const char* const server2; + + // Expectation. + // -1 means server1 is less than server2 + // 0 means server1 equals server2 + // 1 means server1 is greater than server2 + int expected_comparison; + } tests[] = { + { // Equal. + "foo:11", + "http://foo:11", + 0 + }, + { // Port is different. + "foo:333", + "foo:444", + -1 + }, + { // Host is different. + "foo:33", + "bar:33", + 1 + }, + { // Scheme is different. + "socks4://foo:33", + "http://foo:33", + 1 + }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + // Parse the expected inputs to ProxyServer instances. + const ProxyServer server1 = + ProxyServer::FromURI(tests[i].server1, ProxyServer::SCHEME_HTTP); + + const ProxyServer server2 = + ProxyServer::FromURI(tests[i].server2, ProxyServer::SCHEME_HTTP); + + switch (tests[i].expected_comparison) { + case -1: + EXPECT_TRUE(server1 < server2); + EXPECT_FALSE(server2 < server1); + EXPECT_FALSE(server2 == server1); + break; + case 0: + EXPECT_FALSE(server1 < server2); + EXPECT_FALSE(server2 < server1); + EXPECT_TRUE(server2 == server1); + break; + case 1: + EXPECT_FALSE(server1 < server2); + EXPECT_TRUE(server2 < server1); + EXPECT_FALSE(server2 == server1); + break; + default: + FAIL() << "Invalid expectation. Can be only -1, 0, 1"; + } + } +} + +} // namespace + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_service.cc b/chromium/net/proxy_resolution/proxy_service.cc new file mode 100644 index 00000000000..6b633063ec2 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_service.cc @@ -0,0 +1,1602 @@ +// Copyright (c) 2012 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_resolution/proxy_service.h" + +#include <algorithm> +#include <cmath> +#include <utility> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "base/values.h" +#include "net/base/completion_callback.h" +#include "net/base/net_errors.h" +#include "net/base/proxy_delegate.h" +#include "net/base/url_util.h" +#include "net/log/net_log_capture_mode.h" +#include "net/log/net_log_event_type.h" +#include "net/log/net_log_with_source.h" +#include "net/proxy_resolution/dhcp_pac_file_fetcher.h" +#include "net/proxy_resolution/multi_threaded_proxy_resolver.h" +#include "net/proxy_resolution/pac_file_decider.h" +#include "net/proxy_resolution/pac_file_fetcher.h" +#include "net/proxy_resolution/proxy_config_service_fixed.h" +#include "net/proxy_resolution/proxy_resolver.h" +#include "net/proxy_resolution/proxy_resolver_factory.h" +#include "net/url_request/url_request_context.h" +#include "url/gurl.h" + +#if defined(OS_WIN) +#include "net/proxy_resolution/proxy_config_service_win.h" +#include "net/proxy_resolution/proxy_resolver_winhttp.h" +#elif defined(OS_IOS) +#include "net/proxy_resolution/proxy_config_service_ios.h" +#include "net/proxy_resolution/proxy_resolver_mac.h" +#elif defined(OS_MACOSX) +#include "net/proxy_resolution/proxy_config_service_mac.h" +#include "net/proxy_resolution/proxy_resolver_mac.h" +#elif defined(OS_LINUX) && !defined(OS_CHROMEOS) +#include "net/proxy_resolution/proxy_config_service_linux.h" +#elif defined(OS_ANDROID) +#include "net/proxy_resolution/proxy_config_service_android.h" +#endif + +using base::TimeDelta; +using base::TimeTicks; + +namespace net { + +namespace { + +const size_t kDefaultNumPacThreads = 4; + +// When the IP address changes we don't immediately re-run proxy auto-config. +// Instead, we wait for |kDelayAfterNetworkChangesMs| before +// attempting to re-valuate proxy auto-config. +// +// During this time window, any resolve requests sent to the +// ProxyResolutionService will be queued. Once we have waited the required +// amount of them, the proxy auto-config step will be run, and the queued +// requests resumed. +// +// The reason we play this game is that our signal for detecting network +// changes (NetworkChangeNotifier) may fire *before* the system's networking +// dependencies are fully configured. This is a problem since it means if +// we were to run proxy auto-config right away, it could fail due to spurious +// DNS failures. (see http://crbug.com/50779 for more details.) +// +// By adding the wait window, we give things a better chance to get properly +// set up. Network failures can happen at any time though, so we additionally +// poll the PAC script for changes, which will allow us to recover from these +// sorts of problems. +const int64_t kDelayAfterNetworkChangesMs = 2000; + +// This is the default policy for polling the PAC script. +// +// In response to a failure, the poll intervals are: +// 0: 8 seconds (scheduled on timer) +// 1: 32 seconds +// 2: 2 minutes +// 3+: 4 hours +// +// In response to a success, the poll intervals are: +// 0+: 12 hours +// +// Only the 8 second poll is scheduled on a timer, the rest happen in response +// to network activity (and hence will take longer than the written time). +// +// Explanation for these values: +// +// TODO(eroman): These values are somewhat arbitrary, and need to be tuned +// using some histograms data. Trying to be conservative so as not to break +// existing setups when deployed. A simple exponential retry scheme would be +// more elegant, but places more load on server. +// +// The motivation for trying quickly after failures (8 seconds) is to recover +// from spurious network failures, which are common after the IP address has +// just changed (like DNS failing to resolve). The next 32 second boundary is +// to try and catch other VPN weirdness which anecdotally I have seen take +// 10+ seconds for some users. +// +// The motivation for re-trying after a success is to check for possible +// content changes to the script, or to the WPAD auto-discovery results. We are +// not very aggressive with these checks so as to minimize the risk of +// overloading existing PAC setups. Moreover it is unlikely that PAC scripts +// change very frequently in existing setups. More research is needed to +// motivate what safe values are here, and what other user agents do. +// +// Comparison to other browsers: +// +// In Firefox the PAC URL is re-tried on failures according to +// network.proxy.autoconfig_retry_interval_min and +// network.proxy.autoconfig_retry_interval_max. The defaults are 5 seconds and +// 5 minutes respectively. It doubles the interval at each attempt. +// +// TODO(eroman): Figure out what Internet Explorer does. +class DefaultPollPolicy : public ProxyResolutionService::PacPollPolicy { + public: + DefaultPollPolicy() = default; + + Mode GetNextDelay(int initial_error, + TimeDelta current_delay, + TimeDelta* next_delay) const override { + if (initial_error != OK) { + // Re-try policy for failures. + const int kDelay1Seconds = 8; + const int kDelay2Seconds = 32; + const int kDelay3Seconds = 2 * 60; // 2 minutes + const int kDelay4Seconds = 4 * 60 * 60; // 4 Hours + + // Initial poll. + if (current_delay < TimeDelta()) { + *next_delay = TimeDelta::FromSeconds(kDelay1Seconds); + return MODE_USE_TIMER; + } + switch (current_delay.InSeconds()) { + case kDelay1Seconds: + *next_delay = TimeDelta::FromSeconds(kDelay2Seconds); + return MODE_START_AFTER_ACTIVITY; + case kDelay2Seconds: + *next_delay = TimeDelta::FromSeconds(kDelay3Seconds); + return MODE_START_AFTER_ACTIVITY; + default: + *next_delay = TimeDelta::FromSeconds(kDelay4Seconds); + return MODE_START_AFTER_ACTIVITY; + } + } else { + // Re-try policy for succeses. + *next_delay = TimeDelta::FromHours(12); + return MODE_START_AFTER_ACTIVITY; + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(DefaultPollPolicy); +}; + +// Config getter that always returns direct settings. +class ProxyConfigServiceDirect : public ProxyConfigService { + public: + // ProxyConfigService implementation: + void AddObserver(Observer* observer) override {} + void RemoveObserver(Observer* observer) override {} + ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) override { + *config = ProxyConfig::CreateDirect(); + config->set_source(PROXY_CONFIG_SOURCE_UNKNOWN); + return CONFIG_VALID; + } +}; + +// Proxy resolver that fails every time. +class ProxyResolverNull : public ProxyResolver { + public: + ProxyResolverNull() = default; + + // ProxyResolver implementation. + int GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) override { + return ERR_NOT_IMPLEMENTED; + } + +}; + +// ProxyResolver that simulates a PAC script which returns +// |pac_string| for every single URL. +class ProxyResolverFromPacString : public ProxyResolver { + public: + explicit ProxyResolverFromPacString(const std::string& pac_string) + : pac_string_(pac_string) {} + + int GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + std::unique_ptr<Request>* request, + const NetLogWithSource& net_log) override { + results->UsePacString(pac_string_); + return OK; + } + + private: + const std::string pac_string_; +}; + +// Creates ProxyResolvers using a platform-specific implementation. +class ProxyResolverFactoryForSystem : public MultiThreadedProxyResolverFactory { + public: + explicit ProxyResolverFactoryForSystem(size_t max_num_threads) + : MultiThreadedProxyResolverFactory(max_num_threads, + false /*expects_pac_bytes*/) {} + + std::unique_ptr<ProxyResolverFactory> CreateProxyResolverFactory() override { +#if defined(OS_WIN) + return std::make_unique<ProxyResolverFactoryWinHttp>(); +#elif defined(OS_MACOSX) + return std::make_unique<ProxyResolverFactoryMac>(); +#else + NOTREACHED(); + return NULL; +#endif + } + + static bool IsSupported() { +#if defined(OS_WIN) || defined(OS_MACOSX) + return true; +#else + return false; +#endif + } + + private: + DISALLOW_COPY_AND_ASSIGN(ProxyResolverFactoryForSystem); +}; + +class ProxyResolverFactoryForNullResolver : public ProxyResolverFactory { + public: + ProxyResolverFactoryForNullResolver() : ProxyResolverFactory(false) {} + + // ProxyResolverFactory overrides. + int CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const net::CompletionCallback& callback, + std::unique_ptr<Request>* request) override { + resolver->reset(new ProxyResolverNull()); + return OK; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ProxyResolverFactoryForNullResolver); +}; + +class ProxyResolverFactoryForPacResult : public ProxyResolverFactory { + public: + explicit ProxyResolverFactoryForPacResult(const std::string& pac_string) + : ProxyResolverFactory(false), pac_string_(pac_string) {} + + // ProxyResolverFactory override. + int CreateProxyResolver( + const scoped_refptr<ProxyResolverScriptData>& pac_script, + std::unique_ptr<ProxyResolver>* resolver, + const net::CompletionCallback& callback, + std::unique_ptr<Request>* request) override { + resolver->reset(new ProxyResolverFromPacString(pac_string_)); + return OK; + } + + private: + const std::string pac_string_; + + DISALLOW_COPY_AND_ASSIGN(ProxyResolverFactoryForPacResult); +}; + +// Returns NetLog parameters describing a proxy configuration change. +std::unique_ptr<base::Value> NetLogProxyConfigChangedCallback( + const base::Optional<ProxyConfig>* old_config, + const ProxyConfig* new_config, + NetLogCaptureMode /* capture_mode */) { + std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + // The "old_config" is optional -- the first notification will not have + // any "previous" configuration. + if (old_config->has_value()) + dict->Set("old_config", (*old_config)->ToValue()); + dict->Set("new_config", new_config->ToValue()); + return std::move(dict); +} + +std::unique_ptr<base::Value> NetLogBadProxyListCallback( + const ProxyRetryInfoMap* retry_info, + NetLogCaptureMode /* capture_mode */) { + auto dict = std::make_unique<base::DictionaryValue>(); + auto list = std::make_unique<base::ListValue>(); + + for (ProxyRetryInfoMap::const_iterator iter = retry_info->begin(); + iter != retry_info->end(); ++iter) { + list->AppendString(iter->first); + } + dict->Set("bad_proxy_list", std::move(list)); + return std::move(dict); +} + +// Returns NetLog parameters on a successfuly proxy resolution. +std::unique_ptr<base::Value> NetLogFinishedResolvingProxyCallback( + const ProxyInfo* result, + NetLogCaptureMode /* capture_mode */) { + std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + dict->SetString("pac_string", result->ToPacString()); + return std::move(dict); +} + +#if defined(OS_CHROMEOS) +class UnsetProxyConfigService : public ProxyConfigService { + public: + UnsetProxyConfigService() = default; + ~UnsetProxyConfigService() override = default; + + void AddObserver(Observer* observer) override {} + void RemoveObserver(Observer* observer) override {} + ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) override { + return CONFIG_UNSET; + } +}; +#endif + +// Returns a sanitized copy of |url| which is safe to pass on to a PAC script. +// The method for sanitizing is determined by |policy|. See the comments for +// that enum for details. +GURL SanitizeUrl(const GURL& url, + ProxyResolutionService::SanitizeUrlPolicy policy) { + DCHECK(url.is_valid()); + + GURL::Replacements replacements; + replacements.ClearUsername(); + replacements.ClearPassword(); + replacements.ClearRef(); + + if (policy == ProxyResolutionService::SanitizeUrlPolicy::SAFE && + url.SchemeIsCryptographic()) { + replacements.ClearPath(); + replacements.ClearQuery(); + } + + return url.ReplaceComponents(replacements); +} + +} // namespace + +// ProxyResolutionService::InitProxyResolver ---------------------------------- + +// This glues together two asynchronous steps: +// (1) ProxyScriptDecider -- try to fetch/validate a sequence of PAC scripts +// to figure out what we should configure against. +// (2) Feed the fetched PAC script into the ProxyResolver. +// +// InitProxyResolver is a single-use class which encapsulates cancellation as +// part of its destructor. Start() or StartSkipDecider() should be called just +// once. The instance can be destroyed at any time, and the request will be +// cancelled. + +class ProxyResolutionService::InitProxyResolver { + public: + InitProxyResolver() + : proxy_resolver_factory_(nullptr), + proxy_resolver_(NULL), + next_state_(STATE_NONE), + quick_check_enabled_(true) {} + + ~InitProxyResolver() { + // Note that the destruction of ProxyScriptDecider will automatically cancel + // any outstanding work. + } + + // Begins initializing the proxy resolver; calls |callback| when done. A + // ProxyResolver instance will be created using |proxy_resolver_factory| and + // returned via |proxy_resolver| if the final result is OK. + int Start(std::unique_ptr<ProxyResolver>* proxy_resolver, + ProxyResolverFactory* proxy_resolver_factory, + ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher, + NetLog* net_log, + const ProxyConfig& config, + TimeDelta wait_delay, + const CompletionCallback& callback) { + DCHECK_EQ(STATE_NONE, next_state_); + proxy_resolver_ = proxy_resolver; + proxy_resolver_factory_ = proxy_resolver_factory; + + decider_.reset(new ProxyScriptDecider( + proxy_script_fetcher, dhcp_proxy_script_fetcher, net_log)); + decider_->set_quick_check_enabled(quick_check_enabled_); + config_ = config; + wait_delay_ = wait_delay; + callback_ = callback; + + next_state_ = STATE_DECIDE_PROXY_SCRIPT; + return DoLoop(OK); + } + + // Similar to Start(), however it skips the ProxyScriptDecider stage. Instead + // |effective_config|, |decider_result| and |script_data| will be used as the + // inputs for initializing the ProxyResolver. A ProxyResolver instance will + // be created using |proxy_resolver_factory| and returned via + // |proxy_resolver| if the final result is OK. + int StartSkipDecider(std::unique_ptr<ProxyResolver>* proxy_resolver, + ProxyResolverFactory* proxy_resolver_factory, + const ProxyConfig& effective_config, + int decider_result, + ProxyResolverScriptData* script_data, + const CompletionCallback& callback) { + DCHECK_EQ(STATE_NONE, next_state_); + proxy_resolver_ = proxy_resolver; + proxy_resolver_factory_ = proxy_resolver_factory; + + effective_config_ = effective_config; + script_data_ = script_data; + callback_ = callback; + + if (decider_result != OK) + return decider_result; + + next_state_ = STATE_CREATE_RESOLVER; + return DoLoop(OK); + } + + // Returns the proxy configuration that was selected by ProxyScriptDecider. + // Should only be called upon completion of the initialization. + const ProxyConfig& effective_config() const { + DCHECK_EQ(STATE_NONE, next_state_); + return effective_config_; + } + + // Returns the PAC script data that was selected by ProxyScriptDecider. + // Should only be called upon completion of the initialization. + const scoped_refptr<ProxyResolverScriptData>& script_data() { + DCHECK_EQ(STATE_NONE, next_state_); + return script_data_; + } + + LoadState GetLoadState() const { + if (next_state_ == STATE_DECIDE_PROXY_SCRIPT_COMPLETE) { + // In addition to downloading, this state may also include the stall time + // after network change events (kDelayAfterNetworkChangesMs). + return LOAD_STATE_DOWNLOADING_PROXY_SCRIPT; + } + return LOAD_STATE_RESOLVING_PROXY_FOR_URL; + } + + // This must be called before the HostResolver is torn down. + void OnShutdown() { + if (decider_) + decider_->OnShutdown(); + } + + void set_quick_check_enabled(bool enabled) { quick_check_enabled_ = enabled; } + bool quick_check_enabled() const { return quick_check_enabled_; } + + private: + enum State { + STATE_NONE, + STATE_DECIDE_PROXY_SCRIPT, + STATE_DECIDE_PROXY_SCRIPT_COMPLETE, + STATE_CREATE_RESOLVER, + STATE_CREATE_RESOLVER_COMPLETE, + }; + + int DoLoop(int result) { + DCHECK_NE(next_state_, STATE_NONE); + int rv = result; + do { + State state = next_state_; + next_state_ = STATE_NONE; + switch (state) { + case STATE_DECIDE_PROXY_SCRIPT: + DCHECK_EQ(OK, rv); + rv = DoDecideProxyScript(); + break; + case STATE_DECIDE_PROXY_SCRIPT_COMPLETE: + rv = DoDecideProxyScriptComplete(rv); + break; + case STATE_CREATE_RESOLVER: + DCHECK_EQ(OK, rv); + rv = DoCreateResolver(); + break; + case STATE_CREATE_RESOLVER_COMPLETE: + rv = DoCreateResolverComplete(rv); + break; + default: + NOTREACHED() << "bad state: " << state; + rv = ERR_UNEXPECTED; + break; + } + } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); + return rv; + } + + int DoDecideProxyScript() { + next_state_ = STATE_DECIDE_PROXY_SCRIPT_COMPLETE; + + return decider_->Start( + config_, wait_delay_, proxy_resolver_factory_->expects_pac_bytes(), + base::Bind(&InitProxyResolver::OnIOCompletion, base::Unretained(this))); + } + + int DoDecideProxyScriptComplete(int result) { + if (result != OK) + return result; + + effective_config_ = decider_->effective_config(); + script_data_ = decider_->script_data(); + + next_state_ = STATE_CREATE_RESOLVER; + return OK; + } + + int DoCreateResolver() { + DCHECK(script_data_.get()); + // TODO(eroman): Should log this latency to the NetLog. + next_state_ = STATE_CREATE_RESOLVER_COMPLETE; + return proxy_resolver_factory_->CreateProxyResolver( + script_data_, proxy_resolver_, + base::Bind(&InitProxyResolver::OnIOCompletion, base::Unretained(this)), + &create_resolver_request_); + } + + int DoCreateResolverComplete(int result) { + if (result != OK) + proxy_resolver_->reset(); + return result; + } + + void OnIOCompletion(int result) { + DCHECK_NE(STATE_NONE, next_state_); + int rv = DoLoop(result); + if (rv != ERR_IO_PENDING) + DoCallback(rv); + } + + void DoCallback(int result) { + DCHECK_NE(ERR_IO_PENDING, result); + callback_.Run(result); + } + + ProxyConfig config_; + ProxyConfig effective_config_; + scoped_refptr<ProxyResolverScriptData> script_data_; + TimeDelta wait_delay_; + std::unique_ptr<ProxyScriptDecider> decider_; + ProxyResolverFactory* proxy_resolver_factory_; + std::unique_ptr<ProxyResolverFactory::Request> create_resolver_request_; + std::unique_ptr<ProxyResolver>* proxy_resolver_; + CompletionCallback callback_; + State next_state_; + bool quick_check_enabled_; + + DISALLOW_COPY_AND_ASSIGN(InitProxyResolver); +}; + +// ProxyResolutionService::ProxyScriptDeciderPoller --------------------------- + +// This helper class encapsulates the logic to schedule and run periodic +// background checks to see if the PAC script (or effective proxy configuration) +// has changed. If a change is detected, then the caller will be notified via +// the ChangeCallback. +class ProxyResolutionService::ProxyScriptDeciderPoller { + public: + typedef base::Callback<void(int, ProxyResolverScriptData*, + const ProxyConfig&)> ChangeCallback; + + // Builds a poller helper, and starts polling for updates. Whenever a change + // is observed, |callback| will be invoked with the details. + // + // |config| specifies the (unresolved) proxy configuration to poll. + // |proxy_resolver_expects_pac_bytes| the type of proxy resolver we expect + // to use the resulting script data with + // (so it can choose the right format). + // |proxy_script_fetcher| this pointer must remain alive throughout our + // lifetime. It is the dependency that will be used + // for downloading proxy scripts. + // |dhcp_proxy_script_fetcher| similar to |proxy_script_fetcher|, but for + // the DHCP dependency. + // |init_net_error| This is the initial network error (possibly success) + // encountered by the first PAC fetch attempt. We use it + // to schedule updates more aggressively if the initial + // fetch resulted in an error. + // |init_script_data| the initial script data from the PAC fetch attempt. + // This is the baseline used to determine when the + // script's contents have changed. + // |net_log| the NetLog to log progress into. + ProxyScriptDeciderPoller(ChangeCallback callback, + const ProxyConfig& config, + bool proxy_resolver_expects_pac_bytes, + ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher, + int init_net_error, + const scoped_refptr<ProxyResolverScriptData>& + init_script_data, + NetLog* net_log) + : change_callback_(callback), + config_(config), + proxy_resolver_expects_pac_bytes_(proxy_resolver_expects_pac_bytes), + proxy_script_fetcher_(proxy_script_fetcher), + dhcp_proxy_script_fetcher_(dhcp_proxy_script_fetcher), + last_error_(init_net_error), + last_script_data_(init_script_data), + last_poll_time_(TimeTicks::Now()), + weak_factory_(this) { + // Set the initial poll delay. + next_poll_mode_ = poll_policy()->GetNextDelay( + last_error_, TimeDelta::FromSeconds(-1), &next_poll_delay_); + TryToStartNextPoll(false); + } + + void OnLazyPoll() { + // We have just been notified of network activity. Use this opportunity to + // see if we can start our next poll. + TryToStartNextPoll(true); + } + + static const PacPollPolicy* set_policy(const PacPollPolicy* policy) { + const PacPollPolicy* prev = poll_policy_; + poll_policy_ = policy; + return prev; + } + + void set_quick_check_enabled(bool enabled) { quick_check_enabled_ = enabled; } + bool quick_check_enabled() const { return quick_check_enabled_; } + + private: + // Returns the effective poll policy (the one injected by unit-tests, or the + // default). + const PacPollPolicy* poll_policy() { + if (poll_policy_) + return poll_policy_; + return &default_poll_policy_; + } + + void StartPollTimer() { + DCHECK(!decider_.get()); + + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::Bind(&ProxyScriptDeciderPoller::DoPoll, + weak_factory_.GetWeakPtr()), + next_poll_delay_); + } + + void TryToStartNextPoll(bool triggered_by_activity) { + switch (next_poll_mode_) { + case PacPollPolicy::MODE_USE_TIMER: + if (!triggered_by_activity) + StartPollTimer(); + break; + + case PacPollPolicy::MODE_START_AFTER_ACTIVITY: + if (triggered_by_activity && !decider_.get()) { + TimeDelta elapsed_time = TimeTicks::Now() - last_poll_time_; + if (elapsed_time >= next_poll_delay_) + DoPoll(); + } + break; + } + } + + void DoPoll() { + last_poll_time_ = TimeTicks::Now(); + + // Start the proxy script decider to see if anything has changed. + // TODO(eroman): Pass a proper NetLog rather than NULL. + decider_.reset(new ProxyScriptDecider( + proxy_script_fetcher_, dhcp_proxy_script_fetcher_, NULL)); + decider_->set_quick_check_enabled(quick_check_enabled_); + int result = decider_->Start( + config_, TimeDelta(), proxy_resolver_expects_pac_bytes_, + base::Bind(&ProxyScriptDeciderPoller::OnProxyScriptDeciderCompleted, + base::Unretained(this))); + + if (result != ERR_IO_PENDING) + OnProxyScriptDeciderCompleted(result); + } + + void OnProxyScriptDeciderCompleted(int result) { + if (HasScriptDataChanged(result, decider_->script_data())) { + // Something has changed, we must notify the ProxyResolutionService so it + // can re-initialize its ProxyResolver. Note that we post a notification + // task rather than calling it directly -- this is done to avoid an ugly + // destruction sequence, since |this| might be destroyed as a result of + // the notification. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&ProxyScriptDeciderPoller::NotifyProxyServiceOfChange, + weak_factory_.GetWeakPtr(), result, + decider_->script_data(), + decider_->effective_config())); + return; + } + + decider_.reset(); + + // Decide when the next poll should take place, and possibly start the + // next timer. + next_poll_mode_ = poll_policy()->GetNextDelay( + last_error_, next_poll_delay_, &next_poll_delay_); + TryToStartNextPoll(false); + } + + bool HasScriptDataChanged(int result, + const scoped_refptr<ProxyResolverScriptData>& script_data) { + if (result != last_error_) { + // Something changed -- it was failing before and now it succeeded, or + // conversely it succeeded before and now it failed. Or it failed in + // both cases, however the specific failure error codes differ. + return true; + } + + if (result != OK) { + // If it failed last time and failed again with the same error code this + // time, then nothing has actually changed. + return false; + } + + // Otherwise if it succeeded both this time and last time, we need to look + // closer and see if we ended up downloading different content for the PAC + // script. + return !script_data->Equals(last_script_data_.get()); + } + + void NotifyProxyServiceOfChange( + int result, + const scoped_refptr<ProxyResolverScriptData>& script_data, + const ProxyConfig& effective_config) { + // Note that |this| may be deleted after calling into the + // ProxyResolutionService. + change_callback_.Run(result, script_data.get(), effective_config); + } + + ChangeCallback change_callback_; + ProxyConfig config_; + bool proxy_resolver_expects_pac_bytes_; + ProxyScriptFetcher* proxy_script_fetcher_; + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher_; + + int last_error_; + scoped_refptr<ProxyResolverScriptData> last_script_data_; + + std::unique_ptr<ProxyScriptDecider> decider_; + TimeDelta next_poll_delay_; + PacPollPolicy::Mode next_poll_mode_; + + TimeTicks last_poll_time_; + + // Polling policy injected by unit-tests. Otherwise this is NULL and the + // default policy will be used. + static const PacPollPolicy* poll_policy_; + + const DefaultPollPolicy default_poll_policy_; + + bool quick_check_enabled_; + + base::WeakPtrFactory<ProxyScriptDeciderPoller> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ProxyScriptDeciderPoller); +}; + +// static +const ProxyResolutionService::PacPollPolicy* + ProxyResolutionService::ProxyScriptDeciderPoller::poll_policy_ = NULL; + +// ProxyResolutionService::Request -------------------------------------------- + +class ProxyResolutionService::Request + : public base::RefCounted<ProxyResolutionService::Request> { + public: + Request(ProxyResolutionService* service, + const GURL& url, + const std::string& method, + ProxyDelegate* proxy_delegate, + ProxyInfo* results, + const CompletionCallback& user_callback, + const NetLogWithSource& net_log) + : service_(service), + user_callback_(user_callback), + results_(results), + url_(url), + method_(method), + proxy_delegate_(proxy_delegate), + resolve_job_(nullptr), + config_source_(PROXY_CONFIG_SOURCE_UNKNOWN), + net_log_(net_log), + creation_time_(TimeTicks::Now()) { + DCHECK(!user_callback.is_null()); + } + + // Starts the resolve proxy request. + int Start() { + DCHECK(!was_cancelled()); + DCHECK(!is_started()); + + DCHECK(service_->config_); + config_source_ = service_->config_->source(); + + return resolver()->GetProxyForURL( + url_, results_, + base::Bind(&Request::QueryComplete, base::Unretained(this)), + &resolve_job_, net_log_); + } + + bool is_started() const { + // Note that !! casts to bool. (VS gives a warning otherwise). + return !!resolve_job_.get(); + } + + void StartAndCompleteCheckingForSynchronous() { + int rv = + service_->TryToCompleteSynchronously(url_, proxy_delegate_, results_); + if (rv == ERR_IO_PENDING) + rv = Start(); + if (rv != ERR_IO_PENDING) + QueryComplete(rv); + } + + void CancelResolveJob() { + DCHECK(is_started()); + // The request may already be running in the resolver. + resolve_job_.reset(); + DCHECK(!is_started()); + } + + void Cancel() { + net_log_.AddEvent(NetLogEventType::CANCELLED); + + if (is_started()) + CancelResolveJob(); + + // Mark as cancelled, to prevent accessing this again later. + service_ = NULL; + user_callback_.Reset(); + results_ = NULL; + + net_log_.EndEvent(NetLogEventType::PROXY_SERVICE); + } + + // Returns true if Cancel() has been called. + bool was_cancelled() const { + return user_callback_.is_null(); + } + + // Helper to call after ProxyResolver completion (both synchronous and + // asynchronous). Fixes up the result that is to be returned to user. + int QueryDidComplete(int result_code) { + DCHECK(!was_cancelled()); + + // Clear |resolve_job_| so is_started() returns false while + // DidFinishResolvingProxy() runs. + resolve_job_.reset(); + + // Note that DidFinishResolvingProxy might modify |results_|. + int rv = service_->DidFinishResolvingProxy(url_, method_, proxy_delegate_, + results_, result_code, net_log_); + + // Make a note in the results which configuration was in use at the + // time of the resolve. + results_->config_source_ = config_source_; + results_->did_use_pac_script_ = true; + results_->proxy_resolve_start_time_ = creation_time_; + results_->proxy_resolve_end_time_ = TimeTicks::Now(); + + // Reset the state associated with in-progress-resolve. + config_source_ = PROXY_CONFIG_SOURCE_UNKNOWN; + + return rv; + } + + NetLogWithSource* net_log() { return &net_log_; } + + LoadState GetLoadState() const { + if (is_started()) + return resolve_job_->GetLoadState(); + return LOAD_STATE_RESOLVING_PROXY_FOR_URL; + } + + private: + friend class base::RefCounted<ProxyResolutionService::Request>; + + ~Request() = default; + + // Callback for when the ProxyResolver request has completed. + void QueryComplete(int result_code) { + result_code = QueryDidComplete(result_code); + + // Remove this completed Request from the service's pending list. + /// (which will probably cause deletion of |this|). + if (!user_callback_.is_null()) { + CompletionCallback callback = user_callback_; + service_->RemovePendingRequest(this); + callback.Run(result_code); + } + } + + ProxyResolver* resolver() const { return service_->resolver_.get(); } + + // Note that we don't hold a reference to the ProxyResolutionService. + // Outstanding requests are cancelled during ~ProxyResolutionService, so this + // is guaranteed to be valid throughout our lifetime. + ProxyResolutionService* service_; + CompletionCallback user_callback_; + ProxyInfo* results_; + GURL url_; + std::string method_; + ProxyDelegate* proxy_delegate_; + std::unique_ptr<ProxyResolver::Request> resolve_job_; + ProxyConfigSource config_source_; // The source of proxy settings. + NetLogWithSource net_log_; + // Time when the request was created. Stored here rather than in |results_| + // because the time in |results_| will be cleared. + TimeTicks creation_time_; +}; + +// ProxyResolutionService ----------------------------------------------------- + +ProxyResolutionService::ProxyResolutionService( + std::unique_ptr<ProxyConfigService> config_service, + std::unique_ptr<ProxyResolverFactory> resolver_factory, + NetLog* net_log) + : resolver_factory_(std::move(resolver_factory)), + current_state_(STATE_NONE), + net_log_(net_log), + stall_proxy_auto_config_delay_( + TimeDelta::FromMilliseconds(kDelayAfterNetworkChangesMs)), + quick_check_enabled_(true), + sanitize_url_policy_(SanitizeUrlPolicy::SAFE) { + NetworkChangeNotifier::AddIPAddressObserver(this); + NetworkChangeNotifier::AddDNSObserver(this); + ResetConfigService(std::move(config_service)); +} + +// static +std::unique_ptr<ProxyResolutionService> +ProxyResolutionService::CreateUsingSystemProxyResolver( + std::unique_ptr<ProxyConfigService> proxy_config_service, + NetLog* net_log) { + DCHECK(proxy_config_service); + + if (!ProxyResolverFactoryForSystem::IsSupported()) { + VLOG(1) << "PAC support disabled because there is no system implementation"; + return CreateWithoutProxyResolver(std::move(proxy_config_service), net_log); + } + + return std::make_unique<ProxyResolutionService>( + std::move(proxy_config_service), + std::make_unique<ProxyResolverFactoryForSystem>(kDefaultNumPacThreads), + net_log); +} + +// static +std::unique_ptr<ProxyResolutionService> +ProxyResolutionService::CreateWithoutProxyResolver( + std::unique_ptr<ProxyConfigService> proxy_config_service, + NetLog* net_log) { + return std::make_unique<ProxyResolutionService>( + std::move(proxy_config_service), + std::make_unique<ProxyResolverFactoryForNullResolver>(), net_log); +} + +// static +std::unique_ptr<ProxyResolutionService> ProxyResolutionService::CreateFixed( + const ProxyConfig& pc) { + // TODO(eroman): This isn't quite right, won't work if |pc| specifies + // a PAC script. + return CreateUsingSystemProxyResolver( + std::make_unique<ProxyConfigServiceFixed>(pc), NULL); +} + +// static +std::unique_ptr<ProxyResolutionService> ProxyResolutionService::CreateFixed( + const std::string& proxy) { + ProxyConfig proxy_config; + proxy_config.proxy_rules().ParseFromString(proxy); + return ProxyResolutionService::CreateFixed(proxy_config); +} + +// static +std::unique_ptr<ProxyResolutionService> ProxyResolutionService::CreateDirect() { + return CreateDirectWithNetLog(NULL); +} + +std::unique_ptr<ProxyResolutionService> +ProxyResolutionService::CreateDirectWithNetLog(NetLog* net_log) { + // Use direct connections. + return std::make_unique<ProxyResolutionService>( + std::make_unique<ProxyConfigServiceDirect>(), + std::make_unique<ProxyResolverFactoryForNullResolver>(), net_log); +} + +// static +std::unique_ptr<ProxyResolutionService> +ProxyResolutionService::CreateFixedFromPacResult( + const std::string& pac_string) { + // We need the settings to contain an "automatic" setting, otherwise the + // ProxyResolver dependency we give it will never be used. + std::unique_ptr<ProxyConfigService> proxy_config_service( + new ProxyConfigServiceFixed(ProxyConfig::CreateAutoDetect())); + + return std::make_unique<ProxyResolutionService>( + std::move(proxy_config_service), + std::make_unique<ProxyResolverFactoryForPacResult>(pac_string), nullptr); +} + +int ProxyResolutionService::ResolveProxy(const GURL& raw_url, + const std::string& method, + ProxyInfo* result, + const CompletionCallback& callback, + Request** pac_request, + ProxyDelegate* proxy_delegate, + const NetLogWithSource& net_log) { + DCHECK(!callback.is_null()); + return ResolveProxyHelper(raw_url, method, result, callback, pac_request, + proxy_delegate, net_log); +} + +int ProxyResolutionService::ResolveProxyHelper( + const GURL& raw_url, + const std::string& method, + ProxyInfo* result, + const CompletionCallback& callback, + Request** pac_request, + ProxyDelegate* proxy_delegate, + const NetLogWithSource& net_log) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + net_log.BeginEvent(NetLogEventType::PROXY_SERVICE); + + // Notify our polling-based dependencies that a resolve is taking place. + // This way they can schedule their polls in response to network activity. + config_service_->OnLazyPoll(); + if (script_poller_.get()) + script_poller_->OnLazyPoll(); + + if (current_state_ == STATE_NONE) + ApplyProxyConfigIfAvailable(); + + // Sanitize the URL before passing it on to the proxy resolver (i.e. PAC + // script). The goal is to remove sensitive data (like embedded user names + // and password), and local data (i.e. reference fragment) which does not need + // to be disclosed to the resolver. + GURL url = SanitizeUrl(raw_url, sanitize_url_policy_); + + // Check if the request can be completed right away. (This is the case when + // using a direct connection for example). + int rv = TryToCompleteSynchronously(url, proxy_delegate, result); + if (rv != ERR_IO_PENDING) { + rv = DidFinishResolvingProxy(url, method, proxy_delegate, result, rv, + net_log); + return rv; + } + + if (callback.is_null()) + return ERR_IO_PENDING; + + scoped_refptr<Request> req(new Request(this, url, method, proxy_delegate, + result, callback, net_log)); + + if (current_state_ == STATE_READY) { + // Start the resolve request. + rv = req->Start(); + if (rv != ERR_IO_PENDING) + return req->QueryDidComplete(rv); + } else { + req->net_log()->BeginEvent( + NetLogEventType::PROXY_SERVICE_WAITING_FOR_INIT_PAC); + } + + DCHECK_EQ(ERR_IO_PENDING, rv); + DCHECK(!ContainsPendingRequest(req.get())); + pending_requests_.insert(req); + + // Completion will be notified through |callback|, unless the caller cancels + // the request using |pac_request|. + if (pac_request) + *pac_request = req.get(); + return rv; // ERR_IO_PENDING +} + +bool ProxyResolutionService::TryResolveProxySynchronously( + const GURL& raw_url, + const std::string& method, + ProxyInfo* result, + ProxyDelegate* proxy_delegate, + const NetLogWithSource& net_log) { + CompletionCallback null_callback; + return ResolveProxyHelper(raw_url, method, result, null_callback, + nullptr /* pac_request*/, proxy_delegate, + net_log) == OK; +} + +int ProxyResolutionService::TryToCompleteSynchronously( + const GURL& url, + ProxyDelegate* proxy_delegate, + ProxyInfo* result) { + DCHECK_NE(STATE_NONE, current_state_); + + if (current_state_ != STATE_READY) + return ERR_IO_PENDING; // Still initializing. + + DCHECK(config_); + // If it was impossible to fetch or parse the PAC script, we cannot complete + // the request here and bail out. + if (permanent_error_ != OK) + return permanent_error_; + + if (config_->HasAutomaticSettings()) + return ERR_IO_PENDING; // Must submit the request to the proxy resolver. + + // Use the manual proxy settings. + config_->proxy_rules().Apply(url, result); + result->config_source_ = config_->source(); + + return OK; +} + +ProxyResolutionService::~ProxyResolutionService() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + NetworkChangeNotifier::RemoveIPAddressObserver(this); + NetworkChangeNotifier::RemoveDNSObserver(this); + config_service_->RemoveObserver(this); + + // Cancel any inprogress requests. + for (PendingRequests::iterator it = pending_requests_.begin(); + it != pending_requests_.end(); + ++it) { + (*it)->Cancel(); + } +} + +void ProxyResolutionService::SuspendAllPendingRequests() { + for (PendingRequests::iterator it = pending_requests_.begin(); + it != pending_requests_.end(); + ++it) { + Request* req = it->get(); + if (req->is_started()) { + req->CancelResolveJob(); + + req->net_log()->BeginEvent( + NetLogEventType::PROXY_SERVICE_WAITING_FOR_INIT_PAC); + } + } +} + +void ProxyResolutionService::SetReady() { + DCHECK(!init_proxy_resolver_.get()); + current_state_ = STATE_READY; + + // Make a copy in case |this| is deleted during the synchronous completion + // of one of the requests. If |this| is deleted then all of the Request + // instances will be Cancel()-ed. + PendingRequests pending_copy = pending_requests_; + + for (PendingRequests::iterator it = pending_copy.begin(); + it != pending_copy.end(); + ++it) { + Request* req = it->get(); + if (!req->is_started() && !req->was_cancelled()) { + req->net_log()->EndEvent( + NetLogEventType::PROXY_SERVICE_WAITING_FOR_INIT_PAC); + + // Note that we re-check for synchronous completion, in case we are + // no longer using a ProxyResolver (can happen if we fell-back to manual). + req->StartAndCompleteCheckingForSynchronous(); + } + } +} + +void ProxyResolutionService::ApplyProxyConfigIfAvailable() { + DCHECK_EQ(STATE_NONE, current_state_); + + config_service_->OnLazyPoll(); + + // If we have already fetched the configuration, start applying it. + if (fetched_config_) { + InitializeUsingLastFetchedConfig(); + return; + } + + // Otherwise we need to first fetch the configuration. + current_state_ = STATE_WAITING_FOR_PROXY_CONFIG; + + // Retrieve the current proxy configuration from the ProxyConfigService. + // If a configuration is not available yet, we will get called back later + // by our ProxyConfigService::Observer once it changes. + ProxyConfig config; + ProxyConfigService::ConfigAvailability availability = + config_service_->GetLatestProxyConfig(&config); + if (availability != ProxyConfigService::CONFIG_PENDING) + OnProxyConfigChanged(config, availability); +} + +void ProxyResolutionService::OnInitProxyResolverComplete(int result) { + DCHECK_EQ(STATE_WAITING_FOR_INIT_PROXY_RESOLVER, current_state_); + DCHECK(init_proxy_resolver_.get()); + DCHECK(fetched_config_); + DCHECK(fetched_config_->HasAutomaticSettings()); + config_ = init_proxy_resolver_->effective_config(); + + // At this point we have decided which proxy settings to use (i.e. which PAC + // script if any). We start up a background poller to periodically revisit + // this decision. If the contents of the PAC script change, or if the + // result of proxy auto-discovery changes, this poller will notice it and + // will trigger a re-initialization using the newly discovered PAC. + script_poller_.reset(new ProxyScriptDeciderPoller( + base::Bind(&ProxyResolutionService::InitializeUsingDecidedConfig, + base::Unretained(this)), + fetched_config_.value(), resolver_factory_->expects_pac_bytes(), + proxy_script_fetcher_.get(), dhcp_proxy_script_fetcher_.get(), result, + init_proxy_resolver_->script_data(), NULL)); + script_poller_->set_quick_check_enabled(quick_check_enabled_); + + init_proxy_resolver_.reset(); + + if (result != OK) { + if (fetched_config_->pac_mandatory()) { + VLOG(1) << "Failed configuring with mandatory PAC script, blocking all " + "traffic."; + config_ = fetched_config_; + result = ERR_MANDATORY_PROXY_CONFIGURATION_FAILED; + } else { + VLOG(1) << "Failed configuring with PAC script, falling-back to manual " + "proxy servers."; + config_ = fetched_config_; + config_->ClearAutomaticSettings(); + result = OK; + } + } + permanent_error_ = result; + + config_->set_source(fetched_config_->source()); + + // Resume any requests which we had to defer until the PAC script was + // downloaded. + SetReady(); +} + +bool ProxyResolutionService::MarkProxiesAsBadUntil( + const ProxyInfo& result, + base::TimeDelta retry_delay, + const std::vector<ProxyServer>& additional_bad_proxies, + const NetLogWithSource& net_log) { + result.proxy_list_.UpdateRetryInfoOnFallback(&proxy_retry_info_, retry_delay, + false, additional_bad_proxies, + OK, net_log); + return result.proxy_list_.size() > (additional_bad_proxies.size() + 1); +} + +void ProxyResolutionService::ReportSuccess(const ProxyInfo& result, + ProxyDelegate* proxy_delegate) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + const ProxyRetryInfoMap& new_retry_info = result.proxy_retry_info(); + if (new_retry_info.empty()) + return; + + for (ProxyRetryInfoMap::const_iterator iter = new_retry_info.begin(); + iter != new_retry_info.end(); ++iter) { + ProxyRetryInfoMap::iterator existing = proxy_retry_info_.find(iter->first); + if (existing == proxy_retry_info_.end()) { + proxy_retry_info_[iter->first] = iter->second; + if (proxy_delegate) { + const ProxyServer& bad_proxy = + ProxyServer::FromURI(iter->first, ProxyServer::SCHEME_HTTP); + const ProxyRetryInfo& proxy_retry_info = iter->second; + proxy_delegate->OnFallback(bad_proxy, proxy_retry_info.net_error); + } + } + else if (existing->second.bad_until < iter->second.bad_until) + existing->second.bad_until = iter->second.bad_until; + } + if (net_log_) { + net_log_->AddGlobalEntry( + NetLogEventType::BAD_PROXY_LIST_REPORTED, + base::Bind(&NetLogBadProxyListCallback, &new_retry_info)); + } +} + +void ProxyResolutionService::CancelRequest(Request* req) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(req); + req->Cancel(); + RemovePendingRequest(req); +} + +LoadState ProxyResolutionService::GetLoadState(const Request* req) const { + CHECK(req); + if (current_state_ == STATE_WAITING_FOR_INIT_PROXY_RESOLVER) + return init_proxy_resolver_->GetLoadState(); + return req->GetLoadState(); +} + +bool ProxyResolutionService::ContainsPendingRequest(Request* req) { + return pending_requests_.count(req) == 1; +} + +void ProxyResolutionService::RemovePendingRequest(Request* req) { + DCHECK(ContainsPendingRequest(req)); + pending_requests_.erase(req); +} + +int ProxyResolutionService::DidFinishResolvingProxy( + const GURL& url, + const std::string& method, + ProxyDelegate* proxy_delegate, + ProxyInfo* result, + int result_code, + const NetLogWithSource& net_log) { + // Log the result of the proxy resolution. + if (result_code == OK) { + // Allow the proxy delegate to interpose on the resolution decision, + // possibly modifying the ProxyInfo. + if (proxy_delegate) + proxy_delegate->OnResolveProxy(url, method, proxy_retry_info_, result); + + net_log.AddEvent(NetLogEventType::PROXY_SERVICE_RESOLVED_PROXY_LIST, + base::Bind(&NetLogFinishedResolvingProxyCallback, result)); + + // This check is done to only log the NetLog event when necessary, it's + // not a performance optimization. + if (!proxy_retry_info_.empty()) { + result->DeprioritizeBadProxies(proxy_retry_info_); + net_log.AddEvent( + NetLogEventType::PROXY_SERVICE_DEPRIORITIZED_BAD_PROXIES, + base::Bind(&NetLogFinishedResolvingProxyCallback, result)); + } + } else { + net_log.AddEventWithNetErrorCode( + NetLogEventType::PROXY_SERVICE_RESOLVED_PROXY_LIST, result_code); + + bool reset_config = result_code == ERR_PAC_SCRIPT_TERMINATED; + if (!config_->pac_mandatory()) { + // Fall-back to direct when the proxy resolver fails. This corresponds + // with a javascript runtime error in the PAC script. + // + // This implicit fall-back to direct matches Firefox 3.5 and + // Internet Explorer 8. For more information, see: + // + // http://www.chromium.org/developers/design-documents/proxy-settings-fallback + result->UseDirect(); + result_code = OK; + + // Allow the proxy delegate to interpose on the resolution decision, + // possibly modifying the ProxyInfo. + if (proxy_delegate) + proxy_delegate->OnResolveProxy(url, method, proxy_retry_info_, result); + } else { + result_code = ERR_MANDATORY_PROXY_CONFIGURATION_FAILED; + } + if (reset_config) { + ResetProxyConfig(false); + // If the ProxyResolver crashed, force it to be re-initialized for the + // next request by resetting the proxy config. If there are other pending + // requests, trigger the recreation immediately so those requests retry. + if (pending_requests_.size() > 1) + ApplyProxyConfigIfAvailable(); + } + } + + net_log.EndEvent(NetLogEventType::PROXY_SERVICE); + return result_code; +} + +void ProxyResolutionService::SetProxyScriptFetchers( + std::unique_ptr<ProxyScriptFetcher> proxy_script_fetcher, + std::unique_ptr<DhcpProxyScriptFetcher> dhcp_proxy_script_fetcher) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + State previous_state = ResetProxyConfig(false); + proxy_script_fetcher_ = std::move(proxy_script_fetcher); + dhcp_proxy_script_fetcher_ = std::move(dhcp_proxy_script_fetcher); + if (previous_state != STATE_NONE) + ApplyProxyConfigIfAvailable(); +} + +void ProxyResolutionService::OnShutdown() { + // Order here does not matter for correctness. |init_proxy_resolver_| is first + // because shutting it down also cancels its requests using the fetcher. + if (init_proxy_resolver_) + init_proxy_resolver_->OnShutdown(); + if (proxy_script_fetcher_) + proxy_script_fetcher_->OnShutdown(); + if (dhcp_proxy_script_fetcher_) + dhcp_proxy_script_fetcher_->OnShutdown(); +} + +ProxyScriptFetcher* ProxyResolutionService::GetProxyScriptFetcher() const { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + return proxy_script_fetcher_.get(); +} + +ProxyResolutionService::State ProxyResolutionService::ResetProxyConfig( + bool reset_fetched_config) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + State previous_state = current_state_; + + permanent_error_ = OK; + proxy_retry_info_.clear(); + script_poller_.reset(); + init_proxy_resolver_.reset(); + SuspendAllPendingRequests(); + resolver_.reset(); + config_ = base::Optional<ProxyConfig>(); + if (reset_fetched_config) + fetched_config_ = base::Optional<ProxyConfig>(); + current_state_ = STATE_NONE; + + return previous_state; +} + +void ProxyResolutionService::ResetConfigService( + std::unique_ptr<ProxyConfigService> new_proxy_config_service) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + State previous_state = ResetProxyConfig(true); + + // Release the old configuration service. + if (config_service_.get()) + config_service_->RemoveObserver(this); + + // Set the new configuration service. + config_service_ = std::move(new_proxy_config_service); + config_service_->AddObserver(this); + + if (previous_state != STATE_NONE) + ApplyProxyConfigIfAvailable(); +} + +void ProxyResolutionService::ForceReloadProxyConfig() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + ResetProxyConfig(false); + ApplyProxyConfigIfAvailable(); +} + +// static +std::unique_ptr<ProxyConfigService> +ProxyResolutionService::CreateSystemProxyConfigService( + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) { +#if defined(OS_WIN) + return std::make_unique<ProxyConfigServiceWin>(); +#elif defined(OS_IOS) + return std::make_unique<ProxyConfigServiceIOS>(); +#elif defined(OS_MACOSX) + return std::make_unique<ProxyConfigServiceMac>(io_task_runner); +#elif defined(OS_CHROMEOS) + LOG(ERROR) << "ProxyConfigService for ChromeOS should be created in " + << "profile_io_data.cc::CreateProxyConfigService and this should " + << "be used only for examples."; + return std::make_unique<UnsetProxyConfigService>(); +#elif defined(OS_LINUX) + std::unique_ptr<ProxyConfigServiceLinux> linux_config_service( + new ProxyConfigServiceLinux()); + + // Assume we got called on the thread that runs the default glib + // main loop, so the current thread is where we should be running + // gsettings calls from. + scoped_refptr<base::SingleThreadTaskRunner> glib_thread_task_runner = + base::ThreadTaskRunnerHandle::Get(); + + // Synchronously fetch the current proxy config (since we are running on + // glib_default_loop). Additionally register for notifications (delivered in + // either |glib_default_loop| or an internal sequenced task runner) to + // keep us updated when the proxy config changes. + linux_config_service->SetupAndFetchInitialConfig(glib_thread_task_runner, + io_task_runner); + + return std::move(linux_config_service); +#elif defined(OS_ANDROID) + return std::make_unique<ProxyConfigServiceAndroid>( + io_task_runner, base::ThreadTaskRunnerHandle::Get()); +#else + LOG(WARNING) << "Failed to choose a system proxy settings fetcher " + "for this platform."; + return std::make_unique<ProxyConfigServiceDirect>(); +#endif +} + +// static +const ProxyResolutionService::PacPollPolicy* +ProxyResolutionService::set_pac_script_poll_policy( + const PacPollPolicy* policy) { + return ProxyScriptDeciderPoller::set_policy(policy); +} + +// static +std::unique_ptr<ProxyResolutionService::PacPollPolicy> +ProxyResolutionService::CreateDefaultPacPollPolicy() { + return std::unique_ptr<PacPollPolicy>(new DefaultPollPolicy()); +} + +void ProxyResolutionService::OnProxyConfigChanged( + const ProxyConfig& config, + ProxyConfigService::ConfigAvailability availability) { + // Retrieve the current proxy configuration from the ProxyConfigService. + // If a configuration is not available yet, we will get called back later + // by our ProxyConfigService::Observer once it changes. + ProxyConfig effective_config; + switch (availability) { + case ProxyConfigService::CONFIG_PENDING: + // ProxyConfigService implementors should never pass CONFIG_PENDING. + NOTREACHED() << "Proxy config change with CONFIG_PENDING availability!"; + return; + case ProxyConfigService::CONFIG_VALID: + effective_config = config; + break; + case ProxyConfigService::CONFIG_UNSET: + effective_config = ProxyConfig::CreateDirect(); + break; + } + + // Emit the proxy settings change to the NetLog stream. + if (net_log_) { + net_log_->AddGlobalEntry(NetLogEventType::PROXY_CONFIG_CHANGED, + base::Bind(&NetLogProxyConfigChangedCallback, + &fetched_config_, &effective_config)); + } + + // Set the new configuration as the most recently fetched one. + fetched_config_ = effective_config; + + InitializeUsingLastFetchedConfig(); +} + +void ProxyResolutionService::InitializeUsingLastFetchedConfig() { + ResetProxyConfig(false); + + DCHECK(fetched_config_); + if (!fetched_config_->HasAutomaticSettings()) { + config_ = fetched_config_; + SetReady(); + return; + } + + // Start downloading + testing the PAC scripts for this new configuration. + current_state_ = STATE_WAITING_FOR_INIT_PROXY_RESOLVER; + + // If we changed networks recently, we should delay running proxy auto-config. + TimeDelta wait_delay = + stall_proxy_autoconfig_until_ - TimeTicks::Now(); + + init_proxy_resolver_.reset(new InitProxyResolver()); + init_proxy_resolver_->set_quick_check_enabled(quick_check_enabled_); + int rv = init_proxy_resolver_->Start( + &resolver_, resolver_factory_.get(), proxy_script_fetcher_.get(), + dhcp_proxy_script_fetcher_.get(), net_log_, fetched_config_.value(), + wait_delay, + base::Bind(&ProxyResolutionService::OnInitProxyResolverComplete, + base::Unretained(this))); + + if (rv != ERR_IO_PENDING) + OnInitProxyResolverComplete(rv); +} + +void ProxyResolutionService::InitializeUsingDecidedConfig( + int decider_result, + ProxyResolverScriptData* script_data, + const ProxyConfig& effective_config) { + DCHECK(fetched_config_); + DCHECK(fetched_config_->HasAutomaticSettings()); + + ResetProxyConfig(false); + + current_state_ = STATE_WAITING_FOR_INIT_PROXY_RESOLVER; + + init_proxy_resolver_.reset(new InitProxyResolver()); + int rv = init_proxy_resolver_->StartSkipDecider( + &resolver_, resolver_factory_.get(), effective_config, decider_result, + script_data, + base::Bind(&ProxyResolutionService::OnInitProxyResolverComplete, + base::Unretained(this))); + + if (rv != ERR_IO_PENDING) + OnInitProxyResolverComplete(rv); +} + +void ProxyResolutionService::OnIPAddressChanged() { + // See the comment block by |kDelayAfterNetworkChangesMs| for info. + stall_proxy_autoconfig_until_ = + TimeTicks::Now() + stall_proxy_auto_config_delay_; + + State previous_state = ResetProxyConfig(false); + if (previous_state != STATE_NONE) + ApplyProxyConfigIfAvailable(); +} + +void ProxyResolutionService::OnDNSChanged() { + OnIPAddressChanged(); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_service.h b/chromium/net/proxy_resolution/proxy_service.h new file mode 100644 index 00000000000..e7c867a91e0 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_service.h @@ -0,0 +1,464 @@ +// Copyright (c) 2012 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_RESOLUTION_PROXY_SERVICE_H_ +#define NET_PROXY_RESOLUTION_PROXY_SERVICE_H_ + +#include <stddef.h> + +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/optional.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_checker.h" +#include "net/base/completion_callback.h" +#include "net/base/load_states.h" +#include "net/base/net_export.h" +#include "net/base/network_change_notifier.h" +#include "net/base/proxy_server.h" +#include "net/proxy_resolution/proxy_config_service.h" +#include "net/proxy_resolution/proxy_info.h" +#include "url/gurl.h" + +class GURL; + +namespace base { +class SequencedTaskRunner; +class TimeDelta; +} // namespace base + +namespace net { + +class DhcpProxyScriptFetcher; +class NetLog; +class NetLogWithSource; +class ProxyDelegate; +class ProxyResolver; +class ProxyResolverFactory; +class ProxyResolverScriptData; +class ProxyScriptFetcher; + +// This class can be used to resolve the proxy server to use when loading a +// HTTP(S) URL. It uses the given ProxyResolver to handle the actual proxy +// resolution. See ProxyResolverV8 for example. +class NET_EXPORT ProxyResolutionService + : public NetworkChangeNotifier::IPAddressObserver, + public NetworkChangeNotifier::DNSObserver, + public ProxyConfigService::Observer { + public: + // Enumerates the policy to use when sanitizing URLs for proxy resolution + // (before passing them off to PAC scripts). + enum class SanitizeUrlPolicy { + // Do a basic level of sanitization for URLs: + // - strip embedded identities (ex: "username:password@") + // - strip the fragment (ex: "#blah") + // + // This is considered "unsafe" because it does not do any additional + // stripping for https:// URLs. + UNSAFE, + + // SAFE does the same sanitization as UNSAFE, but additionally strips + // everything but the (scheme,host,port) from cryptographic URL schemes + // (https:// and wss://). + // + // In other words, it strips the path and query portion of https:// URLs. + SAFE, + }; + + // This interface defines the set of policies for when to poll the PAC + // script for changes. + // + // The polling policy decides what the next poll delay should be in + // milliseconds. It also decides how to wait for this delay -- either + // by starting a timer to do the poll at exactly |next_delay_ms| + // (MODE_USE_TIMER) or by waiting for the first network request issued after + // |next_delay_ms| (MODE_START_AFTER_ACTIVITY). + // + // The timer method is more precise and guarantees that polling happens when + // it was requested. However it has the disadvantage of causing spurious CPU + // and network activity. It is a reasonable choice to use for short poll + // intervals which only happen a couple times. + // + // However for repeated timers this will prevent the browser from going + // idle. MODE_START_AFTER_ACTIVITY solves this problem by only polling in + // direct response to network activity. The drawback to + // MODE_START_AFTER_ACTIVITY is since the poll is initiated only after the + // request is received, the first couple requests initiated after a long + // period of inactivity will likely see a stale version of the PAC script + // until the background polling gets a chance to update things. + class NET_EXPORT_PRIVATE PacPollPolicy { + public: + enum Mode { + MODE_USE_TIMER, + MODE_START_AFTER_ACTIVITY, + }; + + virtual ~PacPollPolicy() {} + + // Decides the next poll delay. |current_delay| is the delay used + // by the preceding poll, or a negative TimeDelta value if determining + // the delay for the initial poll. |initial_error| is the network error + // code that the last PAC fetch (or WPAD initialization) failed with, + // or OK if it completed successfully. Implementations must set + // |next_delay| to a non-negative value. + virtual Mode GetNextDelay(int initial_error, + base::TimeDelta current_delay, + base::TimeDelta* next_delay) const = 0; + }; + + // |net_log| is a possibly NULL destination to send log events to. It must + // remain alive for the lifetime of this ProxyResolutionService. + ProxyResolutionService(std::unique_ptr<ProxyConfigService> config_service, + std::unique_ptr<ProxyResolverFactory> resolver_factory, + NetLog* net_log); + + ~ProxyResolutionService() override; + + // Used to track proxy resolution requests that complete asynchronously. + class Request; + + // Determines the appropriate proxy for |url| for a |method| request and + // stores the result in |results|. If |method| is empty, the caller can expect + // method independent resolution. + // + // Returns ERR_IO_PENDING if the proxy information could not be provided + // synchronously, to indicate that the result will be available when the + // callback is run. The callback is run on the thread that calls + // ResolveProxy. + // + // The caller is responsible for ensuring that |results| and |callback| + // remain valid until the callback is run or until |request| is cancelled + // via CancelRequest. |request| is only valid while the completion + // callback is still pending. NULL can be passed for |request| if + // the caller will not need to cancel the request. + // + // We use the three possible proxy access types in the following order, + // doing fallback if one doesn't work. See "pac_script_decider.h" + // for the specifics. + // 1. WPAD auto-detection + // 2. PAC URL + // 3. named proxy + // + // Profiling information for the request is saved to |net_log| if non-NULL. + int ResolveProxy(const GURL& url, + const std::string& method, + ProxyInfo* results, + const CompletionCallback& callback, + Request** request, + ProxyDelegate* proxy_delegate, + const NetLogWithSource& net_log); + + // Returns true if the proxy information could be determined without spawning + // an asynchronous task. Otherwise, |result| is unmodified. + bool TryResolveProxySynchronously(const GURL& raw_url, + const std::string& method, + ProxyInfo* result, + ProxyDelegate* proxy_delegate, + const NetLogWithSource& net_log); + + // Explicitly trigger proxy fallback for the given |results| by updating our + // list of bad proxies to include the first entry of |results|, and, + // additional bad proxies (can be none). Will retry after |retry_delay| if + // positive, and will use the default proxy retry duration otherwise. Proxies + // marked as bad will not be retried until |retry_delay| has passed. Returns + // true if there will be at least one proxy remaining in the list after + // fallback and false otherwise. This method should be used to add proxies to + // the bad proxy list only for reasons other than a network error. + bool MarkProxiesAsBadUntil( + const ProxyInfo& results, + base::TimeDelta retry_delay, + const std::vector<ProxyServer>& additional_bad_proxies, + const NetLogWithSource& net_log); + + // Called to report that the last proxy connection succeeded. If |proxy_info| + // has a non empty proxy_retry_info map, the proxies that have been tried (and + // failed) for this request will be marked as bad. If non-null, + // |proxy_delegate| will be notified of any proxy fallbacks. + void ReportSuccess(const ProxyInfo& proxy_info, + ProxyDelegate* proxy_delegate); + + // Call this method with a non-null |request| to cancel the PAC request. + void CancelRequest(Request* request); + + // Returns the LoadState for this |request| which must be non-NULL. + LoadState GetLoadState(const Request* request) const; + + // Sets the ProxyScriptFetcher and DhcpProxyScriptFetcher dependencies. This + // is needed if the ProxyResolver is of type ProxyResolverWithoutFetch. + void SetProxyScriptFetchers( + std::unique_ptr<ProxyScriptFetcher> proxy_script_fetcher, + std::unique_ptr<DhcpProxyScriptFetcher> dhcp_proxy_script_fetcher); + ProxyScriptFetcher* GetProxyScriptFetcher() const; + + // Cancels all network requests, and prevents the service from creating new + // ones. Must be called before the URLRequestContext the + // ProxyResolutionService was created with is torn down, if it's torn down + // before th ProxyResolutionService itself. + void OnShutdown(); + + // Tells this ProxyResolutionService to start using a new ProxyConfigService + // to retrieve its ProxyConfig from. The new ProxyConfigService will + // immediately be queried for new config info which will be used for all + // subsequent ResolveProxy calls. + void ResetConfigService( + std::unique_ptr<ProxyConfigService> new_proxy_config_service); + + // Returns the last configuration fetched from ProxyConfigService. + const base::Optional<ProxyConfig>& fetched_config() const { + return fetched_config_; + } + + // Returns the current configuration being used by ProxyConfigService. + const base::Optional<ProxyConfig>& config() const { return config_; } + + // Returns the map of proxies which have been marked as "bad". + const ProxyRetryInfoMap& proxy_retry_info() const { + return proxy_retry_info_; + } + + // Clears the list of bad proxy servers that has been cached. + void ClearBadProxiesCache() { + proxy_retry_info_.clear(); + } + + // Forces refetching the proxy configuration, and applying it. + // This re-does everything from fetching the system configuration, + // to downloading and testing the PAC files. + void ForceReloadProxyConfig(); + + // Same as CreateProxyServiceUsingV8ProxyResolver, except it uses system + // libraries for evaluating the PAC script if available, otherwise skips + // proxy autoconfig. + static std::unique_ptr<ProxyResolutionService> CreateUsingSystemProxyResolver( + std::unique_ptr<ProxyConfigService> proxy_config_service, + NetLog* net_log); + + // Creates a ProxyResolutionService without support for proxy autoconfig. + static std::unique_ptr<ProxyResolutionService> CreateWithoutProxyResolver( + std::unique_ptr<ProxyConfigService> proxy_config_service, + NetLog* net_log); + + // Convenience methods that creates a proxy service using the + // specified fixed settings. + static std::unique_ptr<ProxyResolutionService> CreateFixed( + const ProxyConfig& pc); + static std::unique_ptr<ProxyResolutionService> CreateFixed( + const std::string& proxy); + + // Creates a proxy service that uses a DIRECT connection for all requests. + static std::unique_ptr<ProxyResolutionService> CreateDirect(); + // |net_log|'s lifetime must exceed ProxyResolutionService. + static std::unique_ptr<ProxyResolutionService> CreateDirectWithNetLog( + NetLog* net_log); + + // This method is used by tests to create a ProxyResolutionService that + // returns a hardcoded proxy fallback list (|pac_string|) for every URL. + // + // |pac_string| is a list of proxy servers, in the format that a PAC script + // would return it. For example, "PROXY foobar:99; SOCKS fml:2; DIRECT" + static std::unique_ptr<ProxyResolutionService> CreateFixedFromPacResult( + const std::string& pac_string); + + // Creates a config service appropriate for this platform that fetches the + // system proxy settings. + static std::unique_ptr<ProxyConfigService> CreateSystemProxyConfigService( + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner); + + // This method should only be used by unit tests. + void set_stall_proxy_auto_config_delay(base::TimeDelta delay) { + stall_proxy_auto_config_delay_ = delay; + } + + // This method should only be used by unit tests. Returns the previously + // active policy. + static const PacPollPolicy* set_pac_script_poll_policy( + const PacPollPolicy* policy); + + // This method should only be used by unit tests. Creates an instance + // of the default internal PacPollPolicy used by ProxyResolutionService. + static std::unique_ptr<PacPollPolicy> CreateDefaultPacPollPolicy(); + + void set_quick_check_enabled(bool value) { + quick_check_enabled_ = value; + } + + void set_sanitize_url_policy(SanitizeUrlPolicy policy) { + sanitize_url_policy_ = policy; + } + + private: + FRIEND_TEST_ALL_PREFIXES(ProxyServiceTest, UpdateConfigAfterFailedAutodetect); + FRIEND_TEST_ALL_PREFIXES(ProxyServiceTest, UpdateConfigFromPACToDirect); + friend class Request; + class InitProxyResolver; + class ProxyScriptDeciderPoller; + + typedef std::set<scoped_refptr<Request>> PendingRequests; + + enum State { + STATE_NONE, + STATE_WAITING_FOR_PROXY_CONFIG, + STATE_WAITING_FOR_INIT_PROXY_RESOLVER, + STATE_READY, + }; + + // Resets all the variables associated with the current proxy configuration, + // and rewinds the current state to |STATE_NONE|. Returns the previous value + // of |current_state_|. If |reset_fetched_config| is true then + // |fetched_config_| will also be reset, otherwise it will be left as-is. + // Resetting it means that we will have to re-fetch the configuration from + // the ProxyConfigService later. + State ResetProxyConfig(bool reset_fetched_config); + + // Retrieves the current proxy configuration from the ProxyConfigService, and + // starts initializing for it. + void ApplyProxyConfigIfAvailable(); + + // Callback for when the proxy resolver has been initialized with a + // PAC script. + void OnInitProxyResolverComplete(int result); + + // Returns ERR_IO_PENDING if the request cannot be completed synchronously. + // Otherwise it fills |result| with the proxy information for |url|. + // Completing synchronously means we don't need to query ProxyResolver. + int TryToCompleteSynchronously(const GURL& url, + ProxyDelegate* proxy_delegate, + ProxyInfo* result); + + // Identical to ResolveProxy, except that |callback| is permitted to be null. + // if |callback.is_null()|, this function becomes a thin wrapper around + // |TryToCompleteSynchronously|. + int ResolveProxyHelper(const GURL& url, + const std::string& method, + ProxyInfo* results, + const CompletionCallback& callback, + Request** request, + ProxyDelegate* proxy_delegate, + const NetLogWithSource& net_log); + + // Cancels all of the requests sent to the ProxyResolver. These will be + // restarted when calling SetReady(). + void SuspendAllPendingRequests(); + + // Advances the current state to |STATE_READY|, and resumes any pending + // requests which had been stalled waiting for initialization to complete. + void SetReady(); + + // Returns true if |pending_requests_| contains |req|. + bool ContainsPendingRequest(Request* req); + + // Removes |req| from the list of pending requests. + void RemovePendingRequest(Request* req); + + // Called when proxy resolution has completed (either synchronously or + // asynchronously). Handles logging the result, and cleaning out + // bad entries from the results list. + int DidFinishResolvingProxy(const GURL& url, + const std::string& method, + ProxyDelegate* proxy_delegate, + ProxyInfo* result, + int result_code, + const NetLogWithSource& net_log); + + // Start initialization using |fetched_config_|. + void InitializeUsingLastFetchedConfig(); + + // Start the initialization skipping past the "decision" phase. + void InitializeUsingDecidedConfig( + int decider_result, + ProxyResolverScriptData* script_data, + const ProxyConfig& effective_config); + + // NetworkChangeNotifier::IPAddressObserver + // When this is called, we re-fetch PAC scripts and re-run WPAD. + void OnIPAddressChanged() override; + + // NetworkChangeNotifier::DNSObserver + // We respond as above. + void OnDNSChanged() override; + + // ProxyConfigService::Observer + void OnProxyConfigChanged( + const ProxyConfig& config, + ProxyConfigService::ConfigAvailability availability) override; + + std::unique_ptr<ProxyConfigService> config_service_; + std::unique_ptr<ProxyResolverFactory> resolver_factory_; + std::unique_ptr<ProxyResolver> resolver_; + + // We store the proxy configuration that was last fetched from the + // ProxyConfigService, as well as the resulting "effective" configuration. + // The effective configuration is what we condense the original fetched + // settings to after testing the various automatic settings (auto-detect + // and custom PAC url). + // + // These are "optional" as their value remains unset while being calculated. + base::Optional<ProxyConfig> fetched_config_; + base::Optional<ProxyConfig> config_; + + // The time when the proxy configuration was last read from the system. + base::TimeTicks config_last_update_time_; + + // Map of the known bad proxies and the information about the retry time. + ProxyRetryInfoMap proxy_retry_info_; + + // Set of pending/inprogress requests. + PendingRequests pending_requests_; + + // The fetcher to use when downloading PAC scripts for the ProxyResolver. + // This dependency can be NULL if our ProxyResolver has no need for + // external PAC script fetching. + std::unique_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. + std::unique_ptr<DhcpProxyScriptFetcher> dhcp_proxy_script_fetcher_; + + // Helper to download the PAC script (wpad + custom) and apply fallback rules. + // + // Note that the declaration is important here: |proxy_script_fetcher_| and + // |proxy_resolver_| must outlive |init_proxy_resolver_|. + std::unique_ptr<InitProxyResolver> init_proxy_resolver_; + + // Helper to poll the PAC script for changes. + std::unique_ptr<ProxyScriptDeciderPoller> script_poller_; + + State current_state_; + + // Either OK or an ERR_* value indicating that a permanent error (e.g. + // failed to fetch the PAC script) prevents proxy resolution. + int permanent_error_; + + // This is the log where any events generated by |init_proxy_resolver_| are + // sent to. + NetLog* net_log_; + + // The earliest time at which we should run any proxy auto-config. (Used to + // stall re-configuration following an IP address change). + base::TimeTicks stall_proxy_autoconfig_until_; + + // The amount of time to stall requests following IP address changes. + base::TimeDelta stall_proxy_auto_config_delay_; + + // Whether child ProxyScriptDeciders should use QuickCheck + bool quick_check_enabled_; + + // The method to use for sanitizing URLs seen by the proxy resolver. + SanitizeUrlPolicy sanitize_url_policy_; + + THREAD_CHECKER(thread_checker_); + + DISALLOW_COPY_AND_ASSIGN(ProxyResolutionService); +}; + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PROXY_SERVICE_H_ diff --git a/chromium/net/proxy_resolution/proxy_service_unittest.cc b/chromium/net/proxy_resolution/proxy_service_unittest.cc new file mode 100644 index 00000000000..5efa61aaa54 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_service_unittest.cc @@ -0,0 +1,3587 @@ +// Copyright (c) 2012 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_resolution/proxy_service.h" + +#include <cstdarg> +#include <string> +#include <vector> + +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/run_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/net_errors.h" +#include "net/base/proxy_delegate.h" +#include "net/base/proxy_server.h" +#include "net/base/test_completion_callback.h" +#include "net/log/net_log_event_type.h" +#include "net/log/net_log_with_source.h" +#include "net/log/test_net_log.h" +#include "net/log/test_net_log_entry.h" +#include "net/log/test_net_log_util.h" +#include "net/proxy_resolution/dhcp_pac_file_fetcher.h" +#include "net/proxy_resolution/mock_pac_file_fetcher.h" +#include "net/proxy_resolution/mock_proxy_resolver.h" +#include "net/proxy_resolution/pac_file_fetcher.h" +#include "net/proxy_resolution/proxy_config_service.h" +#include "net/proxy_resolution/proxy_resolver.h" +#include "net/test/gtest_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using testing::ElementsAre; +using testing::Key; + +using net::test::IsError; +using net::test::IsOk; + +using base::ASCIIToUTF16; + +// TODO(eroman): Write a test which exercises +// ProxyResolutionService::SuspendAllPendingRequests(). +namespace net { +namespace { + +// This polling policy will decide to poll every 1 ms. +class ImmediatePollPolicy : public ProxyResolutionService::PacPollPolicy { + public: + ImmediatePollPolicy() = default; + + Mode GetNextDelay(int error, + base::TimeDelta current_delay, + base::TimeDelta* next_delay) const override { + *next_delay = base::TimeDelta::FromMilliseconds(1); + return MODE_USE_TIMER; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ImmediatePollPolicy); +}; + +// This polling policy chooses a fantastically large delay. In other words, it +// will never trigger a poll +class NeverPollPolicy : public ProxyResolutionService::PacPollPolicy { + public: + NeverPollPolicy() = default; + + Mode GetNextDelay(int error, + base::TimeDelta current_delay, + base::TimeDelta* next_delay) const override { + *next_delay = base::TimeDelta::FromDays(60); + return MODE_USE_TIMER; + } + + private: + DISALLOW_COPY_AND_ASSIGN(NeverPollPolicy); +}; + +// This polling policy starts a poll immediately after network activity. +class ImmediateAfterActivityPollPolicy + : public ProxyResolutionService::PacPollPolicy { + public: + ImmediateAfterActivityPollPolicy() = default; + + Mode GetNextDelay(int error, + base::TimeDelta current_delay, + base::TimeDelta* next_delay) const override { + *next_delay = base::TimeDelta(); + return MODE_START_AFTER_ACTIVITY; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ImmediateAfterActivityPollPolicy); +}; + +// This test fixture is used to partially disable the background polling done by +// the ProxyResolutionService (which it uses to detect whenever its PAC script +// contents or WPAD results have changed). +// +// We disable the feature by setting the poll interval to something really +// large, so it will never actually be reached even on the slowest bots that run +// these tests. +// +// We disable the polling in order to avoid any timing dependencies in the +// tests. If the bot were to run the tests very slowly and we hadn't disabled +// polling, then it might start a background re-try in the middle of our test +// and confuse our expectations leading to flaky failures. +// +// The tests which verify the polling code re-enable the polling behavior but +// are careful to avoid timing problems. +class ProxyServiceTest : public testing::Test { + protected: + void SetUp() override { + testing::Test::SetUp(); + previous_policy_ = + ProxyResolutionService::set_pac_script_poll_policy(&never_poll_policy_); + } + + void TearDown() override { + // Restore the original policy. + ProxyResolutionService::set_pac_script_poll_policy(previous_policy_); + testing::Test::TearDown(); + } + + private: + NeverPollPolicy never_poll_policy_; + const ProxyResolutionService::PacPollPolicy* previous_policy_; +}; + +const char kValidPacScript1[] = "pac-script-v1-FindProxyForURL"; +const char kValidPacScript2[] = "pac-script-v2-FindProxyForURL"; + +class MockProxyConfigService: public ProxyConfigService { + public: + explicit MockProxyConfigService(const ProxyConfig& config) + : availability_(CONFIG_VALID), + config_(config) { + } + + explicit MockProxyConfigService(const std::string& pac_url) + : availability_(CONFIG_VALID), + config_(ProxyConfig::CreateFromCustomPacURL(GURL(pac_url))) { + } + + void AddObserver(Observer* observer) override { + observers_.AddObserver(observer); + } + + void RemoveObserver(Observer* observer) override { + observers_.RemoveObserver(observer); + } + + ConfigAvailability GetLatestProxyConfig(ProxyConfig* results) override { + if (availability_ == CONFIG_VALID) + *results = config_; + return availability_; + } + + void SetConfig(const ProxyConfig& config) { + availability_ = CONFIG_VALID; + config_ = config; + for (auto& observer : observers_) + observer.OnProxyConfigChanged(config_, availability_); + } + + private: + ConfigAvailability availability_; + ProxyConfig config_; + base::ObserverList<Observer, true> observers_; +}; + +// A test network delegate that exercises the OnResolveProxy callback. +class TestResolveProxyDelegate : public ProxyDelegate { + public: + TestResolveProxyDelegate() + : on_resolve_proxy_called_(false), + add_proxy_(false), + remove_proxy_(false) {} + + void OnResolveProxy(const GURL& url, + const std::string& method, + const ProxyRetryInfoMap& proxy_retry_info, + ProxyInfo* result) override { + method_ = method; + on_resolve_proxy_called_ = true; + proxy_retry_info_ = proxy_retry_info; + DCHECK(!add_proxy_ || !remove_proxy_); + if (add_proxy_) { + result->UseNamedProxy("delegate_proxy.com"); + } else if (remove_proxy_) { + result->UseDirect(); + } + } + + bool on_resolve_proxy_called() const { + return on_resolve_proxy_called_; + } + + const std::string& method() const { return method_; } + + void set_add_proxy(bool add_proxy) { + add_proxy_ = add_proxy; + } + + void set_remove_proxy(bool remove_proxy) { + remove_proxy_ = remove_proxy; + } + + const ProxyRetryInfoMap& proxy_retry_info() const { + return proxy_retry_info_; + } + + void OnFallback(const ProxyServer& bad_proxy, int net_error) override {} + + private: + bool on_resolve_proxy_called_; + bool add_proxy_; + bool remove_proxy_; + std::string method_; + ProxyRetryInfoMap proxy_retry_info_; +}; + +// A test network delegate that exercises the OnProxyFallback callback. +class TestProxyFallbackProxyDelegate : public ProxyDelegate { + public: + TestProxyFallbackProxyDelegate() + : on_proxy_fallback_called_(false), proxy_fallback_net_error_(OK) {} + + // ProxyDelegate implementation: + void OnResolveProxy(const GURL& url, + const std::string& method, + const ProxyRetryInfoMap& proxy_retry_info, + ProxyInfo* result) override {} + void OnFallback(const ProxyServer& bad_proxy, int net_error) override { + proxy_server_ = bad_proxy; + proxy_fallback_net_error_ = net_error; + on_proxy_fallback_called_ = true; + } + + bool on_proxy_fallback_called() const { + return on_proxy_fallback_called_; + } + + const ProxyServer& proxy_server() const { + return proxy_server_; + } + + int proxy_fallback_net_error() const { + return proxy_fallback_net_error_; + } + + private: + bool on_proxy_fallback_called_; + ProxyServer proxy_server_; + int proxy_fallback_net_error_; +}; + +using JobMap = std::map<GURL, MockAsyncProxyResolver::Job*>; + +// Given a jobmap and a list of target URLs |urls|, asserts that the set of URLs +// of the jobs appearing in |list| is exactly the set of URLs in |urls|. +JobMap GetJobsForURLs(const JobMap& map, const std::vector<GURL>& urls) { + size_t a = urls.size(); + size_t b = map.size(); + if (a != b) { + ADD_FAILURE() << "map size (" << map.size() << ") != urls size (" + << urls.size() << ")"; + return map; + } + for (const auto& it : urls) { + if (map.count(it) != 1U) { + ADD_FAILURE() << "url not in map: " << it.spec(); + break; + } + } + return map; +} + +// Given a MockAsyncProxyResolver |resolver| and some GURLs, validates that the +// set of pending request URLs for |resolver| is exactly the supplied list of +// URLs and returns a map from URLs to the corresponding pending jobs. +JobMap GetPendingJobsForURLs(const MockAsyncProxyResolver& resolver, + const GURL& url1 = GURL(), + const GURL& url2 = GURL(), + const GURL& url3 = GURL()) { + std::vector<GURL> urls; + if (!url1.is_empty()) + urls.push_back(url1); + if (!url2.is_empty()) + urls.push_back(url2); + if (!url3.is_empty()) + urls.push_back(url3); + + JobMap map; + for (MockAsyncProxyResolver::Job* it : resolver.pending_jobs()) { + DCHECK(it); + map[it->url()] = it; + } + + return GetJobsForURLs(map, urls); +} + +// Given a MockAsyncProxyResolver |resolver| and some GURLs, validates that the +// set of cancelled request URLs for |resolver| is exactly the supplied list of +// URLs and returns a map from URLs to the corresponding cancelled jobs. +JobMap GetCancelledJobsForURLs(const MockAsyncProxyResolver& resolver, + const GURL& url1 = GURL(), + const GURL& url2 = GURL(), + const GURL& url3 = GURL()) { + std::vector<GURL> urls; + if (!url1.is_empty()) + urls.push_back(url1); + if (!url2.is_empty()) + urls.push_back(url2); + if (!url3.is_empty()) + urls.push_back(url3); + + JobMap map; + for (const std::unique_ptr<MockAsyncProxyResolver::Job>& it : + resolver.cancelled_jobs()) { + DCHECK(it); + map[it->url()] = it.get(); + } + + return GetJobsForURLs(map, urls); +} + +} // namespace + +TEST_F(ProxyServiceTest, Direct) { + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(ProxyConfig::CreateDirect()), + base::WrapUnique(factory), nullptr); + + GURL url("http://www.google.com/"); + + ProxyInfo info; + TestCompletionCallback callback; + BoundTestNetLog log; + int rv = service.ResolveProxy(url, std::string(), &info, callback.callback(), + nullptr, nullptr, log.bound()); + EXPECT_THAT(rv, IsOk()); + EXPECT_TRUE(factory->pending_requests().empty()); + + EXPECT_TRUE(info.is_direct()); + EXPECT_TRUE(info.proxy_resolve_start_time().is_null()); + EXPECT_TRUE(info.proxy_resolve_end_time().is_null()); + + // Check the NetLog was filled correctly. + TestNetLogEntry::List entries; + log.GetEntries(&entries); + + EXPECT_EQ(3u, entries.size()); + EXPECT_TRUE( + LogContainsBeginEvent(entries, 0, NetLogEventType::PROXY_SERVICE)); + EXPECT_TRUE(LogContainsEvent( + entries, 1, NetLogEventType::PROXY_SERVICE_RESOLVED_PROXY_LIST, + NetLogEventPhase::NONE)); + EXPECT_TRUE(LogContainsEndEvent(entries, 2, NetLogEventType::PROXY_SERVICE)); +} + +TEST_F(ProxyServiceTest, OnResolveProxyCallbackAddProxy) { + ProxyConfig config; + config.proxy_rules().ParseFromString("badproxy:8080,foopy1:8080"); + config.set_auto_detect(false); + config.proxy_rules().bypass_rules.ParseFromString("*.org"); + + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + + GURL url("http://www.google.com/"); + GURL bypass_url("http://internet.org"); + + ProxyInfo info; + TestCompletionCallback callback; + BoundTestNetLog log; + + // First, warm up the ProxyResolutionService and fake an error to mark the + // first server as bad. + int rv = service.ResolveProxy(url, std::string(), &info, callback.callback(), + nullptr, nullptr, log.bound()); + EXPECT_THAT(rv, IsOk()); + EXPECT_EQ("badproxy:8080", info.proxy_server().ToURI()); + + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + service.ReportSuccess(info, nullptr); + + // Verify that network delegate is invoked. + TestResolveProxyDelegate delegate; + rv = service.ResolveProxy(url, "GET", &info, callback.callback(), nullptr, + &delegate, log.bound()); + EXPECT_TRUE(delegate.on_resolve_proxy_called()); + EXPECT_THAT(delegate.proxy_retry_info(), ElementsAre(Key("badproxy:8080"))); + EXPECT_EQ(delegate.method(), "GET"); + + // Verify that the ProxyDelegate's behavior is stateless across + // invocations of ResolveProxy. Start by having the callback add a proxy + // and checking that subsequent jobs are not affected. + delegate.set_add_proxy(true); + + // Callback should interpose: + rv = service.ResolveProxy(url, "GET", &info, callback.callback(), nullptr, + &delegate, log.bound()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server().host_port_pair().host(), "delegate_proxy.com"); + delegate.set_add_proxy(false); + + // Check non-bypassed URL: + rv = service.ResolveProxy(url, "GET", &info, callback.callback(), nullptr, + &delegate, log.bound()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server().host_port_pair().host(), "foopy1"); + + // Check bypassed URL: + rv = service.ResolveProxy(bypass_url, "GET", &info, callback.callback(), + nullptr, &delegate, log.bound()); + EXPECT_TRUE(info.is_direct()); +} + +TEST_F(ProxyServiceTest, OnResolveProxyCallbackRemoveProxy) { + // Same as OnResolveProxyCallbackAddProxy, but verify that the + // ProxyDelegate's behavior is stateless across invocations after it + // *removes* a proxy. + ProxyConfig config; + config.proxy_rules().ParseFromString("foopy1:8080"); + config.set_auto_detect(false); + config.proxy_rules().bypass_rules.ParseFromString("*.org"); + + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + + GURL url("http://www.google.com/"); + GURL bypass_url("http://internet.org"); + + ProxyInfo info; + TestCompletionCallback callback; + BoundTestNetLog log; + + // First, warm up the ProxyResolutionService. + int rv = service.ResolveProxy(url, std::string(), &info, callback.callback(), + nullptr, nullptr, log.bound()); + EXPECT_THAT(rv, IsOk()); + + TestResolveProxyDelegate delegate; + delegate.set_remove_proxy(true); + + // Callback should interpose: + rv = service.ResolveProxy(url, "GET", &info, callback.callback(), nullptr, + &delegate, log.bound()); + EXPECT_TRUE(info.is_direct()); + delegate.set_remove_proxy(false); + + // Check non-bypassed URL: + rv = service.ResolveProxy(url, "GET", &info, callback.callback(), nullptr, + &delegate, log.bound()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server().host_port_pair().host(), "foopy1"); + + // Check bypassed URL: + rv = service.ResolveProxy(bypass_url, "GET", &info, callback.callback(), + nullptr, &delegate, log.bound()); + EXPECT_TRUE(info.is_direct()); +} + +TEST_F(ProxyServiceTest, PAC) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + GURL url("http://www.google.com/"); + + ProxyInfo info; + TestCompletionCallback callback; + ProxyResolutionService::Request* request; + BoundTestNetLog log; + + int rv = service.ResolveProxy(url, std::string(), &info, callback.callback(), + &request, nullptr, log.bound()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL, service.GetLoadState(request)); + + ASSERT_EQ(1u, factory->pending_requests().size()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // Set the result in proxy resolver. + resolver.pending_jobs()[0]->results()->UseNamedProxy("foopy"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy:80", info.proxy_server().ToURI()); + EXPECT_TRUE(info.did_use_pac_script()); + + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time()); + + // Check the NetLog was filled correctly. + TestNetLogEntry::List entries; + log.GetEntries(&entries); + + EXPECT_EQ(5u, entries.size()); + EXPECT_TRUE( + LogContainsBeginEvent(entries, 0, NetLogEventType::PROXY_SERVICE)); + EXPECT_TRUE(LogContainsBeginEvent( + entries, 1, NetLogEventType::PROXY_SERVICE_WAITING_FOR_INIT_PAC)); + EXPECT_TRUE(LogContainsEndEvent( + entries, 2, NetLogEventType::PROXY_SERVICE_WAITING_FOR_INIT_PAC)); + EXPECT_TRUE(LogContainsEndEvent(entries, 4, NetLogEventType::PROXY_SERVICE)); +} + +// Test that the proxy resolver does not see the URL's username/password +// or its reference section. +TEST_F(ProxyServiceTest, PAC_NoIdentityOrHash) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + GURL url("http://username:password@www.google.com/?ref#hash#hash"); + + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(url, std::string(), &info, callback.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + // The URL should have been simplified, stripping the username/password/hash. + EXPECT_EQ(GURL("http://www.google.com/?ref"), + resolver.pending_jobs()[0]->url()); + + // We end here without ever completing the request -- destruction of + // ProxyResolutionService will cancel the outstanding request. +} + +TEST_F(ProxyServiceTest, PAC_FailoverWithoutDirect) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + GURL url("http://www.google.com/"); + + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // Set the result in proxy resolver. + resolver.pending_jobs()[0]->results()->UseNamedProxy("foopy:8080"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy:8080", info.proxy_server().ToURI()); + EXPECT_TRUE(info.did_use_pac_script()); + + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time()); + + // Now, imagine that connecting to foopy:8080 fails: there is nothing + // left to fallback to, since our proxy list was NOT terminated by + // DIRECT. + EXPECT_FALSE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + EXPECT_TRUE(info.is_empty()); +} + +// Test that if the execution of the PAC script fails (i.e. javascript runtime +// error), and the PAC settings are non-mandatory, that we fall-back to direct. +TEST_F(ProxyServiceTest, PAC_RuntimeError) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + GURL url("http://this-causes-js-error/"); + + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // Simulate a failure in the PAC executor. + resolver.pending_jobs()[0]->CompleteNow(ERR_PAC_SCRIPT_FAILED); + + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + + // Since the PAC script was non-mandatory, we should have fallen-back to + // DIRECT. + EXPECT_TRUE(info.is_direct()); + EXPECT_TRUE(info.did_use_pac_script()); + + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time()); +} + +// The proxy list could potentially contain the DIRECT fallback choice +// in a location other than the very end of the list, and could even +// specify it multiple times. +// +// This is not a typical usage, but we will obey it. +// (If we wanted to disallow this type of input, the right place to +// enforce it would be in parsing the PAC result string). +// +// This test will use the PAC result string: +// +// "DIRECT ; PROXY foobar:10 ; DIRECT ; PROXY foobar:20" +// +// For which we expect it to try DIRECT, then foobar:10, then DIRECT again, +// then foobar:20, and then give up and error. +// +// The important check of this test is to make sure that DIRECT is not somehow +// cached as being a bad proxy. +TEST_F(ProxyServiceTest, PAC_FailoverAfterDirect) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + GURL url("http://www.google.com/"); + + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // Set the result in proxy resolver. + resolver.pending_jobs()[0]->results()->UsePacString( + "DIRECT ; PROXY foobar:10 ; DIRECT ; PROXY foobar:20"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_TRUE(info.is_direct()); + + // Fallback 1. + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foobar:10", info.proxy_server().ToURI()); + + // Fallback 2. + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + EXPECT_TRUE(info.is_direct()); + + // Fallback 3. + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foobar:20", info.proxy_server().ToURI()); + + // Fallback 4 -- Nothing to fall back to! + EXPECT_FALSE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + EXPECT_TRUE(info.is_empty()); +} + +TEST_F(ProxyServiceTest, PAC_ConfigSourcePropagates) { + // Test whether the ProxyConfigSource set by the ProxyConfigService is applied + // to ProxyInfo after the proxy is resolved via a PAC script. + ProxyConfig config = + ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac")); + config.set_source(PROXY_CONFIG_SOURCE_TEST); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + // Resolve something. + GURL url("http://www.google.com/"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(url, std::string(), &info, callback.callback(), + nullptr, nullptr, NetLogWithSource()); + ASSERT_THAT(rv, IsError(ERR_IO_PENDING)); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + ASSERT_EQ(1u, resolver.pending_jobs().size()); + + // Set the result in proxy resolver. + resolver.pending_jobs()[0]->results()->UseNamedProxy("foopy"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(PROXY_CONFIG_SOURCE_TEST, info.config_source()); + EXPECT_TRUE(info.did_use_pac_script()); + + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time()); +} + +TEST_F(ProxyServiceTest, ProxyResolverFails) { + // Test what happens when the ProxyResolver fails. The download and setting + // of the PAC script have already succeeded, so this corresponds with a + // javascript runtime error while calling FindProxyForURL(). + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + // Start first resolve request. + GURL url("http://www.google.com/"); + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // Fail the first resolve request in MockAsyncProxyResolver. + resolver.pending_jobs()[0]->CompleteNow(ERR_FAILED); + + // Although the proxy resolver failed the request, ProxyResolutionService + // implicitly falls-back to DIRECT. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_TRUE(info.is_direct()); + + // Failed PAC executions still have proxy resolution times. + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time()); + + // The second resolve request will try to run through the proxy resolver, + // regardless of whether the first request failed in it. + TestCompletionCallback callback2; + rv = service.ResolveProxy(url, std::string(), &info, callback2.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // This time we will have the resolver succeed (perhaps the PAC script has + // a dependency on the current time). + resolver.pending_jobs()[0]->results()->UseNamedProxy("foopy_valid:8080"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy_valid:8080", info.proxy_server().ToURI()); +} + +TEST_F(ProxyServiceTest, ProxyResolverTerminatedDuringRequest) { + // Test what happens when the ProxyResolver fails with a fatal error while + // a GetProxyForURL() call is in progress. + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + // Start first resolve request. + GURL url("http://www.google.com/"); + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, factory->pending_requests().size()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // Fail the first resolve request in MockAsyncProxyResolver. + resolver.pending_jobs()[0]->CompleteNow(ERR_PAC_SCRIPT_TERMINATED); + + // Although the proxy resolver failed the request, ProxyResolutionService + // implicitly falls-back to DIRECT. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_TRUE(info.is_direct()); + + // Failed PAC executions still have proxy resolution times. + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time()); + + // With no other requests, the ProxyResolutionService waits for a new request + // before initializing a new ProxyResolver. + EXPECT_TRUE(factory->pending_requests().empty()); + + TestCompletionCallback callback2; + rv = service.ResolveProxy(url, std::string(), &info, callback2.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, factory->pending_requests().size()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // This time we will have the resolver succeed. + resolver.pending_jobs()[0]->results()->UseNamedProxy("foopy_valid:8080"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy_valid:8080", info.proxy_server().ToURI()); +} + +TEST_F(ProxyServiceTest, + ProxyResolverTerminatedDuringRequestWithConcurrentRequest) { + // Test what happens when the ProxyResolver fails with a fatal error while + // a GetProxyForURL() call is in progress. + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + // Start two resolve requests. + GURL url1("http://www.google.com/"); + GURL url2("https://www.google.com/"); + ProxyInfo info; + TestCompletionCallback callback1; + int rv = + service.ResolveProxy(url1, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + TestCompletionCallback callback2; + rv = service.ResolveProxy(url2, std::string(), &info, callback2.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, factory->pending_requests().size()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + JobMap jobs = GetPendingJobsForURLs(resolver, url1, url2); + + // Fail the first resolve request in MockAsyncProxyResolver. + jobs[url1]->CompleteNow(ERR_PAC_SCRIPT_TERMINATED); + + // Although the proxy resolver failed the request, ProxyResolutionService + // implicitly falls-back to DIRECT. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_TRUE(info.is_direct()); + + // Failed PAC executions still have proxy resolution times. + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time()); + + // The second request is cancelled when the proxy resolver terminates. + jobs = GetCancelledJobsForURLs(resolver, url2); + + // Since a second request was in progress, the ProxyResolutionService starts + // initializating a new ProxyResolver. + ASSERT_EQ(1u, factory->pending_requests().size()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + jobs = GetPendingJobsForURLs(resolver, url2); + + // This request succeeds. + jobs[url2]->results()->UseNamedProxy("foopy_valid:8080"); + jobs[url2]->CompleteNow(OK); + + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy_valid:8080", info.proxy_server().ToURI()); +} + +TEST_F(ProxyServiceTest, ProxyScriptFetcherFailsDownloadingMandatoryPac) { + // Test what happens when the ProxyScriptResolver fails to download a + // mandatory PAC script. + + ProxyConfig config( + ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac"))); + config.set_pac_mandatory(true); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + // Start first resolve request. + GURL url("http://www.google.com/"); + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNow(ERR_FAILED, nullptr); + + ASSERT_EQ(0u, factory->pending_requests().size()); + // As the proxy resolver factory failed the request and is configured for a + // mandatory PAC script, ProxyResolutionService must not implicitly fall-back + // to DIRECT. + EXPECT_EQ(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, + callback1.WaitForResult()); + EXPECT_FALSE(info.is_direct()); + + // As the proxy resolver factory failed the request and is configured for a + // mandatory PAC script, ProxyResolutionService must not implicitly fall-back + // to DIRECT. + TestCompletionCallback callback2; + rv = service.ResolveProxy(url, std::string(), &info, callback2.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED)); + EXPECT_FALSE(info.is_direct()); +} + +TEST_F(ProxyServiceTest, ProxyResolverFailsParsingJavaScriptMandatoryPac) { + // Test what happens when the ProxyResolver fails that is configured to use a + // mandatory PAC script. The download of the PAC script has already + // succeeded but the PAC script contains no valid javascript. + + ProxyConfig config( + ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac"))); + config.set_pac_mandatory(true); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start resolve request. + GURL url("http://www.google.com/"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(url, std::string(), &info, callback.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Check that nothing has been sent to the proxy resolver factory yet. + ASSERT_EQ(0u, factory->pending_requests().size()); + + // Downloading the PAC script succeeds. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, "invalid-script-contents"); + + EXPECT_FALSE(fetcher->has_pending_request()); + ASSERT_EQ(0u, factory->pending_requests().size()); + + // Since ProxyScriptDecider failed to identify a valid PAC and PAC was + // mandatory for this configuration, the ProxyResolutionService must not + // implicitly fall-back to DIRECT. + EXPECT_EQ(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, + callback.WaitForResult()); + EXPECT_FALSE(info.is_direct()); +} + +TEST_F(ProxyServiceTest, ProxyResolverFailsInJavaScriptMandatoryPac) { + // Test what happens when the ProxyResolver fails that is configured to use a + // mandatory PAC script. The download and setting of the PAC script have + // already succeeded, so this corresponds with a javascript runtime error + // while calling FindProxyForURL(). + + ProxyConfig config( + ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac"))); + config.set_pac_mandatory(true); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + // Start first resolve request. + GURL url("http://www.google.com/"); + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // Fail the first resolve request in MockAsyncProxyResolver. + resolver.pending_jobs()[0]->CompleteNow(ERR_FAILED); + + // As the proxy resolver failed the request and is configured for a mandatory + // PAC script, ProxyResolutionService must not implicitly fall-back to DIRECT. + EXPECT_EQ(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, + callback1.WaitForResult()); + EXPECT_FALSE(info.is_direct()); + + // The second resolve request will try to run through the proxy resolver, + // regardless of whether the first request failed in it. + TestCompletionCallback callback2; + rv = service.ResolveProxy(url, std::string(), &info, callback2.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // This time we will have the resolver succeed (perhaps the PAC script has + // a dependency on the current time). + resolver.pending_jobs()[0]->results()->UseNamedProxy("foopy_valid:8080"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy_valid:8080", info.proxy_server().ToURI()); +} + +TEST_F(ProxyServiceTest, ProxyFallback) { + // Test what happens when we specify multiple proxy servers and some of them + // are bad. + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + GURL url("http://www.google.com/"); + + // Get the proxy information. + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // Set the result in proxy resolver. + resolver.pending_jobs()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy2:9090"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // The first item is valid. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time()); + base::TimeTicks proxy_resolve_start_time = info.proxy_resolve_start_time(); + base::TimeTicks proxy_resolve_end_time = info.proxy_resolve_end_time(); + + // Fake an error on the proxy. + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + + // Proxy times should not have been modified by fallback. + EXPECT_EQ(proxy_resolve_start_time, info.proxy_resolve_start_time()); + EXPECT_EQ(proxy_resolve_end_time, info.proxy_resolve_end_time()); + + // The second proxy should be specified. + EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI()); + // Report back that the second proxy worked. This will globally mark the + // first proxy as bad. + TestProxyFallbackProxyDelegate test_delegate; + service.ReportSuccess(info, &test_delegate); + EXPECT_EQ("foopy1:8080", test_delegate.proxy_server().ToURI()); + EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, + test_delegate.proxy_fallback_net_error()); + + TestCompletionCallback callback3; + rv = service.ResolveProxy(url, std::string(), &info, callback3.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // Set the result in proxy resolver -- the second result is already known + // to be bad, so we will not try to use it initially. + resolver.pending_jobs()[0]->results()->UseNamedProxy( + "foopy3:7070;foopy1:8080;foopy2:9090"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + EXPECT_THAT(callback3.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy3:7070", info.proxy_server().ToURI()); + + // Proxy times should have been updated, so get them again. + EXPECT_LE(proxy_resolve_end_time, info.proxy_resolve_start_time()); + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time()); + proxy_resolve_start_time = info.proxy_resolve_start_time(); + proxy_resolve_end_time = info.proxy_resolve_end_time(); + + // We fake another error. It should now try the third one. + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI()); + + // We fake another error. At this point we have tried all of the + // proxy servers we thought were valid; next we try the proxy server + // that was in our bad proxies map (foopy1:8080). + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + // Fake another error, the last proxy is gone, the list should now be empty, + // so there is nothing left to try. + EXPECT_FALSE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + EXPECT_FALSE(info.is_direct()); + EXPECT_TRUE(info.is_empty()); + + // Proxy times should not have been modified by fallback. + EXPECT_EQ(proxy_resolve_start_time, info.proxy_resolve_start_time()); + EXPECT_EQ(proxy_resolve_end_time, info.proxy_resolve_end_time()); + + // Look up proxies again + TestCompletionCallback callback7; + rv = service.ResolveProxy(url, std::string(), &info, callback7.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // This time, the first 3 results have been found to be bad, but only the + // first proxy has been confirmed ... + resolver.pending_jobs()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy3:7070;foopy2:9090;foopy4:9091"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // ... therefore, we should see the second proxy first. + EXPECT_THAT(callback7.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy3:7070", info.proxy_server().ToURI()); + + EXPECT_LE(proxy_resolve_end_time, info.proxy_resolve_start_time()); + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + // TODO(nsylvain): Test that the proxy can be retried after the delay. +} + +// This test is similar to ProxyFallback, but this time we have an explicit +// fallback choice to DIRECT. +TEST_F(ProxyServiceTest, ProxyFallbackToDirect) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + GURL url("http://www.google.com/"); + + // Get the proxy information. + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // Set the result in proxy resolver. + resolver.pending_jobs()[0]->results()->UsePacString( + "PROXY foopy1:8080; PROXY foopy2:9090; DIRECT"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Get the first result. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + // Fake an error on the proxy. + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + + // Now we get back the second proxy. + EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI()); + + // Fake an error on this proxy as well. + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + + // Finally, we get back DIRECT. + EXPECT_TRUE(info.is_direct()); + + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time()); + + // Now we tell the proxy service that even DIRECT failed. + // There was nothing left to try after DIRECT, so we are out of + // choices. + EXPECT_FALSE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); +} + +TEST_F(ProxyServiceTest, ProxyFallback_BadConfig) { + // Test proxy failover when the configuration is bad. + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + GURL url("http://www.google.com/"); + + // Get the proxy information. + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + resolver.pending_jobs()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy2:9090"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // The first item is valid. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + // Fake a proxy error. + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + + // The first proxy is ignored, and the second one is selected. + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI()); + + // Persist foopy1's failure to |service|'s cache of bad proxies, so it will + // be considered by subsequent calls to ResolveProxy(). + service.ReportSuccess(info, nullptr); + + // Fake a PAC failure. + ProxyInfo info2; + TestCompletionCallback callback3; + rv = service.ResolveProxy(url, std::string(), &info2, callback3.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // This simulates a javascript runtime error in the PAC script. + resolver.pending_jobs()[0]->CompleteNow(ERR_FAILED); + + // Although the resolver failed, the ProxyResolutionService will implicitly + // fall-back to a DIRECT connection. + EXPECT_THAT(callback3.WaitForResult(), IsOk()); + EXPECT_TRUE(info2.is_direct()); + EXPECT_FALSE(info2.is_empty()); + + // The PAC script will work properly next time and successfully return a + // proxy list. Since we have not marked the configuration as bad, it should + // "just work" the next time we call it. + ProxyInfo info3; + TestCompletionCallback callback4; + rv = service.ResolveProxy(url, std::string(), &info3, callback4.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + resolver.pending_jobs()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy2:9090"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // The first proxy was deprioritized since it was added to the bad proxies + // list by the earlier ReportSuccess(). + EXPECT_THAT(callback4.WaitForResult(), IsOk()); + EXPECT_FALSE(info3.is_direct()); + EXPECT_EQ("foopy2:9090", info3.proxy_server().ToURI()); + EXPECT_EQ(2u, info3.proxy_list().size()); + + EXPECT_FALSE(info.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info.proxy_resolve_end_time().is_null()); + EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time()); +} + +TEST_F(ProxyServiceTest, ProxyFallback_BadConfigMandatory) { + // Test proxy failover when the configuration is bad. + + ProxyConfig config( + ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac"))); + + config.set_pac_mandatory(true); + MockProxyConfigService* config_service = new MockProxyConfigService(config); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + GURL url("http://www.google.com/"); + + // Get the proxy information. + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, std::string(), &info, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + resolver.pending_jobs()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy2:9090"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // The first item is valid. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + // Fake a proxy error. + EXPECT_TRUE(info.Fallback(ERR_PROXY_CONNECTION_FAILED, NetLogWithSource())); + + // The first proxy is ignored, and the second one is selected. + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI()); + + // Persist foopy1's failure to |service|'s cache of bad proxies, so it will + // be considered by subsequent calls to ResolveProxy(). + service.ReportSuccess(info, nullptr); + + // Fake a PAC failure. + ProxyInfo info2; + TestCompletionCallback callback3; + rv = service.ResolveProxy(url, std::string(), &info2, callback3.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // This simulates a javascript runtime error in the PAC script. + resolver.pending_jobs()[0]->CompleteNow(ERR_FAILED); + + // Although the resolver failed, the ProxyResolutionService will NOT fall-back + // to a DIRECT connection as it is configured as mandatory. + EXPECT_EQ(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, + callback3.WaitForResult()); + EXPECT_FALSE(info2.is_direct()); + EXPECT_TRUE(info2.is_empty()); + + // The PAC script will work properly next time and successfully return a + // proxy list. Since we have not marked the configuration as bad, it should + // "just work" the next time we call it. + ProxyInfo info3; + TestCompletionCallback callback4; + rv = service.ResolveProxy(url, std::string(), &info3, callback4.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + resolver.pending_jobs()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy2:9090"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // The first proxy was deprioritized since it was added to the bad proxies + // list by the earlier ReportSuccess(). + EXPECT_THAT(callback4.WaitForResult(), IsOk()); + EXPECT_FALSE(info3.is_direct()); + EXPECT_EQ("foopy2:9090", info3.proxy_server().ToURI()); + EXPECT_EQ(2u, info3.proxy_list().size()); +} + +TEST_F(ProxyServiceTest, ProxyBypassList) { + // Test that the proxy bypass rules are consulted. + + TestCompletionCallback callback[2]; + ProxyInfo info[2]; + ProxyConfig config; + config.proxy_rules().ParseFromString("foopy1:8080;foopy2:9090"); + config.set_auto_detect(false); + config.proxy_rules().bypass_rules.ParseFromString("*.org"); + + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + + int rv; + GURL url1("http://www.webkit.org"); + GURL url2("http://www.webkit.com"); + + // Request for a .org domain should bypass proxy. + rv = service.ResolveProxy(url1, std::string(), &info[0], + callback[0].callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_TRUE(info[0].is_direct()); + + // Request for a .com domain hits the proxy. + rv = service.ResolveProxy(url2, std::string(), &info[1], + callback[1].callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_EQ("foopy1:8080", info[1].proxy_server().ToURI()); +} + +TEST_F(ProxyServiceTest, MarkProxiesAsBadTests) { + ProxyConfig config; + config.proxy_rules().ParseFromString( + "http=foopy1:8080;http=foopy2:8080;http=foopy3.8080;http=foopy4:8080"); + config.set_auto_detect(false); + + ProxyList proxy_list; + std::vector<ProxyServer> additional_bad_proxies; + for (const ProxyServer& proxy_server : + config.proxy_rules().proxies_for_http.GetAll()) { + proxy_list.AddProxyServer(proxy_server); + if (proxy_server == config.proxy_rules().proxies_for_http.Get()) + continue; + + additional_bad_proxies.push_back(proxy_server); + } + + EXPECT_EQ(3u, additional_bad_proxies.size()); + + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + ProxyInfo proxy_info; + proxy_info.UseProxyList(proxy_list); + const ProxyRetryInfoMap& retry_info = service.proxy_retry_info(); + service.MarkProxiesAsBadUntil(proxy_info, base::TimeDelta::FromSeconds(1), + additional_bad_proxies, NetLogWithSource()); + ASSERT_EQ(4u, retry_info.size()); + for (const ProxyServer& proxy_server : + config.proxy_rules().proxies_for_http.GetAll()) { + ProxyRetryInfoMap::const_iterator i = + retry_info.find(proxy_server.host_port_pair().ToString()); + ASSERT_TRUE(i != retry_info.end()); + } +} + +TEST_F(ProxyServiceTest, PerProtocolProxyTests) { + ProxyConfig config; + config.proxy_rules().ParseFromString("http=foopy1:8080;https=foopy2:8080"); + config.set_auto_detect(false); + { + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + GURL test_url("http://www.msn.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + } + { + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + GURL test_url("ftp://ftp.google.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_TRUE(info.is_direct()); + EXPECT_EQ("direct://", info.proxy_server().ToURI()); + } + { + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + GURL test_url("https://webbranch.techcu.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy2:8080", info.proxy_server().ToURI()); + } + { + config.proxy_rules().ParseFromString("foopy1:8080"); + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + GURL test_url("http://www.microsoft.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + } +} + +TEST_F(ProxyServiceTest, ProxyConfigSourcePropagates) { + // Test that the proxy config source is set correctly when resolving proxies + // using manual proxy rules. Namely, the config source should only be set if + // any of the rules were applied. + { + ProxyConfig config; + config.set_source(PROXY_CONFIG_SOURCE_TEST); + config.proxy_rules().ParseFromString("https=foopy2:8080"); + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + GURL test_url("http://www.google.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + ASSERT_THAT(rv, IsOk()); + // Should be SOURCE_TEST, even if there are no HTTP proxies configured. + EXPECT_EQ(PROXY_CONFIG_SOURCE_TEST, info.config_source()); + } + { + ProxyConfig config; + config.set_source(PROXY_CONFIG_SOURCE_TEST); + config.proxy_rules().ParseFromString("https=foopy2:8080"); + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + GURL test_url("https://www.google.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + ASSERT_THAT(rv, IsOk()); + // Used the HTTPS proxy. So source should be TEST. + EXPECT_EQ(PROXY_CONFIG_SOURCE_TEST, info.config_source()); + } + { + ProxyConfig config; + config.set_source(PROXY_CONFIG_SOURCE_TEST); + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + GURL test_url("http://www.google.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + ASSERT_THAT(rv, IsOk()); + // ProxyConfig is empty. Source should still be TEST. + EXPECT_EQ(PROXY_CONFIG_SOURCE_TEST, info.config_source()); + } +} + +// If only HTTP and a SOCKS proxy are specified, check if ftp/https queries +// fall back to the SOCKS proxy. +TEST_F(ProxyServiceTest, DefaultProxyFallbackToSOCKS) { + ProxyConfig config; + config.proxy_rules().ParseFromString("http=foopy1:8080;socks=foopy2:1080"); + config.set_auto_detect(false); + EXPECT_EQ(ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME, + config.proxy_rules().type); + + { + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + GURL test_url("http://www.msn.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + } + { + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + GURL test_url("ftp://ftp.google.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI()); + } + { + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + GURL test_url("https://webbranch.techcu.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI()); + } + { + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), nullptr, nullptr); + GURL test_url("unknown://www.microsoft.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI()); + } +} + +// Test cancellation of an in-progress request. +TEST_F(ProxyServiceTest, CancelInProgressRequest) { + const GURL url1("http://request1"); + const GURL url2("http://request2"); + const GURL url3("http://request3"); + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + // Start 3 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = + service.ResolveProxy(url1, std::string(), &info1, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Successfully initialize the PAC script. + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + GetPendingJobsForURLs(resolver, url1); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyResolutionService::Request* request2; + rv = service.ResolveProxy(url2, std::string(), &info2, callback2.callback(), + &request2, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + GetPendingJobsForURLs(resolver, url1, url2); + + ProxyInfo info3; + TestCompletionCallback callback3; + rv = service.ResolveProxy(url3, std::string(), &info3, callback3.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + GetPendingJobsForURLs(resolver, url1, url2, url3); + + // Cancel the second request + service.CancelRequest(request2); + + JobMap jobs = GetPendingJobsForURLs(resolver, url1, url3); + + // Complete the two un-cancelled jobs. + // We complete the last one first, just to mix it up a bit. + jobs[url3]->results()->UseNamedProxy("request3:80"); + jobs[url3]->CompleteNow(OK); // dsaadsasd + + jobs[url1]->results()->UseNamedProxy("request1:80"); + jobs[url1]->CompleteNow(OK); + + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + EXPECT_FALSE(callback2.have_result()); // Cancelled. + GetCancelledJobsForURLs(resolver, url2); + + EXPECT_THAT(callback3.WaitForResult(), IsOk()); + EXPECT_EQ("request3:80", info3.proxy_server().ToURI()); +} + +// Test the initial PAC download for resolver that expects bytes. +TEST_F(ProxyServiceTest, InitialPACScriptDownload) { + const GURL url1("http://request1"); + const GURL url2("http://request2"); + const GURL url3("http://request3"); + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 3 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + ProxyResolutionService::Request* request1; + int rv = + service.ResolveProxy(url1, std::string(), &info1, callback1.callback(), + &request1, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // The first request should have triggered download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyResolutionService::Request* request2; + rv = service.ResolveProxy(url2, std::string(), &info2, callback2.callback(), + &request2, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ProxyInfo info3; + TestCompletionCallback callback3; + ProxyResolutionService::Request* request3; + rv = service.ResolveProxy(url3, std::string(), &info3, callback3.callback(), + &request3, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Nothing has been sent to the factory yet. + EXPECT_TRUE(factory->pending_requests().empty()); + + EXPECT_EQ(LOAD_STATE_DOWNLOADING_PROXY_SCRIPT, + service.GetLoadState(request1)); + EXPECT_EQ(LOAD_STATE_DOWNLOADING_PROXY_SCRIPT, + service.GetLoadState(request2)); + EXPECT_EQ(LOAD_STATE_DOWNLOADING_PROXY_SCRIPT, + service.GetLoadState(request3)); + + // At this point the ProxyResolutionService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + // Now that the PAC script is downloaded, it will have been sent to the proxy + // resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + JobMap jobs = GetPendingJobsForURLs(resolver, url1, url2, url3); + + EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL, service.GetLoadState(request1)); + EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL, service.GetLoadState(request2)); + EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL, service.GetLoadState(request3)); + + // Complete all the jobs (in some order). + + jobs[url3]->results()->UseNamedProxy("request3:80"); + jobs[url3]->CompleteNow(OK); + + jobs[url1]->results()->UseNamedProxy("request1:80"); + jobs[url1]->CompleteNow(OK); + + jobs[url2]->results()->UseNamedProxy("request2:80"); + jobs[url2]->CompleteNow(OK); + + // Complete and verify that jobs ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + // ProxyResolver::GetProxyForURL() to take a std::unique_ptr<Request>* rather + // than a RequestHandle* (patchset #11 id:200001 of + // https://codereview.chromium.org/1439053002/ ) + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + EXPECT_FALSE(info1.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info1.proxy_resolve_end_time().is_null()); + EXPECT_LE(info1.proxy_resolve_start_time(), info1.proxy_resolve_end_time()); + + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); + EXPECT_FALSE(info2.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info2.proxy_resolve_end_time().is_null()); + EXPECT_LE(info2.proxy_resolve_start_time(), info2.proxy_resolve_end_time()); + + EXPECT_THAT(callback3.WaitForResult(), IsOk()); + EXPECT_EQ("request3:80", info3.proxy_server().ToURI()); + EXPECT_FALSE(info3.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info3.proxy_resolve_end_time().is_null()); + EXPECT_LE(info3.proxy_resolve_start_time(), info3.proxy_resolve_end_time()); +} + +// Test changing the ProxyScriptFetcher while PAC download is in progress. +TEST_F(ProxyServiceTest, ChangeScriptFetcherWhilePACDownloadInProgress) { + const GURL url1("http://request1"); + const GURL url2("http://request2"); + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 2 jobs. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = + service.ResolveProxy(url1, std::string(), &info1, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // The first request should have triggered download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy(url2, std::string(), &info2, callback2.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // At this point the ProxyResolutionService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + + // We now change out the ProxyResolutionService's script fetcher. We should + // restart the initialization with the new fetcher. + + fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Nothing has been sent to the factory yet. + EXPECT_TRUE(factory->pending_requests().empty()); + + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + // Now that the PAC script is downloaded, it will have been sent to the proxy + // resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + GetPendingJobsForURLs(resolver, url1, url2); +} + +// Test cancellation of a request, while the PAC script is being fetched. +TEST_F(ProxyServiceTest, CancelWhilePACFetching) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 3 requests. + ProxyInfo info1; + TestCompletionCallback callback1; + ProxyResolutionService::Request* request1; + BoundTestNetLog log1; + int rv = service.ResolveProxy(GURL("http://request1"), std::string(), &info1, + callback1.callback(), &request1, nullptr, + log1.bound()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // The first request should have triggered download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyResolutionService::Request* request2; + rv = service.ResolveProxy(GURL("http://request2"), std::string(), &info2, + callback2.callback(), &request2, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ProxyInfo info3; + TestCompletionCallback callback3; + rv = service.ResolveProxy(GURL("http://request3"), std::string(), &info3, + callback3.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Nothing has been sent to the factory yet. + EXPECT_TRUE(factory->pending_requests().empty()); + + // Cancel the first 2 jobs. + service.CancelRequest(request1); + service.CancelRequest(request2); + + // At this point the ProxyResolutionService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + // Now that the PAC script is downloaded, it will have been sent to the + // proxy resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://request3"), resolver.pending_jobs()[0]->url()); + + // Complete all the jobs. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request3:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + EXPECT_THAT(callback3.WaitForResult(), IsOk()); + EXPECT_EQ("request3:80", info3.proxy_server().ToURI()); + + EXPECT_TRUE(resolver.cancelled_jobs().empty()); + + EXPECT_FALSE(callback1.have_result()); // Cancelled. + EXPECT_FALSE(callback2.have_result()); // Cancelled. + + TestNetLogEntry::List entries1; + log1.GetEntries(&entries1); + + // Check the NetLog for request 1 (which was cancelled) got filled properly. + EXPECT_EQ(4u, entries1.size()); + EXPECT_TRUE( + LogContainsBeginEvent(entries1, 0, NetLogEventType::PROXY_SERVICE)); + EXPECT_TRUE(LogContainsBeginEvent( + entries1, 1, NetLogEventType::PROXY_SERVICE_WAITING_FOR_INIT_PAC)); + // Note that PROXY_SERVICE_WAITING_FOR_INIT_PAC is never completed before + // the cancellation occured. + EXPECT_TRUE(LogContainsEvent(entries1, 2, NetLogEventType::CANCELLED, + NetLogEventPhase::NONE)); + EXPECT_TRUE(LogContainsEndEvent(entries1, 3, NetLogEventType::PROXY_SERVICE)); +} + +// Test that if auto-detect fails, we fall-back to the custom pac. +TEST_F(ProxyServiceTest, FallbackFromAutodetectToCustomPac) { + const GURL url1("http://request1"); + const GURL url2("http://request2"); + ProxyConfig config; + config.set_auto_detect(true); + config.set_pac_url(GURL("http://foopy/proxy.pac")); + config.proxy_rules().ParseFromString("http=foopy:80"); // Won't be used. + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 2 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = + service.ResolveProxy(url1, std::string(), &info1, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyResolutionService::Request* request2; + rv = service.ResolveProxy(url2, std::string(), &info2, callback2.callback(), + &request2, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Check that nothing has been sent to the proxy resolver factory yet. + ASSERT_EQ(0u, factory->pending_requests().size()); + + // It should be trying to auto-detect first -- FAIL the autodetect during + // the script download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(ERR_FAILED, std::string()); + + // Next it should be trying the custom PAC url. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + // Now finally, the pending jobs should have been sent to the resolver + // (which was initialized with custom PAC script). + + JobMap jobs = GetPendingJobsForURLs(resolver, url1, url2); + + // Complete the pending jobs. + jobs[url2]->results()->UseNamedProxy("request2:80"); + jobs[url2]->CompleteNow(OK); + jobs[url1]->results()->UseNamedProxy("request1:80"); + jobs[url1]->CompleteNow(OK); + + // Verify that jobs ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + // ProxyResolver::GetProxyForURL() to take a std::unique_ptr<Request>* rather + // than a RequestHandle* (patchset #11 id:200001 of + // https://codereview.chromium.org/1439053002/ ) + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + EXPECT_FALSE(info1.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info1.proxy_resolve_end_time().is_null()); + EXPECT_LE(info1.proxy_resolve_start_time(), info1.proxy_resolve_end_time()); + + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); + EXPECT_FALSE(info2.proxy_resolve_start_time().is_null()); + EXPECT_FALSE(info2.proxy_resolve_end_time().is_null()); + EXPECT_LE(info2.proxy_resolve_start_time(), info2.proxy_resolve_end_time()); +} + +// This is the same test as FallbackFromAutodetectToCustomPac, except +// the auto-detect script fails parsing rather than downloading. +TEST_F(ProxyServiceTest, FallbackFromAutodetectToCustomPac2) { + const GURL url1("http://request1"); + const GURL url2("http://request2"); + ProxyConfig config; + config.set_auto_detect(true); + config.set_pac_url(GURL("http://foopy/proxy.pac")); + config.proxy_rules().ParseFromString("http=foopy:80"); // Won't be used. + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 2 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = + service.ResolveProxy(url1, std::string(), &info1, callback1.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyResolutionService::Request* request2; + rv = service.ResolveProxy(url2, std::string(), &info2, callback2.callback(), + &request2, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Check that nothing has been sent to the proxy resolver factory yet. + ASSERT_EQ(0u, factory->pending_requests().size()); + + // It should be trying to auto-detect first -- succeed the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, "invalid-script-contents"); + + // The script contents passed failed basic verification step (since didn't + // contain token FindProxyForURL), so it was never passed to the resolver. + + // Next it should be trying the custom PAC url. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + // Now finally, the pending jobs should have been sent to the resolver + // (which was initialized with custom PAC script). + + JobMap jobs = GetPendingJobsForURLs(resolver, url1, url2); + + // Complete the pending jobs. + jobs[url2]->results()->UseNamedProxy("request2:80"); + jobs[url2]->CompleteNow(OK); + jobs[url1]->results()->UseNamedProxy("request1:80"); + jobs[url1]->CompleteNow(OK); + + // Verify that jobs ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + // ProxyResolver::GetProxyForURL() to take a std::unique_ptr<Request>* rather + // than a RequestHandle* (patchset #11 id:200001 of + // https://codereview.chromium.org/1439053002/ ) + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// Test that if all of auto-detect, a custom PAC script, and manual settings +// are given, then we will try them in that order. +TEST_F(ProxyServiceTest, FallbackFromAutodetectToCustomToManual) { + ProxyConfig config; + config.set_auto_detect(true); + config.set_pac_url(GURL("http://foopy/proxy.pac")); + config.proxy_rules().ParseFromString("http=foopy:80"); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 2 jobs. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(GURL("http://request1"), std::string(), &info1, + callback1.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyResolutionService::Request* request2; + rv = service.ResolveProxy(GURL("http://request2"), std::string(), &info2, + callback2.callback(), &request2, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Check that nothing has been sent to the proxy resolver factory yet. + ASSERT_EQ(0u, factory->pending_requests().size()); + + // It should be trying to auto-detect first -- fail the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(ERR_FAILED, std::string()); + + // Next it should be trying the custom PAC url -- fail the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(ERR_FAILED, std::string()); + + // Since we never managed to initialize a resolver, nothing should have been + // sent to it. + ASSERT_EQ(0u, factory->pending_requests().size()); + + // Verify that jobs ran as expected -- they should have fallen back to + // the manual proxy configuration for HTTP urls. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_EQ("foopy:80", info1.proxy_server().ToURI()); + + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_EQ("foopy:80", info2.proxy_server().ToURI()); +} + +// Test that the bypass rules are NOT applied when using autodetect. +TEST_F(ProxyServiceTest, BypassDoesntApplyToPac) { + ProxyConfig config; + config.set_auto_detect(true); + config.set_pac_url(GURL("http://foopy/proxy.pac")); + config.proxy_rules().ParseFromString("http=foopy:80"); // Not used. + config.proxy_rules().bypass_rules.ParseFromString("www.google.com"); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 1 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(GURL("http://www.google.com"), std::string(), + &info1, callback1.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Check that nothing has been sent to the proxy resolver factory yet. + ASSERT_EQ(0u, factory->pending_requests().size()); + + // It should be trying to auto-detect first -- succeed the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://www.google.com"), resolver.pending_jobs()[0]->url()); + + // Complete the pending request. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request1:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Verify that request ran as expected. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // Start another request, it should pickup the bypass item. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy(GURL("http://www.google.com"), std::string(), + &info2, callback2.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://www.google.com"), resolver.pending_jobs()[0]->url()); + + // Complete the pending request. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request2:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// Delete the ProxyResolutionService while InitProxyResolver has an outstanding +// request to the script fetcher. When run under valgrind, should not +// have any memory errors (used to be that the ProxyScriptFetcher was +// being deleted prior to the InitProxyResolver). +TEST_F(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingFetch) { + ProxyConfig config = + ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac")); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(GURL("http://www.google.com"), std::string(), + &info1, callback1.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Check that nothing has been sent to the proxy resolver factory yet. + ASSERT_EQ(0u, factory->pending_requests().size()); + + // InitProxyResolver should have issued a request to the ProxyScriptFetcher + // and be waiting on that to complete. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); +} + +// Delete the ProxyResolutionService while InitProxyResolver has an outstanding +// request to the proxy resolver. When run under valgrind, should not +// have any memory errors (used to be that the ProxyResolver was +// being deleted prior to the InitProxyResolver). +TEST_F(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingSet) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + GURL url("http://www.google.com/"); + + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(url, std::string(), &info, callback.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); +} + +TEST_F(ProxyServiceTest, ResetProxyConfigService) { + ProxyConfig config1; + config1.proxy_rules().ParseFromString("foopy1:8080"); + config1.set_auto_detect(false); + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config1), nullptr, nullptr); + + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(GURL("http://request1"), std::string(), &info, + callback1.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + ProxyConfig config2; + config2.proxy_rules().ParseFromString("foopy2:8080"); + config2.set_auto_detect(false); + service.ResetConfigService(std::make_unique<MockProxyConfigService>(config2)); + TestCompletionCallback callback2; + rv = service.ResolveProxy(GURL("http://request2"), std::string(), &info, + callback2.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_EQ("foopy2:8080", info.proxy_server().ToURI()); +} + +// Test that when going from a configuration that required PAC to one +// that does NOT, we unset the variable |should_use_proxy_resolver_|. +TEST_F(ProxyServiceTest, UpdateConfigFromPACToDirect) { + ProxyConfig config = ProxyConfig::CreateAutoDetect(); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(GURL("http://www.google.com"), std::string(), + &info1, callback1.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Successfully set the autodetect script. + EXPECT_EQ(ProxyResolverScriptData::TYPE_AUTO_DETECT, + factory->pending_requests()[0]->script_data()->type()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + // Complete the pending request. + ASSERT_EQ(1u, resolver.pending_jobs().size()); + resolver.pending_jobs()[0]->results()->UseNamedProxy("request1:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Verify that request ran as expected. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // Force the ProxyResolutionService to pull down a new proxy configuration. + // (Even though the configuration isn't old/bad). + // + // This new configuration no longer has auto_detect set, so + // jobs should complete synchronously now as direct-connect. + config_service->SetConfig(ProxyConfig::CreateDirect()); + + // Start another request -- the effective configuration has changed. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy(GURL("http://www.google.com"), std::string(), + &info2, callback2.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + + EXPECT_TRUE(info2.is_direct()); +} + +TEST_F(ProxyServiceTest, NetworkChangeTriggersPacRefetch) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + TestNetLog log; + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), &log); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Disable the "wait after IP address changes" hack, so this unit-test can + // complete quickly. + service.set_stall_proxy_auto_config_delay(base::TimeDelta()); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(GURL("http://request1"), std::string(), &info1, + callback1.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // The first request should have triggered initial download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // Nothing has been sent to the factory yet. + EXPECT_TRUE(factory->pending_requests().empty()); + + // At this point the ProxyResolutionService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + // Now that the PAC script is downloaded, the request will have been sent to + // the proxy resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://request1"), resolver.pending_jobs()[0]->url()); + + // Complete the pending request. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request1:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // Now simluate a change in the network. The ProxyConfigService is still + // going to return the same PAC URL as before, but this URL needs to be + // refetched on the new network. + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + base::RunLoop().RunUntilIdle(); // Notification happens async. + + // Start a second request. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy(GURL("http://request2"), std::string(), &info2, + callback2.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // This second request should have triggered the re-download of the PAC + // script (since we marked the network as having changed). + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // Nothing has been sent to the factory yet. + EXPECT_TRUE(factory->pending_requests().empty()); + + // Simulate the PAC script fetch as having completed (this time with + // different data). + fetcher->NotifyFetchCompletion(OK, kValidPacScript2); + + // Now that the PAC script is downloaded, the second request will have been + // sent to the proxy resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript2), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://request2"), resolver.pending_jobs()[0]->url()); + + // Complete the pending second request. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request2:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); + + // Check that the expected events were output to the log stream. In particular + // PROXY_CONFIG_CHANGED should have only been emitted once (for the initial + // setup), and NOT a second time when the IP address changed. + TestNetLogEntry::List entries; + log.GetEntries(&entries); + + EXPECT_TRUE(LogContainsEntryWithType(entries, 0, + NetLogEventType::PROXY_CONFIG_CHANGED)); + ASSERT_EQ(9u, entries.size()); + for (size_t i = 1; i < entries.size(); ++i) + EXPECT_NE(NetLogEventType::PROXY_CONFIG_CHANGED, entries[i].type); +} + +// This test verifies that the PAC script specified by the settings is +// periodically polled for changes. Specifically, if the initial fetch fails due +// to a network error, we will eventually re-configure the service to use the +// script once it becomes available. +TEST_F(ProxyServiceTest, PACScriptRefetchAfterFailure) { + // Change the retry policy to wait a mere 1 ms before retrying, so the test + // runs quickly. + ImmediatePollPolicy poll_policy; + ProxyResolutionService::set_pac_script_poll_policy(&poll_policy); + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(GURL("http://request1"), std::string(), &info1, + callback1.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // The first request should have triggered initial download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // Nothing has been sent to the factory yet. + EXPECT_TRUE(factory->pending_requests().empty()); + + // At this point the ProxyResolutionService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + // + // We simulate a failed download attempt, the proxy service should now + // fall-back to DIRECT connections. + fetcher->NotifyFetchCompletion(ERR_FAILED, std::string()); + + ASSERT_TRUE(factory->pending_requests().empty()); + + // Wait for completion callback, and verify it used DIRECT. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_TRUE(info1.is_direct()); + + // At this point we have initialized the proxy service using a PAC script, + // however it failed and fell-back to DIRECT. + // + // A background task to periodically re-check the PAC script for validity will + // have been started. We will now wait for the next download attempt to start. + // + // Note that we shouldn't have to wait long here, since our test enables a + // special unit-test mode. + fetcher->WaitUntilFetch(); + + ASSERT_TRUE(factory->pending_requests().empty()); + + // Make sure that our background checker is trying to download the expected + // PAC script (same one as before). This time we will simulate a successful + // download of the script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + base::RunLoop().RunUntilIdle(); + + // Now that the PAC script is downloaded, it should be used to initialize the + // ProxyResolver. Simulate a successful parse. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + // At this point the ProxyResolutionService should have re-configured itself + // to use the PAC script (thereby recovering from the initial fetch failure). + // We will verify that the next Resolve request uses the resolver rather than + // DIRECT. + + // Start a second request. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy(GURL("http://request2"), std::string(), &info2, + callback2.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Check that it was sent to the resolver. + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://request2"), resolver.pending_jobs()[0]->url()); + + // Complete the pending second request. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request2:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// This test verifies that the PAC script specified by the settings is +// periodically polled for changes. Specifically, if the initial fetch succeeds, +// however at a later time its *contents* change, we will eventually +// re-configure the service to use the new script. +TEST_F(ProxyServiceTest, PACScriptRefetchAfterContentChange) { + // Change the retry policy to wait a mere 1 ms before retrying, so the test + // runs quickly. + ImmediatePollPolicy poll_policy; + ProxyResolutionService::set_pac_script_poll_policy(&poll_policy); + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(GURL("http://request1"), std::string(), &info1, + callback1.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // The first request should have triggered initial download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // Nothing has been sent to the factory yet. + EXPECT_TRUE(factory->pending_requests().empty()); + + // At this point the ProxyResolutionService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + // Now that the PAC script is downloaded, the request will have been sent to + // the proxy resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://request1"), resolver.pending_jobs()[0]->url()); + + // Complete the pending request. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request1:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // At this point we have initialized the proxy service using a PAC script. + // + // A background task to periodically re-check the PAC script for validity will + // have been started. We will now wait for the next download attempt to start. + // + // Note that we shouldn't have to wait long here, since our test enables a + // special unit-test mode. + fetcher->WaitUntilFetch(); + + ASSERT_TRUE(factory->pending_requests().empty()); + ASSERT_TRUE(resolver.pending_jobs().empty()); + + // Make sure that our background checker is trying to download the expected + // PAC script (same one as before). This time we will simulate a successful + // download of a DIFFERENT script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, kValidPacScript2); + + base::RunLoop().RunUntilIdle(); + + // Now that the PAC script is downloaded, it should be used to initialize the + // ProxyResolver. Simulate a successful parse. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript2), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + // At this point the ProxyResolutionService should have re-configured itself + // to use the new PAC script. + + // Start a second request. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy(GURL("http://request2"), std::string(), &info2, + callback2.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Check that it was sent to the resolver. + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://request2"), resolver.pending_jobs()[0]->url()); + + // Complete the pending second request. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request2:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// This test verifies that the PAC script specified by the settings is +// periodically polled for changes. Specifically, if the initial fetch succeeds +// and so does the next poll, however the contents of the downloaded script +// have NOT changed, then we do not bother to re-initialize the proxy resolver. +TEST_F(ProxyServiceTest, PACScriptRefetchAfterContentUnchanged) { + // Change the retry policy to wait a mere 1 ms before retrying, so the test + // runs quickly. + ImmediatePollPolicy poll_policy; + ProxyResolutionService::set_pac_script_poll_policy(&poll_policy); + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(GURL("http://request1"), std::string(), &info1, + callback1.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // The first request should have triggered initial download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // Nothing has been sent to the factory yet. + EXPECT_TRUE(factory->pending_requests().empty()); + + // At this point the ProxyResolutionService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + // Now that the PAC script is downloaded, the request will have been sent to + // the proxy resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://request1"), resolver.pending_jobs()[0]->url()); + + // Complete the pending request. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request1:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // At this point we have initialized the proxy service using a PAC script. + // + // A background task to periodically re-check the PAC script for validity will + // have been started. We will now wait for the next download attempt to start. + // + // Note that we shouldn't have to wait long here, since our test enables a + // special unit-test mode. + fetcher->WaitUntilFetch(); + + ASSERT_TRUE(factory->pending_requests().empty()); + ASSERT_TRUE(resolver.pending_jobs().empty()); + + // Make sure that our background checker is trying to download the expected + // PAC script (same one as before). We will simulate the same response as + // last time (i.e. the script is unchanged). + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(factory->pending_requests().empty()); + ASSERT_TRUE(resolver.pending_jobs().empty()); + + // At this point the ProxyResolutionService is still running the same PAC + // script as before. + + // Start a second request. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy(GURL("http://request2"), std::string(), &info2, + callback2.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // Check that it was sent to the resolver. + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://request2"), resolver.pending_jobs()[0]->url()); + + // Complete the pending second request. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request2:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// This test verifies that the PAC script specified by the settings is +// periodically polled for changes. Specifically, if the initial fetch succeeds, +// however at a later time it starts to fail, we should re-configure the +// ProxyResolutionService to stop using that PAC script. +TEST_F(ProxyServiceTest, PACScriptRefetchAfterSuccess) { + // Change the retry policy to wait a mere 1 ms before retrying, so the test + // runs quickly. + ImmediatePollPolicy poll_policy; + ProxyResolutionService::set_pac_script_poll_policy(&poll_policy); + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(GURL("http://request1"), std::string(), &info1, + callback1.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // The first request should have triggered initial download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // Nothing has been sent to the factory yet. + EXPECT_TRUE(factory->pending_requests().empty()); + + // At this point the ProxyResolutionService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + // Now that the PAC script is downloaded, the request will have been sent to + // the proxy resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://request1"), resolver.pending_jobs()[0]->url()); + + // Complete the pending request. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request1:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // At this point we have initialized the proxy service using a PAC script. + // + // A background task to periodically re-check the PAC script for validity will + // have been started. We will now wait for the next download attempt to start. + // + // Note that we shouldn't have to wait long here, since our test enables a + // special unit-test mode. + fetcher->WaitUntilFetch(); + + ASSERT_TRUE(factory->pending_requests().empty()); + ASSERT_TRUE(resolver.pending_jobs().empty()); + + // Make sure that our background checker is trying to download the expected + // PAC script (same one as before). This time we will simulate a failure + // to download the script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(ERR_FAILED, std::string()); + + base::RunLoop().RunUntilIdle(); + + // At this point the ProxyResolutionService should have re-configured itself + // to use DIRECT connections rather than the given proxy resolver. + + // Start a second request. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy(GURL("http://request2"), std::string(), &info2, + callback2.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_TRUE(info2.is_direct()); +} + +// Tests that the code which decides at what times to poll the PAC +// script follows the expected policy. +TEST_F(ProxyServiceTest, PACScriptPollingPolicy) { + // Retrieve the internal polling policy implementation used by + // ProxyResolutionService. + std::unique_ptr<ProxyResolutionService::PacPollPolicy> policy = + ProxyResolutionService::CreateDefaultPacPollPolicy(); + + int error; + ProxyResolutionService::PacPollPolicy::Mode mode; + const base::TimeDelta initial_delay = base::TimeDelta::FromMilliseconds(-1); + base::TimeDelta delay = initial_delay; + + // -------------------------------------------------- + // Test the poll sequence in response to a failure. + // -------------------------------------------------- + error = ERR_NAME_NOT_RESOLVED; + + // Poll #0 + mode = policy->GetNextDelay(error, initial_delay, &delay); + EXPECT_EQ(8, delay.InSeconds()); + EXPECT_EQ(ProxyResolutionService::PacPollPolicy::MODE_USE_TIMER, mode); + + // Poll #1 + mode = policy->GetNextDelay(error, delay, &delay); + EXPECT_EQ(32, delay.InSeconds()); + EXPECT_EQ(ProxyResolutionService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, + mode); + + // Poll #2 + mode = policy->GetNextDelay(error, delay, &delay); + EXPECT_EQ(120, delay.InSeconds()); + EXPECT_EQ(ProxyResolutionService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, + mode); + + // Poll #3 + mode = policy->GetNextDelay(error, delay, &delay); + EXPECT_EQ(14400, delay.InSeconds()); + EXPECT_EQ(ProxyResolutionService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, + mode); + + // Poll #4 + mode = policy->GetNextDelay(error, delay, &delay); + EXPECT_EQ(14400, delay.InSeconds()); + EXPECT_EQ(ProxyResolutionService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, + mode); + + // -------------------------------------------------- + // Test the poll sequence in response to a success. + // -------------------------------------------------- + error = OK; + + // Poll #0 + mode = policy->GetNextDelay(error, initial_delay, &delay); + EXPECT_EQ(43200, delay.InSeconds()); + EXPECT_EQ(ProxyResolutionService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, + mode); + + // Poll #1 + mode = policy->GetNextDelay(error, delay, &delay); + EXPECT_EQ(43200, delay.InSeconds()); + EXPECT_EQ(ProxyResolutionService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, + mode); + + // Poll #2 + mode = policy->GetNextDelay(error, delay, &delay); + EXPECT_EQ(43200, delay.InSeconds()); + EXPECT_EQ(ProxyResolutionService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, + mode); +} + +// This tests the polling of the PAC script. Specifically, it tests that +// polling occurs in response to user activity. +TEST_F(ProxyServiceTest, PACScriptRefetchAfterActivity) { + ImmediateAfterActivityPollPolicy poll_policy; + ProxyResolutionService::set_pac_script_poll_policy(&poll_policy); + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(GURL("http://request1"), std::string(), &info1, + callback1.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // The first request should have triggered initial download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // Nothing has been sent to the factory yet. + EXPECT_TRUE(factory->pending_requests().empty()); + + // At this point the ProxyResolutionService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + // Now that the PAC script is downloaded, the request will have been sent to + // the proxy resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + factory->pending_requests()[0]->script_data()->utf16()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://request1"), resolver.pending_jobs()[0]->url()); + + // Complete the pending request. + resolver.pending_jobs()[0]->results()->UseNamedProxy("request1:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_THAT(callback1.WaitForResult(), IsOk()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // At this point we have initialized the proxy service using a PAC script. + // Our PAC poller is set to update ONLY in response to network activity, + // (i.e. another call to ResolveProxy()). + + ASSERT_FALSE(fetcher->has_pending_request()); + ASSERT_TRUE(factory->pending_requests().empty()); + ASSERT_TRUE(resolver.pending_jobs().empty()); + + // Start a second request. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy(GURL("http://request2"), std::string(), &info2, + callback2.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // This request should have sent work to the resolver; complete it. + ASSERT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(GURL("http://request2"), resolver.pending_jobs()[0]->url()); + resolver.pending_jobs()[0]->results()->UseNamedProxy("request2:80"); + resolver.pending_jobs()[0]->CompleteNow(OK); + + EXPECT_THAT(callback2.WaitForResult(), IsOk()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); + + // In response to getting that resolve request, the poller should have + // started the next poll, and made it as far as to request the download. + + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // This time we will fail the download, to simulate a PAC script change. + fetcher->NotifyFetchCompletion(ERR_FAILED, std::string()); + + // Drain the message loop, so ProxyResolutionService is notified of the change + // and has a chance to re-configure itself. + base::RunLoop().RunUntilIdle(); + + // Start a third request -- this time we expect to get a direct connection + // since the PAC script poller experienced a failure. + ProxyInfo info3; + TestCompletionCallback callback3; + rv = service.ResolveProxy(GURL("http://request3"), std::string(), &info3, + callback3.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_TRUE(info3.is_direct()); +} + +// Test that the synchronous resolution fails when a PAC script is active. +TEST_F(ProxyServiceTest, SynchronousWithPAC) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + GURL url("http://www.google.com/"); + + ProxyInfo info; + info.UseDirect(); + BoundTestNetLog log; + + bool synchronous_success = service.TryResolveProxySynchronously( + url, std::string(), &info, nullptr, log.bound()); + EXPECT_FALSE(synchronous_success); + + // |info| should not have been modified. + EXPECT_TRUE(info.is_direct()); +} + +// Test that synchronous results are returned correctly if a fixed proxy +// configuration is active. +TEST_F(ProxyServiceTest, SynchronousWithFixedConfiguration) { + ProxyConfig config; + config.proxy_rules().ParseFromString("foopy1:8080"); + config.set_auto_detect(false); + + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(false); + + ProxyResolutionService service( + std::make_unique<MockProxyConfigService>(config), + base::WrapUnique(factory), nullptr); + + GURL url("http://www.google.com/"); + + ProxyInfo info; + BoundTestNetLog log; + + bool synchronous_success = service.TryResolveProxySynchronously( + url, std::string(), &info, nullptr, log.bound()); + EXPECT_TRUE(synchronous_success); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1", info.proxy_server().host_port_pair().host()); + + // No request should have been queued. + EXPECT_EQ(0u, factory->pending_requests().size()); +} + +// Helper class to exercise URL sanitization using the different policies. This +// works by submitted URLs to the ProxyResolutionService. In turn the +// ProxyResolutionService sanitizes the URL and then passes it along to the +// ProxyResolver. This helper returns the URL seen by the ProxyResolver. +class SanitizeUrlHelper { + public: + SanitizeUrlHelper() { + std::unique_ptr<MockProxyConfigService> config_service( + new MockProxyConfigService("http://foopy/proxy.pac")); + + factory = new MockAsyncProxyResolverFactory(false); + + service_.reset(new ProxyResolutionService( + std::move(config_service), base::WrapUnique(factory), nullptr)); + + // Do an initial request to initialize the service (configure the PAC + // script). + GURL url("http://example.com"); + + ProxyInfo info; + TestCompletionCallback callback; + int rv = + service_->ResolveProxy(url, std::string(), &info, callback.callback(), + nullptr, nullptr, NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // First step is to download the PAC script. + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + factory->pending_requests()[0]->script_data()->url()); + factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver); + + EXPECT_EQ(1u, resolver.pending_jobs().size()); + EXPECT_EQ(url, resolver.pending_jobs()[0]->url()); + + // Complete the request. + resolver.pending_jobs()[0]->results()->UsePacString("DIRECT"); + resolver.pending_jobs()[0]->CompleteNow(OK); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_TRUE(info.is_direct()); + } + + // Changes the URL sanitization policy for the underlying + // ProxyResolutionService. This will affect subsequent calls to SanitizeUrl. + void SetSanitizeUrlPolicy(ProxyResolutionService::SanitizeUrlPolicy policy) { + service_->set_sanitize_url_policy(policy); + } + + // Makes a proxy resolution request through the ProxyResolutionService, and + // returns the URL that was submitted to the Proxy Resolver. + GURL SanitizeUrl(const GURL& raw_url) { + // Issue a request and see what URL is sent to the proxy resolver. + ProxyInfo info; + TestCompletionCallback callback; + int rv = service_->ResolveProxy(raw_url, std::string(), &info, + callback.callback(), nullptr, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + EXPECT_EQ(1u, resolver.pending_jobs().size()); + + GURL sanitized_url = resolver.pending_jobs()[0]->url(); + + // Complete the request. + resolver.pending_jobs()[0]->results()->UsePacString("DIRECT"); + resolver.pending_jobs()[0]->CompleteNow(OK); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_TRUE(info.is_direct()); + + return sanitized_url; + } + + // Changes the ProxyResolutionService's URL sanitization policy and then + // sanitizes |raw_url|. + GURL SanitizeUrl(const GURL& raw_url, + ProxyResolutionService::SanitizeUrlPolicy policy) { + service_->set_sanitize_url_policy(policy); + return SanitizeUrl(raw_url); + } + + private: + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory; + std::unique_ptr<ProxyResolutionService> service_; +}; + +TEST_F(ProxyServiceTest, SanitizeUrlDefaultsToSafe) { + SanitizeUrlHelper helper; + + // Without changing the URL sanitization policy, the default should be to + // strip https:// URLs. + EXPECT_EQ(GURL("https://example.com/"), + helper.SanitizeUrl( + GURL("https://foo:bar@example.com/foo/bar/baz?hello#sigh"))); +} + +// Tests URL sanitization with input URLs that have a // non-cryptographic +// scheme (i.e. http://). The sanitized result is consistent regardless of the +// stripping mode selected. +TEST_F(ProxyServiceTest, SanitizeUrlForPacScriptNonCryptographic) { + const struct { + const char* raw_url; + const char* sanitized_url; + } kTests[] = { + // Embedded identity is stripped. + { + "http://foo:bar@example.com/", "http://example.com/", + }, + { + "ftp://foo:bar@example.com/", "ftp://example.com/", + }, + { + "ftp://example.com/some/path/here", + "ftp://example.com/some/path/here", + }, + // Reference fragment is stripped. + { + "http://example.com/blah#hello", "http://example.com/blah", + }, + // Query parameters are NOT stripped. + { + "http://example.com/foo/bar/baz?hello", + "http://example.com/foo/bar/baz?hello", + }, + // Fragment is stripped, but path and query are left intact. + { + "http://foo:bar@example.com/foo/bar/baz?hello#sigh", + "http://example.com/foo/bar/baz?hello", + }, + // Port numbers are not affected. + { + "http://example.com:88/hi", "http://example.com:88/hi", + }, + }; + + SanitizeUrlHelper helper; + + for (const auto& test : kTests) { + // The result of SanitizeUrlForPacScript() is the same regardless of the + // second parameter (sanitization mode), since the input URLs do not use a + // cryptographic scheme. + GURL raw_url(test.raw_url); + ASSERT_TRUE(raw_url.is_valid()); + EXPECT_FALSE(raw_url.SchemeIsCryptographic()); + + EXPECT_EQ(GURL(test.sanitized_url), + helper.SanitizeUrl( + raw_url, ProxyResolutionService::SanitizeUrlPolicy::UNSAFE)); + + EXPECT_EQ(GURL(test.sanitized_url), + helper.SanitizeUrl( + raw_url, ProxyResolutionService::SanitizeUrlPolicy::SAFE)); + } +} + +// Tests URL sanitization using input URLs that have a cryptographic schemes +// (i.e. https://). The sanitized result differs depending on the sanitization +// mode chosen. +TEST_F(ProxyServiceTest, SanitizeUrlForPacScriptCryptographic) { + const struct { + // Input URL. + const char* raw_url; + + // Output URL when stripping of cryptographic URLs is disabled. + const char* sanitized_url_unstripped; + + // Output URL when stripping of cryptographic URLs is enabled. + const char* sanitized_url; + } kTests[] = { + // Embedded identity is always stripped. + { + "https://foo:bar@example.com/", "https://example.com/", + "https://example.com/", + }, + // Fragments are always stripped, but stripping path is conditional on the + // mode. + { + "https://example.com/blah#hello", "https://example.com/blah", + "https://example.com/", + }, + // Stripping the query is conditional on the mode. + { + "https://example.com/?hello", "https://example.com/?hello", + "https://example.com/", + }, + // The embedded identity and fragment is always stripped, however path and + // query are conditional on the stripping mode. + { + "https://foo:bar@example.com/foo/bar/baz?hello#sigh", + "https://example.com/foo/bar/baz?hello", "https://example.com/", + }, + // The URL's port should not be stripped. + { + "https://example.com:88/hi", "https://example.com:88/hi", + "https://example.com:88/", + }, + // Try a wss:// URL, to make sure it also strips (is is also a + // cryptographic URL). + { + "wss://example.com:88/hi", "wss://example.com:88/hi", + "wss://example.com:88/", + }, + }; + + SanitizeUrlHelper helper; + + for (const auto& test : kTests) { + GURL raw_url(test.raw_url); + ASSERT_TRUE(raw_url.is_valid()); + EXPECT_TRUE(raw_url.SchemeIsCryptographic()); + + EXPECT_EQ(GURL(test.sanitized_url_unstripped), + helper.SanitizeUrl( + raw_url, ProxyResolutionService::SanitizeUrlPolicy::UNSAFE)); + + EXPECT_EQ(GURL(test.sanitized_url), + helper.SanitizeUrl( + raw_url, ProxyResolutionService::SanitizeUrlPolicy::SAFE)); + } +} + +TEST_F(ProxyServiceTest, OnShutdownWithLiveRequest) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + ProxyInfo info; + TestCompletionCallback callback; + ProxyResolutionService::Request* request; + int rv = service.ResolveProxy(GURL("http://request/"), std::string(), &info, + callback.callback(), &request, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); + + // The first request should have triggered download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + service.OnShutdown(); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_FALSE(fetcher->has_pending_request()); + EXPECT_TRUE(info.is_direct()); +} + +TEST_F(ProxyServiceTest, OnShutdownFollowedByRequest) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver resolver; + MockAsyncProxyResolverFactory* factory = + new MockAsyncProxyResolverFactory(true); + + ProxyResolutionService service(base::WrapUnique(config_service), + base::WrapUnique(factory), nullptr); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers( + base::WrapUnique(fetcher), + std::make_unique<DoNothingDhcpProxyScriptFetcher>()); + + service.OnShutdown(); + + ProxyInfo info; + TestCompletionCallback callback; + ProxyResolutionService::Request* request; + int rv = service.ResolveProxy(GURL("http://request/"), std::string(), &info, + callback.callback(), &request, nullptr, + NetLogWithSource()); + EXPECT_THAT(rv, IsOk()); + EXPECT_FALSE(fetcher->has_pending_request()); + EXPECT_TRUE(info.is_direct()); +} + +} // namespace net |