summaryrefslogtreecommitdiff
path: root/chromium/net/proxy_resolution
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/proxy_resolution')
-rw-r--r--chromium/net/proxy_resolution/OWNERS6
-rw-r--r--chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.cc296
-rw-r--r--chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h194
-rw-r--r--chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win_unittest.cc340
-rw-r--r--chromium/net/proxy_resolution/dhcp_pac_file_fetcher.cc40
-rw-r--r--chromium/net/proxy_resolution/dhcp_pac_file_fetcher.h107
-rw-r--r--chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.cc29
-rw-r--r--chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.h56
-rw-r--r--chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory_unittest.cc35
-rw-r--r--chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.cc465
-rw-r--r--chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.h186
-rw-r--r--chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win_unittest.cc682
-rw-r--r--chromium/net/proxy_resolution/dhcpcsvc_init_win.cc39
-rw-r--r--chromium/net/proxy_resolution/dhcpcsvc_init_win.h20
-rw-r--r--chromium/net/proxy_resolution/mock_pac_file_fetcher.cc79
-rw-r--r--chromium/net/proxy_resolution/mock_pac_file_fetcher.h50
-rw-r--r--chromium/net/proxy_resolution/mock_proxy_resolver.cc179
-rw-r--r--chromium/net/proxy_resolution/mock_proxy_resolver.h166
-rw-r--r--chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings.h70
-rw-r--r--chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings_unittest.cc61
-rw-r--r--chromium/net/proxy_resolution/multi_threaded_proxy_resolver.cc625
-rw-r--r--chromium/net/proxy_resolution/multi_threaded_proxy_resolver.h81
-rw-r--r--chromium/net/proxy_resolution/multi_threaded_proxy_resolver_unittest.cc784
-rw-r--r--chromium/net/proxy_resolution/network_delegate_error_observer.cc92
-rw-r--r--chromium/net/proxy_resolution/network_delegate_error_observer.h50
-rw-r--r--chromium/net/proxy_resolution/network_delegate_error_observer_unittest.cc116
-rw-r--r--chromium/net/proxy_resolution/pac_file_data.cc72
-rw-r--r--chromium/net/proxy_resolution/pac_file_data.h71
-rw-r--r--chromium/net/proxy_resolution/pac_file_decider.cc502
-rw-r--r--chromium/net/proxy_resolution/pac_file_decider.h211
-rw-r--r--chromium/net/proxy_resolution/pac_file_decider_unittest.cc809
-rw-r--r--chromium/net/proxy_resolution/pac_file_fetcher.h67
-rw-r--r--chromium/net/proxy_resolution/pac_file_fetcher_impl.cc402
-rw-r--r--chromium/net/proxy_resolution/pac_file_fetcher_impl.h139
-rw-r--r--chromium/net/proxy_resolution/pac_file_fetcher_impl_unittest.cc601
-rw-r--r--chromium/net/proxy_resolution/pac_js_library.h282
-rw-r--r--chromium/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc24
-rw-r--r--chromium/net/proxy_resolution/parse_proxy_list_fuzzer.cc16
-rw-r--r--chromium/net/proxy_resolution/parse_proxy_list_pac_fuzzer.cc16
-rw-r--r--chromium/net/proxy_resolution/parse_proxy_rules_fuzzer.cc16
-rw-r--r--chromium/net/proxy_resolution/polling_proxy_config_service.cc194
-rw-r--r--chromium/net/proxy_resolution/polling_proxy_config_service.h57
-rw-r--r--chromium/net/proxy_resolution/proxy_bypass_rules.cc344
-rw-r--r--chromium/net/proxy_resolution/proxy_bypass_rules.h181
-rw-r--r--chromium/net/proxy_resolution/proxy_bypass_rules_unittest.cc324
-rw-r--r--chromium/net/proxy_resolution/proxy_config.cc281
-rw-r--r--chromium/net/proxy_resolution/proxy_config.h255
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service.h68
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_android.cc404
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_android.h98
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_android_unittest.cc366
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_common_unittest.cc191
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_common_unittest.h80
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_fixed.cc21
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_fixed.h33
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_ios.cc109
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_ios.h25
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_linux.cc1413
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_linux.h315
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc1912
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_mac.cc284
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_mac.h85
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_win.cc170
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_win.h83
-rw-r--r--chromium/net/proxy_resolution/proxy_config_service_win_unittest.cc215
-rw-r--r--chromium/net/proxy_resolution/proxy_config_source.cc33
-rw-r--r--chromium/net/proxy_resolution/proxy_config_source.h36
-rw-r--r--chromium/net/proxy_resolution/proxy_config_unittest.cc428
-rw-r--r--chromium/net/proxy_resolution/proxy_info.cc102
-rw-r--r--chromium/net/proxy_resolution/proxy_info.h212
-rw-r--r--chromium/net/proxy_resolution/proxy_info_unittest.cc60
-rw-r--r--chromium/net/proxy_resolution/proxy_list.cc223
-rw-r--r--chromium/net/proxy_resolution/proxy_list.h134
-rw-r--r--chromium/net/proxy_resolution/proxy_list_unittest.cc316
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver.h58
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_error_observer.h37
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_factory.cc18
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_factory.h61
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_mac.cc356
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_mac.h36
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_v8.cc910
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_v8.h86
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_v8_tracing.cc1119
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_v8_tracing.h89
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_v8_tracing_unittest.cc1070
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper.cc189
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper.h66
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_v8_tracing_wrapper_unittest.cc1192
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_v8_unittest.cc544
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_winhttp.cc215
-rw-r--r--chromium/net/proxy_resolution/proxy_resolver_winhttp.h35
-rw-r--r--chromium/net/proxy_resolution/proxy_retry_info.h40
-rw-r--r--chromium/net/proxy_resolution/proxy_server_unittest.cc338
-rw-r--r--chromium/net/proxy_resolution/proxy_service.cc1602
-rw-r--r--chromium/net/proxy_resolution/proxy_service.h464
-rw-r--r--chromium/net/proxy_resolution/proxy_service_unittest.cc3587
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