Requiem for client_screen.js
[chromium-blink-merge.git] / net / proxy / proxy_script_decider.cc
blob3d830fc7d23f73d5467e3a733b87e8ec0abd33cb
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/profiler/scoped_tracker.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/values.h"
17 #include "net/base/net_errors.h"
18 #include "net/proxy/dhcp_proxy_script_fetcher.h"
19 #include "net/proxy/dhcp_proxy_script_fetcher_factory.h"
20 #include "net/proxy/proxy_script_fetcher.h"
21 #include "net/url_request/url_request_context.h"
23 namespace net {
25 namespace {
27 bool LooksLikePacScript(const base::string16& script) {
28 // Note: this is only an approximation! It may not always work correctly,
29 // however it is very likely that legitimate scripts have this exact string,
30 // since they must minimally define a function of this name. Conversely, a
31 // file not containing the string is not likely to be a PAC script.
33 // An exact test would have to load the script in a javascript evaluator.
34 return script.find(base::ASCIIToUTF16("FindProxyForURL")) !=
35 base::string16::npos;
40 // This is the hard-coded location used by the DNS portion of web proxy
41 // auto-discovery.
43 // Note that we not use DNS devolution to find the WPAD host, since that could
44 // be dangerous should our top level domain registry become out of date.
46 // Instead we directly resolve "wpad", and let the operating system apply the
47 // DNS suffix search paths. This is the same approach taken by Firefox, and
48 // compatibility hasn't been an issue.
50 // For more details, also check out this comment:
51 // http://code.google.com/p/chromium/issues/detail?id=18575#c20
52 namespace {
53 const char kWpadUrl[] = "http://wpad/wpad.dat";
54 const int kQuickCheckDelayMs = 1000;
57 base::Value* ProxyScriptDecider::PacSource::NetLogCallback(
58 const GURL* effective_pac_url,
59 NetLog::LogLevel /* log_level */) const {
60 base::DictionaryValue* dict = new base::DictionaryValue();
61 std::string source;
62 switch (type) {
63 case PacSource::WPAD_DHCP:
64 source = "WPAD DHCP";
65 break;
66 case PacSource::WPAD_DNS:
67 source = "WPAD DNS: ";
68 source += effective_pac_url->possibly_invalid_spec();
69 break;
70 case PacSource::CUSTOM:
71 source = "Custom PAC URL: ";
72 source += effective_pac_url->possibly_invalid_spec();
73 break;
75 dict->SetString("source", source);
76 return dict;
79 ProxyScriptDecider::ProxyScriptDecider(
80 ProxyScriptFetcher* proxy_script_fetcher,
81 DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher,
82 NetLog* net_log)
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(net_log, NetLog::SOURCE_PROXY_SCRIPT_DECIDER)),
89 fetch_pac_bytes_(false),
90 quick_check_enabled_(true) {
91 // TODO(pkasting): Remove ScopedTracker below once crbug.com/455942 is fixed.
92 tracked_objects::ScopedTracker tracking_profile(
93 FROM_HERE_WITH_EXPLICIT_FUNCTION(
94 "455942 ProxyScriptDecider::ProxyScriptDecider"));
95 if (proxy_script_fetcher &&
96 proxy_script_fetcher->GetRequestContext() &&
97 proxy_script_fetcher->GetRequestContext()->host_resolver()) {
98 host_resolver_.reset(new SingleRequestHostResolver(
99 proxy_script_fetcher->GetRequestContext()->host_resolver()));
103 ProxyScriptDecider::~ProxyScriptDecider() {
104 if (next_state_ != STATE_NONE)
105 Cancel();
108 int ProxyScriptDecider::Start(
109 const ProxyConfig& config, const base::TimeDelta wait_delay,
110 bool fetch_pac_bytes, const CompletionCallback& callback) {
111 DCHECK_EQ(STATE_NONE, next_state_);
112 DCHECK(!callback.is_null());
113 DCHECK(config.HasAutomaticSettings());
115 net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER);
117 fetch_pac_bytes_ = fetch_pac_bytes;
119 // Save the |wait_delay| as a non-negative value.
120 wait_delay_ = wait_delay;
121 if (wait_delay_ < base::TimeDelta())
122 wait_delay_ = base::TimeDelta();
124 pac_mandatory_ = config.pac_mandatory();
125 have_custom_pac_url_ = config.has_pac_url();
127 pac_sources_ = BuildPacSourcesFallbackList(config);
128 DCHECK(!pac_sources_.empty());
130 next_state_ = STATE_WAIT;
132 int rv = DoLoop(OK);
133 if (rv == ERR_IO_PENDING)
134 callback_ = callback;
135 else
136 DidComplete();
138 return rv;
141 const ProxyConfig& ProxyScriptDecider::effective_config() const {
142 DCHECK_EQ(STATE_NONE, next_state_);
143 return effective_config_;
146 // TODO(eroman): Return a const-pointer.
147 ProxyResolverScriptData* ProxyScriptDecider::script_data() const {
148 DCHECK_EQ(STATE_NONE, next_state_);
149 return script_data_.get();
152 // Initialize the fallback rules.
153 // (1) WPAD (DHCP).
154 // (2) WPAD (DNS).
155 // (3) Custom PAC URL.
156 ProxyScriptDecider::PacSourceList ProxyScriptDecider::
157 BuildPacSourcesFallbackList(
158 const ProxyConfig& config) const {
159 PacSourceList pac_sources;
160 if (config.auto_detect()) {
161 pac_sources.push_back(PacSource(PacSource::WPAD_DHCP, GURL(kWpadUrl)));
162 pac_sources.push_back(PacSource(PacSource::WPAD_DNS, GURL(kWpadUrl)));
164 if (config.has_pac_url())
165 pac_sources.push_back(PacSource(PacSource::CUSTOM, config.pac_url()));
166 return pac_sources;
169 void ProxyScriptDecider::OnIOCompletion(int result) {
170 // TODO(vadimt): Remove ScopedTracker below once crbug.com/436634 is fixed.
171 tracked_objects::ScopedTracker tracking_profile(
172 FROM_HERE_WITH_EXPLICIT_FUNCTION(
173 "436634 ProxyScriptDecider::OnIOCompletion"));
175 DCHECK_NE(STATE_NONE, next_state_);
176 int rv = DoLoop(result);
177 if (rv != ERR_IO_PENDING) {
178 DidComplete();
179 DoCallback(rv);
183 int ProxyScriptDecider::DoLoop(int result) {
184 DCHECK_NE(next_state_, STATE_NONE);
185 int rv = result;
186 do {
187 State state = next_state_;
188 next_state_ = STATE_NONE;
189 switch (state) {
190 case STATE_WAIT:
191 DCHECK_EQ(OK, rv);
192 rv = DoWait();
193 break;
194 case STATE_WAIT_COMPLETE:
195 rv = DoWaitComplete(rv);
196 break;
197 case STATE_QUICK_CHECK:
198 DCHECK_EQ(OK, rv);
199 rv = DoQuickCheck();
200 break;
201 case STATE_QUICK_CHECK_COMPLETE:
202 rv = DoQuickCheckComplete(rv);
203 break;
204 case STATE_FETCH_PAC_SCRIPT:
205 DCHECK_EQ(OK, rv);
206 rv = DoFetchPacScript();
207 break;
208 case STATE_FETCH_PAC_SCRIPT_COMPLETE:
209 rv = DoFetchPacScriptComplete(rv);
210 break;
211 case STATE_VERIFY_PAC_SCRIPT:
212 DCHECK_EQ(OK, rv);
213 rv = DoVerifyPacScript();
214 break;
215 case STATE_VERIFY_PAC_SCRIPT_COMPLETE:
216 rv = DoVerifyPacScriptComplete(rv);
217 break;
218 default:
219 NOTREACHED() << "bad state";
220 rv = ERR_UNEXPECTED;
221 break;
223 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
224 return rv;
227 void ProxyScriptDecider::DoCallback(int result) {
228 DCHECK_NE(ERR_IO_PENDING, result);
229 DCHECK(!callback_.is_null());
230 callback_.Run(result);
233 int ProxyScriptDecider::DoWait() {
234 next_state_ = STATE_WAIT_COMPLETE;
236 // If no waiting is required, continue on to the next state.
237 if (wait_delay_.ToInternalValue() == 0)
238 return OK;
240 // Otherwise wait the specified amount of time.
241 wait_timer_.Start(FROM_HERE, wait_delay_, this,
242 &ProxyScriptDecider::OnWaitTimerFired);
243 net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT);
244 return ERR_IO_PENDING;
247 int ProxyScriptDecider::DoWaitComplete(int result) {
248 DCHECK_EQ(OK, result);
249 if (wait_delay_.ToInternalValue() != 0) {
250 net_log_.EndEventWithNetErrorCode(NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT,
251 result);
253 if (quick_check_enabled_ && current_pac_source().type == PacSource::WPAD_DNS)
254 next_state_ = STATE_QUICK_CHECK;
255 else
256 next_state_ = GetStartState();
257 return OK;
260 int ProxyScriptDecider::DoQuickCheck() {
261 DCHECK(quick_check_enabled_);
262 if (host_resolver_.get() == NULL) {
263 // If we have no resolver, skip QuickCheck altogether.
264 next_state_ = GetStartState();
265 return OK;
268 quick_check_start_time_ = base::Time::Now();
269 std::string host = current_pac_source().url.host();
270 HostResolver::RequestInfo reqinfo(HostPortPair(host, 80));
271 reqinfo.set_host_resolver_flags(HOST_RESOLVER_SYSTEM_ONLY);
272 CompletionCallback callback = base::Bind(
273 &ProxyScriptDecider::OnIOCompletion,
274 base::Unretained(this));
276 next_state_ = STATE_QUICK_CHECK_COMPLETE;
277 quick_check_timer_.Start(FROM_HERE,
278 base::TimeDelta::FromMilliseconds(
279 kQuickCheckDelayMs),
280 base::Bind(callback, ERR_NAME_NOT_RESOLVED));
282 // We use HIGHEST here because proxy decision blocks doing any other requests.
283 return host_resolver_->Resolve(reqinfo, HIGHEST, &wpad_addresses_,
284 callback, net_log_);
287 int ProxyScriptDecider::DoQuickCheckComplete(int result) {
288 DCHECK(quick_check_enabled_);
289 base::TimeDelta delta = base::Time::Now() - quick_check_start_time_;
290 if (result == OK)
291 UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckSuccess", delta);
292 else
293 UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckFailure", delta);
294 host_resolver_->Cancel();
295 quick_check_timer_.Stop();
296 if (result != OK)
297 return TryToFallbackPacSource(result);
298 next_state_ = GetStartState();
299 return result;
302 int ProxyScriptDecider::DoFetchPacScript() {
303 DCHECK(fetch_pac_bytes_);
305 next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE;
307 const PacSource& pac_source = current_pac_source();
309 GURL effective_pac_url;
310 DetermineURL(pac_source, &effective_pac_url);
312 net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT,
313 base::Bind(&PacSource::NetLogCallback,
314 base::Unretained(&pac_source),
315 &effective_pac_url));
317 if (pac_source.type == PacSource::WPAD_DHCP) {
318 if (!dhcp_proxy_script_fetcher_) {
319 net_log_.AddEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER);
320 return ERR_UNEXPECTED;
323 return dhcp_proxy_script_fetcher_->Fetch(
324 &pac_script_, base::Bind(&ProxyScriptDecider::OnIOCompletion,
325 base::Unretained(this)));
328 if (!proxy_script_fetcher_) {
329 net_log_.AddEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER);
330 return ERR_UNEXPECTED;
333 return proxy_script_fetcher_->Fetch(
334 effective_pac_url, &pac_script_,
335 base::Bind(&ProxyScriptDecider::OnIOCompletion, base::Unretained(this)));
338 int ProxyScriptDecider::DoFetchPacScriptComplete(int result) {
339 DCHECK(fetch_pac_bytes_);
341 net_log_.EndEventWithNetErrorCode(
342 NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT, result);
343 if (result != OK)
344 return TryToFallbackPacSource(result);
346 next_state_ = STATE_VERIFY_PAC_SCRIPT;
347 return result;
350 int ProxyScriptDecider::DoVerifyPacScript() {
351 next_state_ = STATE_VERIFY_PAC_SCRIPT_COMPLETE;
353 // This is just a heuristic. Ideally we would try to parse the script.
354 if (fetch_pac_bytes_ && !LooksLikePacScript(pac_script_))
355 return ERR_PAC_SCRIPT_FAILED;
357 return OK;
360 int ProxyScriptDecider::DoVerifyPacScriptComplete(int result) {
361 if (result != OK)
362 return TryToFallbackPacSource(result);
364 const PacSource& pac_source = current_pac_source();
366 // Extract the current script data.
367 if (fetch_pac_bytes_) {
368 script_data_ = ProxyResolverScriptData::FromUTF16(pac_script_);
369 } else {
370 script_data_ = pac_source.type == PacSource::CUSTOM ?
371 ProxyResolverScriptData::FromURL(pac_source.url) :
372 ProxyResolverScriptData::ForAutoDetect();
375 // Let the caller know which automatic setting we ended up initializing the
376 // resolver for (there may have been multiple fallbacks to choose from.)
377 if (current_pac_source().type == PacSource::CUSTOM) {
378 effective_config_ =
379 ProxyConfig::CreateFromCustomPacURL(current_pac_source().url);
380 effective_config_.set_pac_mandatory(pac_mandatory_);
381 } else {
382 if (fetch_pac_bytes_) {
383 GURL auto_detected_url;
385 switch (current_pac_source().type) {
386 case PacSource::WPAD_DHCP:
387 auto_detected_url = dhcp_proxy_script_fetcher_->GetPacURL();
388 break;
390 case PacSource::WPAD_DNS:
391 auto_detected_url = GURL(kWpadUrl);
392 break;
394 default:
395 NOTREACHED();
398 effective_config_ =
399 ProxyConfig::CreateFromCustomPacURL(auto_detected_url);
400 } else {
401 // The resolver does its own resolution so we cannot know the
402 // URL. Just do the best we can and state that the configuration
403 // is to auto-detect proxy settings.
404 effective_config_ = ProxyConfig::CreateAutoDetect();
408 return OK;
411 int ProxyScriptDecider::TryToFallbackPacSource(int error) {
412 DCHECK_LT(error, 0);
414 if (current_pac_source_index_ + 1 >= pac_sources_.size()) {
415 // Nothing left to fall back to.
416 return error;
419 // Advance to next URL in our list.
420 ++current_pac_source_index_;
422 net_log_.AddEvent(
423 NetLog::TYPE_PROXY_SCRIPT_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE);
424 if (quick_check_enabled_ && current_pac_source().type == PacSource::WPAD_DNS)
425 next_state_ = STATE_QUICK_CHECK;
426 else
427 next_state_ = GetStartState();
429 return OK;
432 ProxyScriptDecider::State ProxyScriptDecider::GetStartState() const {
433 return fetch_pac_bytes_ ? STATE_FETCH_PAC_SCRIPT : STATE_VERIFY_PAC_SCRIPT;
436 void ProxyScriptDecider::DetermineURL(const PacSource& pac_source,
437 GURL* effective_pac_url) {
438 DCHECK(effective_pac_url);
440 switch (pac_source.type) {
441 case PacSource::WPAD_DHCP:
442 break;
443 case PacSource::WPAD_DNS:
444 *effective_pac_url = GURL(kWpadUrl);
445 break;
446 case PacSource::CUSTOM:
447 *effective_pac_url = pac_source.url;
448 break;
452 const ProxyScriptDecider::PacSource&
453 ProxyScriptDecider::current_pac_source() const {
454 DCHECK_LT(current_pac_source_index_, pac_sources_.size());
455 return pac_sources_[current_pac_source_index_];
458 void ProxyScriptDecider::OnWaitTimerFired() {
459 OnIOCompletion(OK);
462 void ProxyScriptDecider::DidComplete() {
463 net_log_.EndEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER);
466 void ProxyScriptDecider::Cancel() {
467 DCHECK_NE(STATE_NONE, next_state_);
469 net_log_.AddEvent(NetLog::TYPE_CANCELLED);
471 switch (next_state_) {
472 case STATE_WAIT_COMPLETE:
473 wait_timer_.Stop();
474 break;
475 case STATE_FETCH_PAC_SCRIPT_COMPLETE:
476 proxy_script_fetcher_->Cancel();
477 break;
478 default:
479 break;
482 // This is safe to call in any state.
483 if (dhcp_proxy_script_fetcher_)
484 dhcp_proxy_script_fetcher_->Cancel();
486 DidComplete();
489 } // namespace net