• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
LooksLikePacScript(const base::string16 & script)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 
NetLogCallback(const GURL * effective_pac_url,NetLog::LogLevel) const55 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 
ProxyScriptDecider(ProxyScriptFetcher * proxy_script_fetcher,DhcpProxyScriptFetcher * dhcp_proxy_script_fetcher,NetLog * net_log)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 
~ProxyScriptDecider()98 ProxyScriptDecider::~ProxyScriptDecider() {
99   if (next_state_ != STATE_NONE)
100     Cancel();
101 }
102 
Start(const ProxyConfig & config,const base::TimeDelta wait_delay,bool fetch_pac_bytes,const CompletionCallback & callback)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 
effective_config() const136 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.
script_data() const142 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::
BuildPacSourcesFallbackList(const ProxyConfig & config) const152     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 
OnIOCompletion(int result)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 
DoLoop(int result)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 
DoCallback(int result)217 void ProxyScriptDecider::DoCallback(int result) {
218   DCHECK_NE(ERR_IO_PENDING, result);
219   DCHECK(!callback_.is_null());
220   callback_.Run(result);
221 }
222 
DoWait()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 
DoWaitComplete(int result)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 
DoQuickCheck()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 
DoQuickCheckComplete(int result)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 
DoFetchPacScript()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 
DoFetchPacScriptComplete(int result)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 
DoVerifyPacScript()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 
DoVerifyPacScriptComplete(int result)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 
TryToFallbackPacSource(int error)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 
GetStartState() const420 ProxyScriptDecider::State ProxyScriptDecider::GetStartState() const {
421   return fetch_pac_bytes_ ?  STATE_FETCH_PAC_SCRIPT : STATE_VERIFY_PAC_SCRIPT;
422 }
423 
DetermineURL(const PacSource & pac_source,GURL * effective_pac_url)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&
current_pac_source() const441     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 
OnWaitTimerFired()446 void ProxyScriptDecider::OnWaitTimerFired() {
447   OnIOCompletion(OK);
448 }
449 
DidComplete()450 void ProxyScriptDecider::DidComplete() {
451   net_log_.EndEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER);
452 }
453 
Cancel()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