Home | History | Annotate | Download | only in proxy
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "net/proxy/proxy_script_decider.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/compiler_specific.h"
     10 #include "base/format_macros.h"
     11 #include "base/logging.h"
     12 #include "base/metrics/histogram.h"
     13 #include "base/strings/string_util.h"
     14 #include "base/strings/utf_string_conversions.h"
     15 #include "base/values.h"
     16 #include "net/base/net_errors.h"
     17 #include "net/proxy/dhcp_proxy_script_fetcher.h"
     18 #include "net/proxy/dhcp_proxy_script_fetcher_factory.h"
     19 #include "net/proxy/proxy_script_fetcher.h"
     20 #include "net/url_request/url_request_context.h"
     21 
     22 namespace net {
     23 
     24 namespace {
     25 
     26 bool LooksLikePacScript(const base::string16& script) {
     27   // Note: this is only an approximation! It may not always work correctly,
     28   // however it is very likely that legitimate scripts have this exact string,
     29   // since they must minimally define a function of this name. Conversely, a
     30   // file not containing the string is not likely to be a PAC script.
     31   //
     32   // An exact test would have to load the script in a javascript evaluator.
     33   return script.find(ASCIIToUTF16("FindProxyForURL")) != base::string16::npos;
     34 }
     35 
     36 }
     37 
     38 // This is the hard-coded location used by the DNS portion of web proxy
     39 // auto-discovery.
     40 //
     41 // Note that we not use DNS devolution to find the WPAD host, since that could
     42 // be dangerous should our top level domain registry  become out of date.
     43 //
     44 // Instead we directly resolve "wpad", and let the operating system apply the
     45 // DNS suffix search paths. This is the same approach taken by Firefox, and
     46 // compatibility hasn't been an issue.
     47 //
     48 // For more details, also check out this comment:
     49 // http://code.google.com/p/chromium/issues/detail?id=18575#c20
     50 namespace {
     51 const char kWpadUrl[] = "http://wpad/wpad.dat";
     52 const int kQuickCheckDelayMs = 1000;
     53 };
     54 
     55 base::Value* ProxyScriptDecider::PacSource::NetLogCallback(
     56     const GURL* effective_pac_url,
     57     NetLog::LogLevel /* log_level */) const {
     58   base::DictionaryValue* dict = new base::DictionaryValue();
     59   std::string source;
     60   switch (type) {
     61     case PacSource::WPAD_DHCP:
     62       source = "WPAD DHCP";
     63       break;
     64     case PacSource::WPAD_DNS:
     65       source = "WPAD DNS: ";
     66       source += effective_pac_url->possibly_invalid_spec();
     67       break;
     68     case PacSource::CUSTOM:
     69       source = "Custom PAC URL: ";
     70       source += effective_pac_url->possibly_invalid_spec();
     71       break;
     72   }
     73   dict->SetString("source", source);
     74   return dict;
     75 }
     76 
     77 ProxyScriptDecider::ProxyScriptDecider(
     78     ProxyScriptFetcher* proxy_script_fetcher,
     79     DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher,
     80     NetLog* net_log)
     81     : resolver_(NULL),
     82       proxy_script_fetcher_(proxy_script_fetcher),
     83       dhcp_proxy_script_fetcher_(dhcp_proxy_script_fetcher),
     84       current_pac_source_index_(0u),
     85       pac_mandatory_(false),
     86       next_state_(STATE_NONE),
     87       net_log_(BoundNetLog::Make(
     88           net_log, NetLog::SOURCE_PROXY_SCRIPT_DECIDER)),
     89       fetch_pac_bytes_(false) {
     90   if (proxy_script_fetcher &&
     91       proxy_script_fetcher->GetRequestContext() &&
     92       proxy_script_fetcher->GetRequestContext()->host_resolver()) {
     93       host_resolver_.reset(new SingleRequestHostResolver(
     94           proxy_script_fetcher->GetRequestContext()->host_resolver()));
     95   }
     96 }
     97 
     98 ProxyScriptDecider::~ProxyScriptDecider() {
     99   if (next_state_ != STATE_NONE)
    100     Cancel();
    101 }
    102 
    103 int ProxyScriptDecider::Start(
    104     const ProxyConfig& config, const base::TimeDelta wait_delay,
    105     bool fetch_pac_bytes, const CompletionCallback& callback) {
    106   DCHECK_EQ(STATE_NONE, next_state_);
    107   DCHECK(!callback.is_null());
    108   DCHECK(config.HasAutomaticSettings());
    109 
    110   net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER);
    111 
    112   fetch_pac_bytes_ = fetch_pac_bytes;
    113 
    114   // Save the |wait_delay| as a non-negative value.
    115   wait_delay_ = wait_delay;
    116   if (wait_delay_ < base::TimeDelta())
    117     wait_delay_ = base::TimeDelta();
    118 
    119   pac_mandatory_ = config.pac_mandatory();
    120   have_custom_pac_url_ = config.has_pac_url();
    121 
    122   pac_sources_ = BuildPacSourcesFallbackList(config);
    123   DCHECK(!pac_sources_.empty());
    124 
    125   next_state_ = STATE_WAIT;
    126 
    127   int rv = DoLoop(OK);
    128   if (rv == ERR_IO_PENDING)
    129     callback_ = callback;
    130   else
    131     DidComplete();
    132 
    133   return rv;
    134 }
    135 
    136 const ProxyConfig& ProxyScriptDecider::effective_config() const {
    137   DCHECK_EQ(STATE_NONE, next_state_);
    138   return effective_config_;
    139 }
    140 
    141 // TODO(eroman): Return a const-pointer.
    142 ProxyResolverScriptData* ProxyScriptDecider::script_data() const {
    143   DCHECK_EQ(STATE_NONE, next_state_);
    144   return script_data_.get();
    145 }
    146 
    147 // Initialize the fallback rules.
    148 // (1) WPAD (DHCP).
    149 // (2) WPAD (DNS).
    150 // (3) Custom PAC URL.
    151 ProxyScriptDecider::PacSourceList ProxyScriptDecider::
    152     BuildPacSourcesFallbackList(
    153     const ProxyConfig& config) const {
    154   PacSourceList pac_sources;
    155   if (config.auto_detect()) {
    156     pac_sources.push_back(PacSource(PacSource::WPAD_DHCP, GURL(kWpadUrl)));
    157     pac_sources.push_back(PacSource(PacSource::WPAD_DNS, GURL(kWpadUrl)));
    158   }
    159   if (config.has_pac_url())
    160     pac_sources.push_back(PacSource(PacSource::CUSTOM, config.pac_url()));
    161   return pac_sources;
    162 }
    163 
    164 void ProxyScriptDecider::OnIOCompletion(int result) {
    165   DCHECK_NE(STATE_NONE, next_state_);
    166   int rv = DoLoop(result);
    167   if (rv != ERR_IO_PENDING) {
    168     DidComplete();
    169     DoCallback(rv);
    170   }
    171 }
    172 
    173 int ProxyScriptDecider::DoLoop(int result) {
    174   DCHECK_NE(next_state_, STATE_NONE);
    175   int rv = result;
    176   do {
    177     State state = next_state_;
    178     next_state_ = STATE_NONE;
    179     switch (state) {
    180       case STATE_WAIT:
    181         DCHECK_EQ(OK, rv);
    182         rv = DoWait();
    183         break;
    184       case STATE_WAIT_COMPLETE:
    185         rv = DoWaitComplete(rv);
    186         break;
    187       case STATE_QUICK_CHECK:
    188         DCHECK_EQ(OK, rv);
    189         rv = DoQuickCheck();
    190         break;
    191       case STATE_QUICK_CHECK_COMPLETE:
    192         rv = DoQuickCheckComplete(rv);
    193         break;
    194       case STATE_FETCH_PAC_SCRIPT:
    195         DCHECK_EQ(OK, rv);
    196         rv = DoFetchPacScript();
    197         break;
    198       case STATE_FETCH_PAC_SCRIPT_COMPLETE:
    199         rv = DoFetchPacScriptComplete(rv);
    200         break;
    201       case STATE_VERIFY_PAC_SCRIPT:
    202         DCHECK_EQ(OK, rv);
    203         rv = DoVerifyPacScript();
    204         break;
    205       case STATE_VERIFY_PAC_SCRIPT_COMPLETE:
    206         rv = DoVerifyPacScriptComplete(rv);
    207         break;
    208       default:
    209         NOTREACHED() << "bad state";
    210         rv = ERR_UNEXPECTED;
    211         break;
    212     }
    213   } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
    214   return rv;
    215 }
    216 
    217 void ProxyScriptDecider::DoCallback(int result) {
    218   DCHECK_NE(ERR_IO_PENDING, result);
    219   DCHECK(!callback_.is_null());
    220   callback_.Run(result);
    221 }
    222 
    223 int ProxyScriptDecider::DoWait() {
    224   next_state_ = STATE_WAIT_COMPLETE;
    225 
    226   // If no waiting is required, continue on to the next state.
    227   if (wait_delay_.ToInternalValue() == 0)
    228     return OK;
    229 
    230   // Otherwise wait the specified amount of time.
    231   wait_timer_.Start(FROM_HERE, wait_delay_, this,
    232                     &ProxyScriptDecider::OnWaitTimerFired);
    233   net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT);
    234   return ERR_IO_PENDING;
    235 }
    236 
    237 int ProxyScriptDecider::DoWaitComplete(int result) {
    238   DCHECK_EQ(OK, result);
    239   if (wait_delay_.ToInternalValue() != 0) {
    240     net_log_.EndEventWithNetErrorCode(NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT,
    241                                       result);
    242   }
    243   if (current_pac_source().type == PacSource::WPAD_DNS)
    244     next_state_ = STATE_QUICK_CHECK;
    245   else
    246     next_state_ = GetStartState();
    247   return OK;
    248 }
    249 
    250 int ProxyScriptDecider::DoQuickCheck() {
    251   if (host_resolver_.get() == NULL) {
    252     // If we have no resolver, skip QuickCheck altogether.
    253     next_state_ = GetStartState();
    254     return OK;
    255   }
    256 
    257   quick_check_start_time_ = base::Time::Now();
    258   std::string host = current_pac_source().url.host();
    259   HostResolver::RequestInfo reqinfo(HostPortPair(host, 80));
    260   reqinfo.set_host_resolver_flags(HOST_RESOLVER_SYSTEM_ONLY);
    261   CompletionCallback callback = base::Bind(
    262       &ProxyScriptDecider::OnIOCompletion,
    263       base::Unretained(this));
    264 
    265   next_state_ = STATE_QUICK_CHECK_COMPLETE;
    266   quick_check_timer_.Start(FROM_HERE,
    267                            base::TimeDelta::FromMilliseconds(
    268                               kQuickCheckDelayMs),
    269                            base::Bind(callback, ERR_NAME_NOT_RESOLVED));
    270 
    271   // We use HIGHEST here because proxy decision blocks doing any other requests.
    272   return host_resolver_->Resolve(reqinfo, HIGHEST, &wpad_addresses_,
    273                                  callback, net_log_);
    274 }
    275 
    276 int ProxyScriptDecider::DoQuickCheckComplete(int result) {
    277   base::TimeDelta delta = base::Time::Now() - quick_check_start_time_;
    278   if (result == OK)
    279     UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckSuccess", delta);
    280   else
    281     UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckFailure", delta);
    282   host_resolver_->Cancel();
    283   quick_check_timer_.Stop();
    284   if (result != OK)
    285     return TryToFallbackPacSource(result);
    286   next_state_ = GetStartState();
    287   return result;
    288 }
    289 
    290 int ProxyScriptDecider::DoFetchPacScript() {
    291   DCHECK(fetch_pac_bytes_);
    292 
    293   next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE;
    294 
    295   const PacSource& pac_source = current_pac_source();
    296 
    297   GURL effective_pac_url;
    298   DetermineURL(pac_source, &effective_pac_url);
    299 
    300   net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT,
    301                       base::Bind(&PacSource::NetLogCallback,
    302                                  base::Unretained(&pac_source),
    303                                  &effective_pac_url));
    304 
    305   if (pac_source.type == PacSource::WPAD_DHCP) {
    306     if (!dhcp_proxy_script_fetcher_) {
    307       net_log_.AddEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER);
    308       return ERR_UNEXPECTED;
    309     }
    310 
    311     return dhcp_proxy_script_fetcher_->Fetch(
    312         &pac_script_, base::Bind(&ProxyScriptDecider::OnIOCompletion,
    313                                  base::Unretained(this)));
    314   }
    315 
    316   if (!proxy_script_fetcher_) {
    317     net_log_.AddEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER);
    318     return ERR_UNEXPECTED;
    319   }
    320 
    321   return proxy_script_fetcher_->Fetch(
    322       effective_pac_url, &pac_script_,
    323       base::Bind(&ProxyScriptDecider::OnIOCompletion, base::Unretained(this)));
    324 }
    325 
    326 int ProxyScriptDecider::DoFetchPacScriptComplete(int result) {
    327   DCHECK(fetch_pac_bytes_);
    328 
    329   net_log_.EndEventWithNetErrorCode(
    330       NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT, result);
    331   if (result != OK)
    332     return TryToFallbackPacSource(result);
    333 
    334   next_state_ = STATE_VERIFY_PAC_SCRIPT;
    335   return result;
    336 }
    337 
    338 int ProxyScriptDecider::DoVerifyPacScript() {
    339   next_state_ = STATE_VERIFY_PAC_SCRIPT_COMPLETE;
    340 
    341   // This is just a heuristic. Ideally we would try to parse the script.
    342   if (fetch_pac_bytes_ && !LooksLikePacScript(pac_script_))
    343     return ERR_PAC_SCRIPT_FAILED;
    344 
    345   return OK;
    346 }
    347 
    348 int ProxyScriptDecider::DoVerifyPacScriptComplete(int result) {
    349   if (result != OK)
    350     return TryToFallbackPacSource(result);
    351 
    352   const PacSource& pac_source = current_pac_source();
    353 
    354   // Extract the current script data.
    355   if (fetch_pac_bytes_) {
    356     script_data_ = ProxyResolverScriptData::FromUTF16(pac_script_);
    357   } else {
    358     script_data_ = pac_source.type == PacSource::CUSTOM ?
    359         ProxyResolverScriptData::FromURL(pac_source.url) :
    360         ProxyResolverScriptData::ForAutoDetect();
    361   }
    362 
    363   // Let the caller know which automatic setting we ended up initializing the
    364   // resolver for (there may have been multiple fallbacks to choose from.)
    365   if (current_pac_source().type == PacSource::CUSTOM) {
    366     effective_config_ =
    367         ProxyConfig::CreateFromCustomPacURL(current_pac_source().url);
    368     effective_config_.set_pac_mandatory(pac_mandatory_);
    369   } else {
    370     if (fetch_pac_bytes_) {
    371       GURL auto_detected_url;
    372 
    373       switch (current_pac_source().type) {
    374         case PacSource::WPAD_DHCP:
    375           auto_detected_url = dhcp_proxy_script_fetcher_->GetPacURL();
    376           break;
    377 
    378         case PacSource::WPAD_DNS:
    379           auto_detected_url = GURL(kWpadUrl);
    380           break;
    381 
    382         default:
    383           NOTREACHED();
    384       }
    385 
    386       effective_config_ =
    387           ProxyConfig::CreateFromCustomPacURL(auto_detected_url);
    388     } else {
    389       // The resolver does its own resolution so we cannot know the
    390       // URL. Just do the best we can and state that the configuration
    391       // is to auto-detect proxy settings.
    392       effective_config_ = ProxyConfig::CreateAutoDetect();
    393     }
    394   }
    395 
    396   return OK;
    397 }
    398 
    399 int ProxyScriptDecider::TryToFallbackPacSource(int error) {
    400   DCHECK_LT(error, 0);
    401 
    402   if (current_pac_source_index_ + 1 >= pac_sources_.size()) {
    403     // Nothing left to fall back to.
    404     return error;
    405   }
    406 
    407   // Advance to next URL in our list.
    408   ++current_pac_source_index_;
    409 
    410   net_log_.AddEvent(
    411       NetLog::TYPE_PROXY_SCRIPT_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE);
    412   if (current_pac_source().type == PacSource::WPAD_DNS)
    413     next_state_ = STATE_QUICK_CHECK;
    414   else
    415     next_state_ = GetStartState();
    416 
    417   return OK;
    418 }
    419 
    420 ProxyScriptDecider::State ProxyScriptDecider::GetStartState() const {
    421   return fetch_pac_bytes_ ?  STATE_FETCH_PAC_SCRIPT : STATE_VERIFY_PAC_SCRIPT;
    422 }
    423 
    424 void ProxyScriptDecider::DetermineURL(const PacSource& pac_source,
    425                                       GURL* effective_pac_url) {
    426   DCHECK(effective_pac_url);
    427 
    428   switch (pac_source.type) {
    429     case PacSource::WPAD_DHCP:
    430       break;
    431     case PacSource::WPAD_DNS:
    432       *effective_pac_url = GURL(kWpadUrl);
    433       break;
    434     case PacSource::CUSTOM:
    435       *effective_pac_url = pac_source.url;
    436       break;
    437   }
    438 }
    439 
    440 const ProxyScriptDecider::PacSource&
    441     ProxyScriptDecider::current_pac_source() const {
    442   DCHECK_LT(current_pac_source_index_, pac_sources_.size());
    443   return pac_sources_[current_pac_source_index_];
    444 }
    445 
    446 void ProxyScriptDecider::OnWaitTimerFired() {
    447   OnIOCompletion(OK);
    448 }
    449 
    450 void ProxyScriptDecider::DidComplete() {
    451   net_log_.EndEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER);
    452 }
    453 
    454 void ProxyScriptDecider::Cancel() {
    455   DCHECK_NE(STATE_NONE, next_state_);
    456 
    457   net_log_.AddEvent(NetLog::TYPE_CANCELLED);
    458 
    459   switch (next_state_) {
    460     case STATE_WAIT_COMPLETE:
    461       wait_timer_.Stop();
    462       break;
    463     case STATE_FETCH_PAC_SCRIPT_COMPLETE:
    464       proxy_script_fetcher_->Cancel();
    465       break;
    466     default:
    467       NOTREACHED();
    468       break;
    469   }
    470 
    471   // This is safe to call in any state.
    472   if (dhcp_proxy_script_fetcher_)
    473     dhcp_proxy_script_fetcher_->Cancel();
    474 
    475   DidComplete();
    476 }
    477 
    478 }  // namespace net
    479