| // 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/proxy_service.h" |
| |
| #include <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/compiler_specific.h" |
| #include "base/logging.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/sparse_histogram.h" |
| #include "base/strings/string_util.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "base/values.h" |
| #include "net/base/completion_callback.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/net_log.h" |
| #include "net/base/net_util.h" |
| #include "net/proxy/dhcp_proxy_script_fetcher.h" |
| #include "net/proxy/multi_threaded_proxy_resolver.h" |
| #include "net/proxy/network_delegate_error_observer.h" |
| #include "net/proxy/proxy_config_service_fixed.h" |
| #include "net/proxy/proxy_resolver.h" |
| #include "net/proxy/proxy_script_decider.h" |
| #include "net/proxy/proxy_script_fetcher.h" |
| #include "net/url_request/url_request_context.h" |
| #include "url/gurl.h" |
| |
| #if defined(OS_WIN) |
| #include "net/proxy/proxy_config_service_win.h" |
| #include "net/proxy/proxy_resolver_winhttp.h" |
| #elif defined(OS_IOS) |
| #include "net/proxy/proxy_config_service_ios.h" |
| #include "net/proxy/proxy_resolver_mac.h" |
| #elif defined(OS_MACOSX) |
| #include "net/proxy/proxy_config_service_mac.h" |
| #include "net/proxy/proxy_resolver_mac.h" |
| #elif defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| #include "net/proxy/proxy_config_service_linux.h" |
| #elif defined(OS_ANDROID) |
| #include "net/proxy/proxy_config_service_android.h" |
| #endif |
| |
| using base::TimeDelta; |
| using base::TimeTicks; |
| |
| namespace net { |
| |
| namespace { |
| |
| // 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 ProxyService 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 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 ProxyService::PacPollPolicy { |
| public: |
| DefaultPollPolicy() {} |
| |
| virtual 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: |
| virtual void AddObserver(Observer* observer) OVERRIDE {} |
| virtual void RemoveObserver(Observer* observer) OVERRIDE {} |
| virtual 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() : ProxyResolver(false /*expects_pac_bytes*/) {} |
| |
| // ProxyResolver implementation. |
| virtual int GetProxyForURL(const GURL& url, |
| ProxyInfo* results, |
| const CompletionCallback& callback, |
| RequestHandle* request, |
| const BoundNetLog& net_log) OVERRIDE { |
| return ERR_NOT_IMPLEMENTED; |
| } |
| |
| virtual void CancelRequest(RequestHandle request) OVERRIDE { |
| NOTREACHED(); |
| } |
| |
| virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE { |
| NOTREACHED(); |
| return LOAD_STATE_IDLE; |
| } |
| |
| virtual void CancelSetPacScript() OVERRIDE { |
| NOTREACHED(); |
| } |
| |
| virtual int SetPacScript( |
| const scoped_refptr<ProxyResolverScriptData>& /*script_data*/, |
| const CompletionCallback& /*callback*/) 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) |
| : ProxyResolver(false /*expects_pac_bytes*/), |
| pac_string_(pac_string) {} |
| |
| virtual int GetProxyForURL(const GURL& url, |
| ProxyInfo* results, |
| const CompletionCallback& callback, |
| RequestHandle* request, |
| const BoundNetLog& net_log) OVERRIDE { |
| results->UsePacString(pac_string_); |
| return OK; |
| } |
| |
| virtual void CancelRequest(RequestHandle request) OVERRIDE { |
| NOTREACHED(); |
| } |
| |
| virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE { |
| NOTREACHED(); |
| return LOAD_STATE_IDLE; |
| } |
| |
| virtual void CancelSetPacScript() OVERRIDE { |
| NOTREACHED(); |
| } |
| |
| virtual int SetPacScript( |
| const scoped_refptr<ProxyResolverScriptData>& pac_script, |
| const CompletionCallback& callback) OVERRIDE { |
| return OK; |
| } |
| |
| private: |
| const std::string pac_string_; |
| }; |
| |
| // Creates ProxyResolvers using a platform-specific implementation. |
| class ProxyResolverFactoryForSystem : public ProxyResolverFactory { |
| public: |
| ProxyResolverFactoryForSystem() |
| : ProxyResolverFactory(false /*expects_pac_bytes*/) {} |
| |
| virtual ProxyResolver* CreateProxyResolver() OVERRIDE { |
| DCHECK(IsSupported()); |
| #if defined(OS_WIN) |
| return new ProxyResolverWinHttp(); |
| #elif defined(OS_MACOSX) |
| return new ProxyResolverMac(); |
| #else |
| NOTREACHED(); |
| return NULL; |
| #endif |
| } |
| |
| static bool IsSupported() { |
| #if defined(OS_WIN) || defined(OS_MACOSX) |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| }; |
| |
| // Returns NetLog parameters describing a proxy configuration change. |
| base::Value* NetLogProxyConfigChangedCallback( |
| const ProxyConfig* old_config, |
| const ProxyConfig* new_config, |
| NetLog::LogLevel /* log_level */) { |
| base::DictionaryValue* dict = new base::DictionaryValue(); |
| // The "old_config" is optional -- the first notification will not have |
| // any "previous" configuration. |
| if (old_config->is_valid()) |
| dict->Set("old_config", old_config->ToValue()); |
| dict->Set("new_config", new_config->ToValue()); |
| return dict; |
| } |
| |
| base::Value* NetLogBadProxyListCallback(const ProxyRetryInfoMap* retry_info, |
| NetLog::LogLevel /* log_level */) { |
| base::DictionaryValue* dict = new base::DictionaryValue(); |
| base::ListValue* list = new base::ListValue(); |
| |
| for (ProxyRetryInfoMap::const_iterator iter = retry_info->begin(); |
| iter != retry_info->end(); ++iter) { |
| list->Append(new base::StringValue(iter->first)); |
| } |
| dict->Set("bad_proxy_list", list); |
| return dict; |
| } |
| |
| // Returns NetLog parameters on a successfuly proxy resolution. |
| base::Value* NetLogFinishedResolvingProxyCallback( |
| ProxyInfo* result, |
| NetLog::LogLevel /* log_level */) { |
| base::DictionaryValue* dict = new base::DictionaryValue(); |
| dict->SetString("pac_string", result->ToPacString()); |
| return dict; |
| } |
| |
| #if defined(OS_CHROMEOS) |
| class UnsetProxyConfigService : public ProxyConfigService { |
| public: |
| UnsetProxyConfigService() {} |
| virtual ~UnsetProxyConfigService() {} |
| |
| virtual void AddObserver(Observer* observer) OVERRIDE {} |
| virtual void RemoveObserver(Observer* observer) OVERRIDE {} |
| virtual ConfigAvailability GetLatestProxyConfig( |
| ProxyConfig* config) OVERRIDE { |
| return CONFIG_UNSET; |
| } |
| }; |
| #endif |
| |
| } // namespace |
| |
| // ProxyService::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 ProxyService::InitProxyResolver { |
| public: |
| InitProxyResolver() |
| : proxy_resolver_(NULL), |
| next_state_(STATE_NONE), |
| quick_check_enabled_(true) { |
| } |
| |
| ~InitProxyResolver() { |
| // Note that the destruction of ProxyScriptDecider will automatically cancel |
| // any outstanding work. |
| if (next_state_ == STATE_SET_PAC_SCRIPT_COMPLETE) { |
| proxy_resolver_->CancelSetPacScript(); |
| } |
| } |
| |
| // Begins initializing the proxy resolver; calls |callback| when done. |
| int Start(ProxyResolver* proxy_resolver, |
| 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; |
| |
| 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. |
| int StartSkipDecider(ProxyResolver* proxy_resolver, |
| const ProxyConfig& effective_config, |
| int decider_result, |
| ProxyResolverScriptData* script_data, |
| const CompletionCallback& callback) { |
| DCHECK_EQ(STATE_NONE, next_state_); |
| proxy_resolver_ = proxy_resolver; |
| |
| effective_config_ = effective_config; |
| script_data_ = script_data; |
| callback_ = callback; |
| |
| if (decider_result != OK) |
| return decider_result; |
| |
| next_state_ = STATE_SET_PAC_SCRIPT; |
| 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. |
| ProxyResolverScriptData* script_data() { |
| DCHECK_EQ(STATE_NONE, next_state_); |
| return script_data_.get(); |
| } |
| |
| 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; |
| } |
| |
| 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_SET_PAC_SCRIPT, |
| STATE_SET_PAC_SCRIPT_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_SET_PAC_SCRIPT: |
| DCHECK_EQ(OK, rv); |
| rv = DoSetPacScript(); |
| break; |
| case STATE_SET_PAC_SCRIPT_COMPLETE: |
| rv = DoSetPacScriptComplete(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_->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_SET_PAC_SCRIPT; |
| return OK; |
| } |
| |
| int DoSetPacScript() { |
| DCHECK(script_data_.get()); |
| // TODO(eroman): Should log this latency to the NetLog. |
| next_state_ = STATE_SET_PAC_SCRIPT_COMPLETE; |
| return proxy_resolver_->SetPacScript( |
| script_data_, |
| base::Bind(&InitProxyResolver::OnIOCompletion, base::Unretained(this))); |
| } |
| |
| int DoSetPacScriptComplete(int result) { |
| 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_; |
| scoped_ptr<ProxyScriptDecider> decider_; |
| ProxyResolver* proxy_resolver_; |
| CompletionCallback callback_; |
| State next_state_; |
| bool quick_check_enabled_; |
| |
| DISALLOW_COPY_AND_ASSIGN(InitProxyResolver); |
| }; |
| |
| // ProxyService::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 ProxyService::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, |
| ProxyResolverScriptData* init_script_data, |
| NetLog* net_log) |
| : weak_factory_(this), |
| 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()) { |
| // 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::MessageLoop::current()->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 ProxyService 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::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&ProxyScriptDeciderPoller::NotifyProxyServiceOfChange, |
| weak_factory_.GetWeakPtr(), |
| result, |
| make_scoped_refptr(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, 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 ProxyService. |
| change_callback_.Run(result, script_data.get(), effective_config); |
| } |
| |
| base::WeakPtrFactory<ProxyScriptDeciderPoller> weak_factory_; |
| |
| 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_; |
| |
| scoped_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_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ProxyScriptDeciderPoller); |
| }; |
| |
| // static |
| const ProxyService::PacPollPolicy* |
| ProxyService::ProxyScriptDeciderPoller::poll_policy_ = NULL; |
| |
| // ProxyService::PacRequest --------------------------------------------------- |
| |
| class ProxyService::PacRequest |
| : public base::RefCounted<ProxyService::PacRequest> { |
| public: |
| PacRequest(ProxyService* service, |
| const GURL& url, |
| int load_flags, |
| NetworkDelegate* network_delegate, |
| ProxyInfo* results, |
| const net::CompletionCallback& user_callback, |
| const BoundNetLog& net_log) |
| : service_(service), |
| user_callback_(user_callback), |
| results_(results), |
| url_(url), |
| load_flags_(load_flags), |
| network_delegate_(network_delegate), |
| resolve_job_(NULL), |
| config_id_(ProxyConfig::kInvalidConfigID), |
| config_source_(PROXY_CONFIG_SOURCE_UNKNOWN), |
| net_log_(net_log) { |
| DCHECK(!user_callback.is_null()); |
| } |
| |
| // Starts the resolve proxy request. |
| int Start() { |
| DCHECK(!was_cancelled()); |
| DCHECK(!is_started()); |
| |
| DCHECK(service_->config_.is_valid()); |
| |
| config_id_ = service_->config_.id(); |
| config_source_ = service_->config_.source(); |
| proxy_resolve_start_time_ = TimeTicks::Now(); |
| |
| return resolver()->GetProxyForURL( |
| url_, results_, |
| base::Bind(&PacRequest::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_; |
| } |
| |
| void StartAndCompleteCheckingForSynchronous() { |
| int rv = service_->TryToCompleteSynchronously(url_, load_flags_, |
| network_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. |
| resolver()->CancelRequest(resolve_job_); |
| resolve_job_ = NULL; |
| DCHECK(!is_started()); |
| } |
| |
| void Cancel() { |
| net_log_.AddEvent(NetLog::TYPE_CANCELLED); |
| |
| if (is_started()) |
| CancelResolveJob(); |
| |
| // Mark as cancelled, to prevent accessing this again later. |
| service_ = NULL; |
| user_callback_.Reset(); |
| results_ = NULL; |
| |
| net_log_.EndEvent(NetLog::TYPE_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()); |
| |
| // Note that DidFinishResolvingProxy might modify |results_|. |
| int rv = service_->DidFinishResolvingProxy(url_, load_flags_, |
| network_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_id_ = config_id_; |
| results_->config_source_ = config_source_; |
| results_->did_use_pac_script_ = true; |
| results_->proxy_resolve_start_time_ = proxy_resolve_start_time_; |
| results_->proxy_resolve_end_time_ = TimeTicks::Now(); |
| |
| // Reset the state associated with in-progress-resolve. |
| resolve_job_ = NULL; |
| config_id_ = ProxyConfig::kInvalidConfigID; |
| config_source_ = PROXY_CONFIG_SOURCE_UNKNOWN; |
| |
| return rv; |
| } |
| |
| BoundNetLog* net_log() { return &net_log_; } |
| |
| LoadState GetLoadState() const { |
| if (is_started()) |
| return resolver()->GetLoadState(resolve_job_); |
| return LOAD_STATE_RESOLVING_PROXY_FOR_URL; |
| } |
| |
| private: |
| friend class base::RefCounted<ProxyService::PacRequest>; |
| |
| ~PacRequest() {} |
| |
| // Callback for when the ProxyResolver request has completed. |
| void QueryComplete(int result_code) { |
| result_code = QueryDidComplete(result_code); |
| |
| // Remove this completed PacRequest from the service's pending list. |
| /// (which will probably cause deletion of |this|). |
| if (!user_callback_.is_null()) { |
| net::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 ProxyService. Outstanding |
| // requests are cancelled during ~ProxyService, so this is guaranteed |
| // to be valid throughout our lifetime. |
| ProxyService* service_; |
| net::CompletionCallback user_callback_; |
| ProxyInfo* results_; |
| GURL url_; |
| int load_flags_; |
| NetworkDelegate* network_delegate_; |
| ProxyResolver::RequestHandle resolve_job_; |
| ProxyConfig::ID config_id_; // The config id when the resolve was started. |
| ProxyConfigSource config_source_; // The source of proxy settings. |
| BoundNetLog net_log_; |
| // Time when the PAC is started. Cached here since resetting ProxyInfo also |
| // clears the proxy times. |
| TimeTicks proxy_resolve_start_time_; |
| }; |
| |
| // ProxyService --------------------------------------------------------------- |
| |
| ProxyService::ProxyService(ProxyConfigService* config_service, |
| ProxyResolver* resolver, |
| NetLog* net_log) |
| : resolver_(resolver), |
| next_config_id_(1), |
| current_state_(STATE_NONE), |
| net_log_(net_log), |
| stall_proxy_auto_config_delay_(TimeDelta::FromMilliseconds( |
| kDelayAfterNetworkChangesMs)), |
| quick_check_enabled_(true) { |
| NetworkChangeNotifier::AddIPAddressObserver(this); |
| NetworkChangeNotifier::AddDNSObserver(this); |
| ResetConfigService(config_service); |
| } |
| |
| // static |
| ProxyService* ProxyService::CreateUsingSystemProxyResolver( |
| ProxyConfigService* proxy_config_service, |
| size_t num_pac_threads, |
| NetLog* net_log) { |
| DCHECK(proxy_config_service); |
| |
| if (!ProxyResolverFactoryForSystem::IsSupported()) { |
| LOG(WARNING) << "PAC support disabled because there is no " |
| "system implementation"; |
| return CreateWithoutProxyResolver(proxy_config_service, net_log); |
| } |
| |
| if (num_pac_threads == 0) |
| num_pac_threads = kDefaultNumPacThreads; |
| |
| ProxyResolver* proxy_resolver = new MultiThreadedProxyResolver( |
| new ProxyResolverFactoryForSystem(), num_pac_threads); |
| |
| return new ProxyService(proxy_config_service, proxy_resolver, net_log); |
| } |
| |
| // static |
| ProxyService* ProxyService::CreateWithoutProxyResolver( |
| ProxyConfigService* proxy_config_service, |
| NetLog* net_log) { |
| return new ProxyService(proxy_config_service, |
| new ProxyResolverNull(), |
| net_log); |
| } |
| |
| // static |
| ProxyService* ProxyService::CreateFixed(const ProxyConfig& pc) { |
| // TODO(eroman): This isn't quite right, won't work if |pc| specifies |
| // a PAC script. |
| return CreateUsingSystemProxyResolver(new ProxyConfigServiceFixed(pc), |
| 0, NULL); |
| } |
| |
| // static |
| ProxyService* ProxyService::CreateFixed(const std::string& proxy) { |
| net::ProxyConfig proxy_config; |
| proxy_config.proxy_rules().ParseFromString(proxy); |
| return ProxyService::CreateFixed(proxy_config); |
| } |
| |
| // static |
| ProxyService* ProxyService::CreateDirect() { |
| return CreateDirectWithNetLog(NULL); |
| } |
| |
| ProxyService* ProxyService::CreateDirectWithNetLog(NetLog* net_log) { |
| // Use direct connections. |
| return new ProxyService(new ProxyConfigServiceDirect, new ProxyResolverNull, |
| net_log); |
| } |
| |
| // static |
| ProxyService* ProxyService::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. |
| scoped_ptr<ProxyConfigService> proxy_config_service( |
| new ProxyConfigServiceFixed(ProxyConfig::CreateAutoDetect())); |
| |
| scoped_ptr<ProxyResolver> proxy_resolver( |
| new ProxyResolverFromPacString(pac_string)); |
| |
| return new ProxyService(proxy_config_service.release(), |
| proxy_resolver.release(), |
| NULL); |
| } |
| |
| int ProxyService::ResolveProxy(const GURL& raw_url, |
| int load_flags, |
| ProxyInfo* result, |
| const net::CompletionCallback& callback, |
| PacRequest** pac_request, |
| NetworkDelegate* network_delegate, |
| const BoundNetLog& net_log) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(!callback.is_null()); |
| |
| net_log.BeginEvent(NetLog::TYPE_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(); |
| |
| // Strip away any reference fragments and the username/password, as they |
| // are not relevant to proxy resolution. |
| GURL url = SimplifyUrlForRequest(raw_url); |
| |
| // Check if the request can be completed right away. (This is the case when |
| // using a direct connection for example). |
| int rv = TryToCompleteSynchronously(url, load_flags, |
| network_delegate, result); |
| if (rv != ERR_IO_PENDING) |
| return DidFinishResolvingProxy(url, load_flags, network_delegate, |
| result, rv, net_log); |
| |
| scoped_refptr<PacRequest> req( |
| new PacRequest(this, url, load_flags, network_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(NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC); |
| } |
| |
| DCHECK_EQ(ERR_IO_PENDING, rv); |
| DCHECK(!ContainsPendingRequest(req.get())); |
| pending_requests_.push_back(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 |
| } |
| |
| int ProxyService::TryToCompleteSynchronously(const GURL& url, |
| int load_flags, |
| NetworkDelegate* network_delegate, |
| ProxyInfo* result) { |
| DCHECK_NE(STATE_NONE, current_state_); |
| |
| if (current_state_ != STATE_READY) |
| return ERR_IO_PENDING; // Still initializing. |
| |
| DCHECK_NE(config_.id(), ProxyConfig::kInvalidConfigID); |
| |
| // 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(); |
| result->config_id_ = config_.id(); |
| |
| return OK; |
| } |
| |
| ProxyService::~ProxyService() { |
| 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 ProxyService::SuspendAllPendingRequests() { |
| for (PendingRequests::iterator it = pending_requests_.begin(); |
| it != pending_requests_.end(); |
| ++it) { |
| PacRequest* req = it->get(); |
| if (req->is_started()) { |
| req->CancelResolveJob(); |
| |
| req->net_log()->BeginEvent( |
| NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC); |
| } |
| } |
| } |
| |
| void ProxyService::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 PacRequest |
| // instances will be Cancel()-ed. |
| PendingRequests pending_copy = pending_requests_; |
| |
| for (PendingRequests::iterator it = pending_copy.begin(); |
| it != pending_copy.end(); |
| ++it) { |
| PacRequest* req = it->get(); |
| if (!req->is_started() && !req->was_cancelled()) { |
| req->net_log()->EndEvent(NetLog::TYPE_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 ProxyService::ApplyProxyConfigIfAvailable() { |
| DCHECK_EQ(STATE_NONE, current_state_); |
| |
| config_service_->OnLazyPoll(); |
| |
| // If we have already fetched the configuration, start applying it. |
| if (fetched_config_.is_valid()) { |
| 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 ProxyService::OnInitProxyResolverComplete(int result) { |
| DCHECK_EQ(STATE_WAITING_FOR_INIT_PROXY_RESOLVER, current_state_); |
| DCHECK(init_proxy_resolver_.get()); |
| 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(&ProxyService::InitializeUsingDecidedConfig, |
| base::Unretained(this)), |
| fetched_config_, |
| resolver_->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; |
| |
| // TODO(eroman): Make this ID unique in the case where configuration changed |
| // due to ProxyScriptDeciderPoller. |
| config_.set_id(fetched_config_.id()); |
| config_.set_source(fetched_config_.source()); |
| |
| // Resume any requests which we had to defer until the PAC script was |
| // downloaded. |
| SetReady(); |
| } |
| |
| int ProxyService::ReconsiderProxyAfterError(const GURL& url, |
| int load_flags, |
| int net_error, |
| ProxyInfo* result, |
| const CompletionCallback& callback, |
| PacRequest** pac_request, |
| NetworkDelegate* network_delegate, |
| const BoundNetLog& net_log) { |
| DCHECK(CalledOnValidThread()); |
| |
| // Check to see if we have a new config since ResolveProxy was called. We |
| // want to re-run ResolveProxy in two cases: 1) we have a new config, or 2) a |
| // direct connection failed and we never tried the current config. |
| |
| bool re_resolve = result->config_id_ != config_.id(); |
| |
| if (re_resolve) { |
| // If we have a new config or the config was never tried, we delete the |
| // list of bad proxies and we try again. |
| proxy_retry_info_.clear(); |
| return ResolveProxy(url, load_flags, result, callback, pac_request, |
| network_delegate, net_log); |
| } |
| |
| #if defined(SPDY_PROXY_AUTH_ORIGIN) |
| if (result->proxy_server().isDataReductionProxy()) { |
| RecordDataReductionProxyBypassInfo( |
| true, false, result->proxy_server(), NETWORK_ERROR); |
| RecordDataReductionProxyBypassOnNetworkError( |
| true, result->proxy_server(), net_error); |
| } else if (result->proxy_server().isDataReductionProxyFallback()) { |
| RecordDataReductionProxyBypassInfo( |
| false, false, result->proxy_server(), NETWORK_ERROR); |
| RecordDataReductionProxyBypassOnNetworkError( |
| false, result->proxy_server(), net_error); |
| } |
| #endif |
| |
| // We don't have new proxy settings to try, try to fallback to the next proxy |
| // in the list. |
| bool did_fallback = result->Fallback(net_log); |
| |
| // Return synchronous failure if there is nothing left to fall-back to. |
| // TODO(eroman): This is a yucky API, clean it up. |
| return did_fallback ? OK : ERR_FAILED; |
| } |
| |
| bool ProxyService::MarkProxiesAsBadUntil( |
| const ProxyInfo& result, |
| base::TimeDelta retry_delay, |
| const ProxyServer& another_bad_proxy, |
| const BoundNetLog& net_log) { |
| result.proxy_list_.UpdateRetryInfoOnFallback(&proxy_retry_info_, retry_delay, |
| false, |
| another_bad_proxy, |
| net_log); |
| if (another_bad_proxy.is_valid()) |
| return result.proxy_list_.size() > 2; |
| else |
| return result.proxy_list_.size() > 1; |
| } |
| |
| void ProxyService::ReportSuccess(const ProxyInfo& result) { |
| DCHECK(CalledOnValidThread()); |
| |
| 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; |
| else if (existing->second.bad_until < iter->second.bad_until) |
| existing->second.bad_until = iter->second.bad_until; |
| } |
| if (net_log_) { |
| net_log_->AddGlobalEntry( |
| NetLog::TYPE_BAD_PROXY_LIST_REPORTED, |
| base::Bind(&NetLogBadProxyListCallback, &new_retry_info)); |
| } |
| } |
| |
| void ProxyService::CancelPacRequest(PacRequest* req) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(req); |
| req->Cancel(); |
| RemovePendingRequest(req); |
| } |
| |
| LoadState ProxyService::GetLoadState(const PacRequest* req) const { |
| CHECK(req); |
| if (current_state_ == STATE_WAITING_FOR_INIT_PROXY_RESOLVER) |
| return init_proxy_resolver_->GetLoadState(); |
| return req->GetLoadState(); |
| } |
| |
| bool ProxyService::ContainsPendingRequest(PacRequest* req) { |
| PendingRequests::iterator it = std::find( |
| pending_requests_.begin(), pending_requests_.end(), req); |
| return pending_requests_.end() != it; |
| } |
| |
| void ProxyService::RemovePendingRequest(PacRequest* req) { |
| DCHECK(ContainsPendingRequest(req)); |
| PendingRequests::iterator it = std::find( |
| pending_requests_.begin(), pending_requests_.end(), req); |
| pending_requests_.erase(it); |
| } |
| |
| int ProxyService::DidFinishResolvingProxy(const GURL& url, |
| int load_flags, |
| NetworkDelegate* network_delegate, |
| ProxyInfo* result, |
| int result_code, |
| const BoundNetLog& net_log) { |
| // Log the result of the proxy resolution. |
| if (result_code == OK) { |
| // Allow the network delegate to interpose on the resolution decision, |
| // possibly modifying the ProxyInfo. |
| if (network_delegate) |
| network_delegate->NotifyResolveProxy(url, load_flags, result); |
| |
| // When logging all events is enabled, dump the proxy list. |
| if (net_log.IsLogging()) { |
| net_log.AddEvent( |
| NetLog::TYPE_PROXY_SERVICE_RESOLVED_PROXY_LIST, |
| base::Bind(&NetLogFinishedResolvingProxyCallback, result)); |
| } |
| result->DeprioritizeBadProxies(proxy_retry_info_); |
| } else { |
| net_log.AddEventWithNetErrorCode( |
| NetLog::TYPE_PROXY_SERVICE_RESOLVED_PROXY_LIST, result_code); |
| |
| 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 network delegate to interpose on the resolution decision, |
| // possibly modifying the ProxyInfo. |
| if (network_delegate) |
| network_delegate->NotifyResolveProxy(url, load_flags, result); |
| } else { |
| result_code = ERR_MANDATORY_PROXY_CONFIGURATION_FAILED; |
| } |
| } |
| |
| net_log.EndEvent(NetLog::TYPE_PROXY_SERVICE); |
| return result_code; |
| } |
| |
| void ProxyService::SetProxyScriptFetchers( |
| ProxyScriptFetcher* proxy_script_fetcher, |
| DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher) { |
| DCHECK(CalledOnValidThread()); |
| State previous_state = ResetProxyConfig(false); |
| proxy_script_fetcher_.reset(proxy_script_fetcher); |
| dhcp_proxy_script_fetcher_.reset(dhcp_proxy_script_fetcher); |
| if (previous_state != STATE_NONE) |
| ApplyProxyConfigIfAvailable(); |
| } |
| |
| ProxyScriptFetcher* ProxyService::GetProxyScriptFetcher() const { |
| DCHECK(CalledOnValidThread()); |
| return proxy_script_fetcher_.get(); |
| } |
| |
| ProxyService::State ProxyService::ResetProxyConfig(bool reset_fetched_config) { |
| DCHECK(CalledOnValidThread()); |
| State previous_state = current_state_; |
| |
| permanent_error_ = OK; |
| proxy_retry_info_.clear(); |
| script_poller_.reset(); |
| init_proxy_resolver_.reset(); |
| SuspendAllPendingRequests(); |
| config_ = ProxyConfig(); |
| if (reset_fetched_config) |
| fetched_config_ = ProxyConfig(); |
| current_state_ = STATE_NONE; |
| |
| return previous_state; |
| } |
| |
| void ProxyService::ResetConfigService( |
| ProxyConfigService* new_proxy_config_service) { |
| DCHECK(CalledOnValidThread()); |
| 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_.reset(new_proxy_config_service); |
| config_service_->AddObserver(this); |
| |
| if (previous_state != STATE_NONE) |
| ApplyProxyConfigIfAvailable(); |
| } |
| |
| void ProxyService::ForceReloadProxyConfig() { |
| DCHECK(CalledOnValidThread()); |
| ResetProxyConfig(false); |
| ApplyProxyConfigIfAvailable(); |
| } |
| |
| // static |
| ProxyConfigService* ProxyService::CreateSystemProxyConfigService( |
| base::SingleThreadTaskRunner* io_thread_task_runner, |
| base::MessageLoop* file_loop) { |
| #if defined(OS_WIN) |
| return new ProxyConfigServiceWin(); |
| #elif defined(OS_IOS) |
| return new ProxyConfigServiceIOS(); |
| #elif defined(OS_MACOSX) |
| return new ProxyConfigServiceMac(io_thread_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 new UnsetProxyConfigService; |
| #elif defined(OS_LINUX) |
| 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 |
| // gconf calls from. |
| scoped_refptr<base::SingleThreadTaskRunner> glib_thread_task_runner = |
| base::ThreadTaskRunnerHandle::Get(); |
| |
| // The file loop should be a MessageLoopForIO on Linux. |
| DCHECK_EQ(base::MessageLoop::TYPE_IO, file_loop->type()); |
| |
| // 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 |
| // |file_loop|) to keep us updated when the proxy config changes. |
| linux_config_service->SetupAndFetchInitialConfig( |
| glib_thread_task_runner.get(), |
| io_thread_task_runner, |
| static_cast<base::MessageLoopForIO*>(file_loop)); |
| |
| return linux_config_service; |
| #elif defined(OS_ANDROID) |
| return new ProxyConfigServiceAndroid( |
| io_thread_task_runner, |
| base::MessageLoop::current()->message_loop_proxy()); |
| #else |
| LOG(WARNING) << "Failed to choose a system proxy settings fetcher " |
| "for this platform."; |
| return new ProxyConfigServiceDirect(); |
| #endif |
| } |
| |
| // static |
| const ProxyService::PacPollPolicy* ProxyService::set_pac_script_poll_policy( |
| const PacPollPolicy* policy) { |
| return ProxyScriptDeciderPoller::set_policy(policy); |
| } |
| |
| // static |
| scoped_ptr<ProxyService::PacPollPolicy> |
| ProxyService::CreateDefaultPacPollPolicy() { |
| return scoped_ptr<PacPollPolicy>(new DefaultPollPolicy()); |
| } |
| |
| void ProxyService::RecordDataReductionProxyBypassInfo( |
| bool is_primary, |
| bool bypass_all, |
| const ProxyServer& proxy_server, |
| DataReductionProxyBypassType bypass_type) const { |
| // Only record UMA if the proxy isn't already on the retry list. |
| if (proxy_retry_info_.find(proxy_server.ToURI()) != proxy_retry_info_.end()) |
| return; |
| |
| if (bypass_all) { |
| if (is_primary) { |
| UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BlockTypePrimary", |
| bypass_type, BYPASS_EVENT_TYPE_MAX); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BlockTypeFallback", |
| bypass_type, BYPASS_EVENT_TYPE_MAX); |
| } |
| } else { |
| if (is_primary) { |
| UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BypassTypePrimary", |
| bypass_type, BYPASS_EVENT_TYPE_MAX); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BypassTypeFallback", |
| bypass_type, BYPASS_EVENT_TYPE_MAX); |
| } |
| } |
| } |
| |
| void ProxyService::RecordDataReductionProxyBypassOnNetworkError( |
| bool is_primary, |
| const ProxyServer& proxy_server, |
| int net_error) { |
| // Only record UMA if the proxy isn't already on the retry list. |
| if (proxy_retry_info_.find(proxy_server.ToURI()) != proxy_retry_info_.end()) |
| return; |
| |
| if (is_primary) { |
| UMA_HISTOGRAM_SPARSE_SLOWLY( |
| "DataReductionProxy.BypassOnNetworkErrorPrimary", |
| std::abs(net_error)); |
| return; |
| } |
| UMA_HISTOGRAM_SPARSE_SLOWLY( |
| "DataReductionProxy.BypassOnNetworkErrorFallback", |
| std::abs(net_error)); |
| } |
| |
| void ProxyService::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( |
| net::NetLog::TYPE_PROXY_CONFIG_CHANGED, |
| base::Bind(&NetLogProxyConfigChangedCallback, |
| &fetched_config_, &effective_config)); |
| } |
| |
| // Set the new configuration as the most recently fetched one. |
| fetched_config_ = effective_config; |
| fetched_config_.set_id(1); // Needed for a later DCHECK of is_valid(). |
| |
| InitializeUsingLastFetchedConfig(); |
| } |
| |
| void ProxyService::InitializeUsingLastFetchedConfig() { |
| ResetProxyConfig(false); |
| |
| DCHECK(fetched_config_.is_valid()); |
| |
| // Increment the ID to reflect that the config has changed. |
| fetched_config_.set_id(next_config_id_++); |
| |
| 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_.get(), |
| proxy_script_fetcher_.get(), |
| dhcp_proxy_script_fetcher_.get(), |
| net_log_, |
| fetched_config_, |
| wait_delay, |
| base::Bind(&ProxyService::OnInitProxyResolverComplete, |
| base::Unretained(this))); |
| |
| if (rv != ERR_IO_PENDING) |
| OnInitProxyResolverComplete(rv); |
| } |
| |
| void ProxyService::InitializeUsingDecidedConfig( |
| int decider_result, |
| ProxyResolverScriptData* script_data, |
| const ProxyConfig& effective_config) { |
| DCHECK(fetched_config_.is_valid()); |
| 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_.get(), |
| effective_config, |
| decider_result, |
| script_data, |
| base::Bind(&ProxyService::OnInitProxyResolverComplete, |
| base::Unretained(this))); |
| |
| if (rv != ERR_IO_PENDING) |
| OnInitProxyResolverComplete(rv); |
| } |
| |
| void ProxyService::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 ProxyService::OnDNSChanged() { |
| OnIPAddressChanged(); |
| } |
| |
| } // namespace net |