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/string_util.h"
13 #include "base/utf_string_conversions.h"
14 #include "base/values.h"
15 #include "net/base/net_errors.h"
16 #include "net/proxy/dhcp_proxy_script_fetcher.h"
17 #include "net/proxy/dhcp_proxy_script_fetcher_factory.h"
18 #include "net/proxy/proxy_script_fetcher.h"
24 bool LooksLikePacScript(const string16
& script
) {
25 // Note: this is only an approximation! It may not always work correctly,
26 // however it is very likely that legitimate scripts have this exact string,
27 // since they must minimally define a function of this name. Conversely, a
28 // file not containing the string is not likely to be a PAC script.
30 // An exact test would have to load the script in a javascript evaluator.
31 return script
.find(ASCIIToUTF16("FindProxyForURL")) != string16::npos
;
36 // This is the hard-coded location used by the DNS portion of web proxy
39 // Note that we not use DNS devolution to find the WPAD host, since that could
40 // be dangerous should our top level domain registry become out of date.
42 // Instead we directly resolve "wpad", and let the operating system apply the
43 // DNS suffix search paths. This is the same approach taken by Firefox, and
44 // compatibility hasn't been an issue.
46 // For more details, also check out this comment:
47 // http://code.google.com/p/chromium/issues/detail?id=18575#c20
48 static const char kWpadUrl
[] = "http://wpad/wpad.dat";
50 Value
* ProxyScriptDecider::PacSource::NetLogCallback(
51 const GURL
* effective_pac_url
,
52 NetLog::LogLevel
/* log_level */) const {
53 DictionaryValue
* dict
= new DictionaryValue();
56 case PacSource::WPAD_DHCP
:
59 case PacSource::WPAD_DNS
:
60 source
= "WPAD DNS: ";
61 source
+= effective_pac_url
->possibly_invalid_spec();
63 case PacSource::CUSTOM
:
64 source
= "Custom PAC URL: ";
65 source
+= effective_pac_url
->possibly_invalid_spec();
68 dict
->SetString("source", source
);
72 ProxyScriptDecider::ProxyScriptDecider(
73 ProxyScriptFetcher
* proxy_script_fetcher
,
74 DhcpProxyScriptFetcher
* dhcp_proxy_script_fetcher
,
77 proxy_script_fetcher_(proxy_script_fetcher
),
78 dhcp_proxy_script_fetcher_(dhcp_proxy_script_fetcher
),
79 current_pac_source_index_(0u),
80 pac_mandatory_(false),
81 next_state_(STATE_NONE
),
82 net_log_(BoundNetLog::Make(
83 net_log
, NetLog::SOURCE_PROXY_SCRIPT_DECIDER
)),
84 fetch_pac_bytes_(false) {
87 ProxyScriptDecider::~ProxyScriptDecider() {
88 if (next_state_
!= STATE_NONE
)
92 int ProxyScriptDecider::Start(
93 const ProxyConfig
& config
, const base::TimeDelta wait_delay
,
94 bool fetch_pac_bytes
, const CompletionCallback
& callback
) {
95 DCHECK_EQ(STATE_NONE
, next_state_
);
96 DCHECK(!callback
.is_null());
97 DCHECK(config
.HasAutomaticSettings());
99 net_log_
.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER
);
101 fetch_pac_bytes_
= fetch_pac_bytes
;
103 // Save the |wait_delay| as a non-negative value.
104 wait_delay_
= wait_delay
;
105 if (wait_delay_
< base::TimeDelta())
106 wait_delay_
= base::TimeDelta();
108 pac_mandatory_
= config
.pac_mandatory();
110 pac_sources_
= BuildPacSourcesFallbackList(config
);
111 DCHECK(!pac_sources_
.empty());
113 next_state_
= STATE_WAIT
;
116 if (rv
== ERR_IO_PENDING
)
117 callback_
= callback
;
124 const ProxyConfig
& ProxyScriptDecider::effective_config() const {
125 DCHECK_EQ(STATE_NONE
, next_state_
);
126 return effective_config_
;
129 // TODO(eroman): Return a const-pointer.
130 ProxyResolverScriptData
* ProxyScriptDecider::script_data() const {
131 DCHECK_EQ(STATE_NONE
, next_state_
);
132 return script_data_
.get();
135 // Initialize the fallback rules.
138 // (3) Custom PAC URL.
139 ProxyScriptDecider::PacSourceList
ProxyScriptDecider::
140 BuildPacSourcesFallbackList(
141 const ProxyConfig
& config
) const {
142 PacSourceList pac_sources
;
143 if (config
.auto_detect()) {
144 pac_sources
.push_back(PacSource(PacSource::WPAD_DHCP
, GURL()));
145 pac_sources
.push_back(PacSource(PacSource::WPAD_DNS
, GURL()));
147 if (config
.has_pac_url())
148 pac_sources
.push_back(PacSource(PacSource::CUSTOM
, config
.pac_url()));
152 void ProxyScriptDecider::OnIOCompletion(int result
) {
153 DCHECK_NE(STATE_NONE
, next_state_
);
154 int rv
= DoLoop(result
);
155 if (rv
!= ERR_IO_PENDING
) {
161 int ProxyScriptDecider::DoLoop(int result
) {
162 DCHECK_NE(next_state_
, STATE_NONE
);
165 State state
= next_state_
;
166 next_state_
= STATE_NONE
;
172 case STATE_WAIT_COMPLETE
:
173 rv
= DoWaitComplete(rv
);
175 case STATE_FETCH_PAC_SCRIPT
:
177 rv
= DoFetchPacScript();
179 case STATE_FETCH_PAC_SCRIPT_COMPLETE
:
180 rv
= DoFetchPacScriptComplete(rv
);
182 case STATE_VERIFY_PAC_SCRIPT
:
184 rv
= DoVerifyPacScript();
186 case STATE_VERIFY_PAC_SCRIPT_COMPLETE
:
187 rv
= DoVerifyPacScriptComplete(rv
);
190 NOTREACHED() << "bad state";
194 } while (rv
!= ERR_IO_PENDING
&& next_state_
!= STATE_NONE
);
198 void ProxyScriptDecider::DoCallback(int result
) {
199 DCHECK_NE(ERR_IO_PENDING
, result
);
200 DCHECK(!callback_
.is_null());
201 callback_
.Run(result
);
204 int ProxyScriptDecider::DoWait() {
205 next_state_
= STATE_WAIT_COMPLETE
;
207 // If no waiting is required, continue on to the next state.
208 if (wait_delay_
.ToInternalValue() == 0)
211 // Otherwise wait the specified amount of time.
212 wait_timer_
.Start(FROM_HERE
, wait_delay_
, this,
213 &ProxyScriptDecider::OnWaitTimerFired
);
214 net_log_
.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT
);
215 return ERR_IO_PENDING
;
218 int ProxyScriptDecider::DoWaitComplete(int result
) {
219 DCHECK_EQ(OK
, result
);
220 if (wait_delay_
.ToInternalValue() != 0) {
221 net_log_
.EndEventWithNetErrorCode(NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT
,
224 next_state_
= GetStartState();
228 int ProxyScriptDecider::DoFetchPacScript() {
229 DCHECK(fetch_pac_bytes_
);
231 next_state_
= STATE_FETCH_PAC_SCRIPT_COMPLETE
;
233 const PacSource
& pac_source
= current_pac_source();
235 GURL effective_pac_url
;
236 DetermineURL(pac_source
, &effective_pac_url
);
238 net_log_
.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT
,
239 base::Bind(&PacSource::NetLogCallback
,
240 base::Unretained(&pac_source
),
241 &effective_pac_url
));
243 if (pac_source
.type
== PacSource::WPAD_DHCP
) {
244 if (!dhcp_proxy_script_fetcher_
) {
245 net_log_
.AddEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER
);
246 return ERR_UNEXPECTED
;
249 return dhcp_proxy_script_fetcher_
->Fetch(
250 &pac_script_
, base::Bind(&ProxyScriptDecider::OnIOCompletion
,
251 base::Unretained(this)));
254 if (!proxy_script_fetcher_
) {
255 net_log_
.AddEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER
);
256 return ERR_UNEXPECTED
;
259 return proxy_script_fetcher_
->Fetch(
260 effective_pac_url
, &pac_script_
,
261 base::Bind(&ProxyScriptDecider::OnIOCompletion
, base::Unretained(this)));
264 int ProxyScriptDecider::DoFetchPacScriptComplete(int result
) {
265 DCHECK(fetch_pac_bytes_
);
267 net_log_
.EndEventWithNetErrorCode(
268 NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT
, result
);
270 return TryToFallbackPacSource(result
);
272 next_state_
= STATE_VERIFY_PAC_SCRIPT
;
276 int ProxyScriptDecider::DoVerifyPacScript() {
277 next_state_
= STATE_VERIFY_PAC_SCRIPT_COMPLETE
;
279 // This is just a heuristic. Ideally we would try to parse the script.
280 if (fetch_pac_bytes_
&& !LooksLikePacScript(pac_script_
))
281 return ERR_PAC_SCRIPT_FAILED
;
286 int ProxyScriptDecider::DoVerifyPacScriptComplete(int result
) {
288 return TryToFallbackPacSource(result
);
290 const PacSource
& pac_source
= current_pac_source();
292 // Extract the current script data.
293 if (fetch_pac_bytes_
) {
294 script_data_
= ProxyResolverScriptData::FromUTF16(pac_script_
);
296 script_data_
= pac_source
.type
== PacSource::CUSTOM
?
297 ProxyResolverScriptData::FromURL(pac_source
.url
) :
298 ProxyResolverScriptData::ForAutoDetect();
301 // Let the caller know which automatic setting we ended up initializing the
302 // resolver for (there may have been multiple fallbacks to choose from.)
303 if (current_pac_source().type
== PacSource::CUSTOM
) {
305 ProxyConfig::CreateFromCustomPacURL(current_pac_source().url
);
306 effective_config_
.set_pac_mandatory(pac_mandatory_
);
308 if (fetch_pac_bytes_
) {
309 GURL auto_detected_url
;
311 switch (current_pac_source().type
) {
312 case PacSource::WPAD_DHCP
:
313 auto_detected_url
= dhcp_proxy_script_fetcher_
->GetPacURL();
316 case PacSource::WPAD_DNS
:
317 auto_detected_url
= GURL(kWpadUrl
);
325 ProxyConfig::CreateFromCustomPacURL(auto_detected_url
);
327 // The resolver does its own resolution so we cannot know the
328 // URL. Just do the best we can and state that the configuration
329 // is to auto-detect proxy settings.
330 effective_config_
= ProxyConfig::CreateAutoDetect();
337 int ProxyScriptDecider::TryToFallbackPacSource(int error
) {
340 if (current_pac_source_index_
+ 1 >= pac_sources_
.size()) {
341 // Nothing left to fall back to.
345 // Advance to next URL in our list.
346 ++current_pac_source_index_
;
349 NetLog::TYPE_PROXY_SCRIPT_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE
);
351 next_state_
= GetStartState();
356 ProxyScriptDecider::State
ProxyScriptDecider::GetStartState() const {
357 return fetch_pac_bytes_
? STATE_FETCH_PAC_SCRIPT
: STATE_VERIFY_PAC_SCRIPT
;
360 void ProxyScriptDecider::DetermineURL(const PacSource
& pac_source
,
361 GURL
* effective_pac_url
) {
362 DCHECK(effective_pac_url
);
364 switch (pac_source
.type
) {
365 case PacSource::WPAD_DHCP
:
367 case PacSource::WPAD_DNS
:
368 *effective_pac_url
= GURL(kWpadUrl
);
370 case PacSource::CUSTOM
:
371 *effective_pac_url
= pac_source
.url
;
376 const ProxyScriptDecider::PacSource
&
377 ProxyScriptDecider::current_pac_source() const {
378 DCHECK_LT(current_pac_source_index_
, pac_sources_
.size());
379 return pac_sources_
[current_pac_source_index_
];
382 void ProxyScriptDecider::OnWaitTimerFired() {
386 void ProxyScriptDecider::DidComplete() {
387 net_log_
.EndEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER
);
390 void ProxyScriptDecider::Cancel() {
391 DCHECK_NE(STATE_NONE
, next_state_
);
393 net_log_
.AddEvent(NetLog::TYPE_CANCELLED
);
395 switch (next_state_
) {
396 case STATE_WAIT_COMPLETE
:
399 case STATE_FETCH_PAC_SCRIPT_COMPLETE
:
400 proxy_script_fetcher_
->Cancel();
407 // This is safe to call in any state.
408 if (dhcp_proxy_script_fetcher_
)
409 dhcp_proxy_script_fetcher_
->Cancel();