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"
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"
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")) !=
40 // This is the hard-coded location used by the DNS portion of web proxy
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
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();
63 case PacSource::WPAD_DHCP
:
66 case PacSource::WPAD_DNS
:
67 source
= "WPAD DNS: ";
68 source
+= effective_pac_url
->possibly_invalid_spec();
70 case PacSource::CUSTOM
:
71 source
= "Custom PAC URL: ";
72 source
+= effective_pac_url
->possibly_invalid_spec();
75 dict
->SetString("source", source
);
79 ProxyScriptDecider::ProxyScriptDecider(
80 ProxyScriptFetcher
* proxy_script_fetcher
,
81 DhcpProxyScriptFetcher
* dhcp_proxy_script_fetcher
,
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
)
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
;
133 if (rv
== ERR_IO_PENDING
)
134 callback_
= callback
;
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.
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()));
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
) {
183 int ProxyScriptDecider::DoLoop(int result
) {
184 DCHECK_NE(next_state_
, STATE_NONE
);
187 State state
= next_state_
;
188 next_state_
= STATE_NONE
;
194 case STATE_WAIT_COMPLETE
:
195 rv
= DoWaitComplete(rv
);
197 case STATE_QUICK_CHECK
:
201 case STATE_QUICK_CHECK_COMPLETE
:
202 rv
= DoQuickCheckComplete(rv
);
204 case STATE_FETCH_PAC_SCRIPT
:
206 rv
= DoFetchPacScript();
208 case STATE_FETCH_PAC_SCRIPT_COMPLETE
:
209 rv
= DoFetchPacScriptComplete(rv
);
211 case STATE_VERIFY_PAC_SCRIPT
:
213 rv
= DoVerifyPacScript();
215 case STATE_VERIFY_PAC_SCRIPT_COMPLETE
:
216 rv
= DoVerifyPacScriptComplete(rv
);
219 NOTREACHED() << "bad state";
223 } while (rv
!= ERR_IO_PENDING
&& next_state_
!= STATE_NONE
);
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)
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
,
253 if (quick_check_enabled_
&& current_pac_source().type
== PacSource::WPAD_DNS
)
254 next_state_
= STATE_QUICK_CHECK
;
256 next_state_
= GetStartState();
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();
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(
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_
,
287 int ProxyScriptDecider::DoQuickCheckComplete(int result
) {
288 DCHECK(quick_check_enabled_
);
289 base::TimeDelta delta
= base::Time::Now() - quick_check_start_time_
;
291 UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckSuccess", delta
);
293 UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckFailure", delta
);
294 host_resolver_
->Cancel();
295 quick_check_timer_
.Stop();
297 return TryToFallbackPacSource(result
);
298 next_state_
= GetStartState();
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
);
344 return TryToFallbackPacSource(result
);
346 next_state_
= STATE_VERIFY_PAC_SCRIPT
;
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
;
360 int ProxyScriptDecider::DoVerifyPacScriptComplete(int result
) {
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_
);
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
) {
379 ProxyConfig::CreateFromCustomPacURL(current_pac_source().url
);
380 effective_config_
.set_pac_mandatory(pac_mandatory_
);
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();
390 case PacSource::WPAD_DNS
:
391 auto_detected_url
= GURL(kWpadUrl
);
399 ProxyConfig::CreateFromCustomPacURL(auto_detected_url
);
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();
411 int ProxyScriptDecider::TryToFallbackPacSource(int error
) {
414 if (current_pac_source_index_
+ 1 >= pac_sources_
.size()) {
415 // Nothing left to fall back to.
419 // Advance to next URL in our list.
420 ++current_pac_source_index_
;
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
;
427 next_state_
= GetStartState();
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
:
443 case PacSource::WPAD_DNS
:
444 *effective_pac_url
= GURL(kWpadUrl
);
446 case PacSource::CUSTOM
:
447 *effective_pac_url
= pac_source
.url
;
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() {
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
:
475 case STATE_FETCH_PAC_SCRIPT_COMPLETE
:
476 proxy_script_fetcher_
->Cancel();
482 // This is safe to call in any state.
483 if (dhcp_proxy_script_fetcher_
)
484 dhcp_proxy_script_fetcher_
->Cancel();