Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / net / proxy / proxy_script_decider.cc
blob939f7af1422a413fa5fd8a140b308d04e0843af6
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.
5 #include "net/proxy/proxy_script_decider.h"
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"
22 namespace net {
24 namespace {
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.
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;
39 // This is the hard-coded location used by the DNS portion of web proxy
40 // auto-discovery.
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.
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.
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;
56 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;
74 dict->SetString("source", source);
75 return dict;
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()));
100 ProxyScriptDecider::~ProxyScriptDecider() {
101 if (next_state_ != STATE_NONE)
102 Cancel();
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());
112 net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER);
114 fetch_pac_bytes_ = fetch_pac_bytes;
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();
121 pac_mandatory_ = config.pac_mandatory();
122 have_custom_pac_url_ = config.has_pac_url();
124 pac_sources_ = BuildPacSourcesFallbackList(config);
125 DCHECK(!pac_sources_.empty());
127 next_state_ = STATE_WAIT;
129 int rv = DoLoop(OK);
130 if (rv == ERR_IO_PENDING)
131 callback_ = callback;
132 else
133 DidComplete();
135 return rv;
138 const ProxyConfig& ProxyScriptDecider::effective_config() const {
139 DCHECK_EQ(STATE_NONE, next_state_);
140 return effective_config_;
143 // TODO(eroman): Return a const-pointer.
144 ProxyResolverScriptData* ProxyScriptDecider::script_data() const {
145 DCHECK_EQ(STATE_NONE, next_state_);
146 return script_data_.get();
149 // Initialize the fallback rules.
150 // (1) WPAD (DHCP).
151 // (2) WPAD (DNS).
152 // (3) Custom PAC URL.
153 ProxyScriptDecider::PacSourceList ProxyScriptDecider::
154 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)));
161 if (config.has_pac_url())
162 pac_sources.push_back(PacSource(PacSource::CUSTOM, config.pac_url()));
163 return pac_sources;
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);
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;
215 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
216 return rv;
219 void ProxyScriptDecider::DoCallback(int result) {
220 DCHECK_NE(ERR_IO_PENDING, result);
221 DCHECK(!callback_.is_null());
222 callback_.Run(result);
225 int ProxyScriptDecider::DoWait() {
226 next_state_ = STATE_WAIT_COMPLETE;
228 // If no waiting is required, continue on to the next state.
229 if (wait_delay_.ToInternalValue() == 0)
230 return OK;
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;
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);
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;
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;
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));
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));
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_);
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;
294 int ProxyScriptDecider::DoFetchPacScript() {
295 DCHECK(fetch_pac_bytes_);
297 next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE;
299 const PacSource& pac_source = current_pac_source();
301 GURL effective_pac_url;
302 DetermineURL(pac_source, &effective_pac_url);
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));
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;
315 return dhcp_proxy_script_fetcher_->Fetch(
316 &pac_script_, base::Bind(&ProxyScriptDecider::OnIOCompletion,
317 base::Unretained(this)));
320 if (!proxy_script_fetcher_) {
321 net_log_.AddEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER);
322 return ERR_UNEXPECTED;
325 return proxy_script_fetcher_->Fetch(
326 effective_pac_url, &pac_script_,
327 base::Bind(&ProxyScriptDecider::OnIOCompletion, base::Unretained(this)));
330 int ProxyScriptDecider::DoFetchPacScriptComplete(int result) {
331 DCHECK(fetch_pac_bytes_);
333 net_log_.EndEventWithNetErrorCode(
334 NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT, result);
335 if (result != OK)
336 return TryToFallbackPacSource(result);
338 next_state_ = STATE_VERIFY_PAC_SCRIPT;
339 return result;
342 int ProxyScriptDecider::DoVerifyPacScript() {
343 next_state_ = STATE_VERIFY_PAC_SCRIPT_COMPLETE;
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;
349 return OK;
352 int ProxyScriptDecider::DoVerifyPacScriptComplete(int result) {
353 if (result != OK)
354 return TryToFallbackPacSource(result);
356 const PacSource& pac_source = current_pac_source();
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();
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;
377 switch (current_pac_source().type) {
378 case PacSource::WPAD_DHCP:
379 auto_detected_url = dhcp_proxy_script_fetcher_->GetPacURL();
380 break;
382 case PacSource::WPAD_DNS:
383 auto_detected_url = GURL(kWpadUrl);
384 break;
386 default:
387 NOTREACHED();
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();
400 return OK;
403 int ProxyScriptDecider::TryToFallbackPacSource(int error) {
404 DCHECK_LT(error, 0);
406 if (current_pac_source_index_ + 1 >= pac_sources_.size()) {
407 // Nothing left to fall back to.
408 return error;
411 // Advance to next URL in our list.
412 ++current_pac_source_index_;
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();
421 return OK;
424 ProxyScriptDecider::State ProxyScriptDecider::GetStartState() const {
425 return fetch_pac_bytes_ ? STATE_FETCH_PAC_SCRIPT : STATE_VERIFY_PAC_SCRIPT;
428 void ProxyScriptDecider::DetermineURL(const PacSource& pac_source,
429 GURL* effective_pac_url) {
430 DCHECK(effective_pac_url);
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;
444 const ProxyScriptDecider::PacSource&
445 ProxyScriptDecider::current_pac_source() const {
446 DCHECK_LT(current_pac_source_index_, pac_sources_.size());
447 return pac_sources_[current_pac_source_index_];
450 void ProxyScriptDecider::OnWaitTimerFired() {
451 OnIOCompletion(OK);
454 void ProxyScriptDecider::DidComplete() {
455 net_log_.EndEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER);
458 void ProxyScriptDecider::Cancel() {
459 DCHECK_NE(STATE_NONE, next_state_);
461 net_log_.AddEvent(NetLog::TYPE_CANCELLED);
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;
475 // This is safe to call in any state.
476 if (dhcp_proxy_script_fetcher_)
477 dhcp_proxy_script_fetcher_->Cancel();
479 DidComplete();
482 } // namespace net