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