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_resolution/pac_file_decider.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/compiler_specific.h"
12 #include "base/format_macros.h"
13 #include "base/logging.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/values.h"
18 #include "net/base/completion_repeating_callback.h"
19 #include "net/base/host_port_pair.h"
20 #include "net/base/net_errors.h"
21 #include "net/base/network_isolation_key.h"
22 #include "net/base/request_priority.h"
23 #include "net/log/net_log_capture_mode.h"
24 #include "net/log/net_log_event_type.h"
25 #include "net/log/net_log_source_type.h"
26 #include "net/proxy_resolution/dhcp_pac_file_fetcher.h"
27 #include "net/proxy_resolution/pac_file_fetcher.h"
28 #include "net/url_request/url_request_context.h"
29
30 namespace net {
31
32 namespace {
33
LooksLikePacScript(const base::string16 & script)34 bool LooksLikePacScript(const base::string16& script) {
35 // Note: this is only an approximation! It may not always work correctly,
36 // however it is very likely that legitimate scripts have this exact string,
37 // since they must minimally define a function of this name. Conversely, a
38 // file not containing the string is not likely to be a PAC script.
39 //
40 // An exact test would have to load the script in a javascript evaluator.
41 return script.find(base::ASCIIToUTF16("FindProxyForURL")) !=
42 base::string16::npos;
43 }
44
45 // This is the hard-coded location used by the DNS portion of web proxy
46 // auto-discovery.
47 //
48 // Note that we not use DNS devolution to find the WPAD host, since that could
49 // be dangerous should our top level domain registry become out of date.
50 //
51 // Instead we directly resolve "wpad", and let the operating system apply the
52 // DNS suffix search paths. This is the same approach taken by Firefox, and
53 // compatibility hasn't been an issue.
54 //
55 // For more details, also check out this comment:
56 // http://code.google.com/p/chromium/issues/detail?id=18575#c20
57 const char kWpadUrl[] = "http://wpad/wpad.dat";
58 const int kQuickCheckDelayMs = 1000;
59
60 } // namespace
61
62 PacFileDataWithSource::PacFileDataWithSource() = default;
63 PacFileDataWithSource::~PacFileDataWithSource() = default;
64 PacFileDataWithSource::PacFileDataWithSource(const PacFileDataWithSource&) =
65 default;
66 PacFileDataWithSource& PacFileDataWithSource::operator=(
67 const PacFileDataWithSource&) = default;
68
NetLogParams(const GURL & effective_pac_url) const69 base::Value PacFileDecider::PacSource::NetLogParams(
70 const GURL& effective_pac_url) const {
71 base::Value dict(base::Value::Type::DICTIONARY);
72 std::string source;
73 switch (type) {
74 case PacSource::WPAD_DHCP:
75 source = "WPAD DHCP";
76 break;
77 case PacSource::WPAD_DNS:
78 source = "WPAD DNS: ";
79 source += effective_pac_url.possibly_invalid_spec();
80 break;
81 case PacSource::CUSTOM:
82 source = "Custom PAC URL: ";
83 source += effective_pac_url.possibly_invalid_spec();
84 break;
85 }
86 dict.SetStringKey("source", source);
87 return dict;
88 }
89
PacFileDecider(PacFileFetcher * pac_file_fetcher,DhcpPacFileFetcher * dhcp_pac_file_fetcher,NetLog * net_log)90 PacFileDecider::PacFileDecider(PacFileFetcher* pac_file_fetcher,
91 DhcpPacFileFetcher* dhcp_pac_file_fetcher,
92 NetLog* net_log)
93 : pac_file_fetcher_(pac_file_fetcher),
94 dhcp_pac_file_fetcher_(dhcp_pac_file_fetcher),
95 current_pac_source_index_(0u),
96 pac_mandatory_(false),
97 next_state_(STATE_NONE),
98 net_log_(
99 NetLogWithSource::Make(net_log, NetLogSourceType::PAC_FILE_DECIDER)),
100 fetch_pac_bytes_(false),
101 quick_check_enabled_(true) {}
102
~PacFileDecider()103 PacFileDecider::~PacFileDecider() {
104 if (next_state_ != STATE_NONE)
105 Cancel();
106 }
107
Start(const ProxyConfigWithAnnotation & config,const base::TimeDelta wait_delay,bool fetch_pac_bytes,CompletionOnceCallback callback)108 int PacFileDecider::Start(const ProxyConfigWithAnnotation& config,
109 const base::TimeDelta wait_delay,
110 bool fetch_pac_bytes,
111 CompletionOnceCallback callback) {
112 DCHECK_EQ(STATE_NONE, next_state_);
113 DCHECK(!callback.is_null());
114 DCHECK(config.value().HasAutomaticSettings());
115
116 net_log_.BeginEvent(NetLogEventType::PAC_FILE_DECIDER);
117
118 fetch_pac_bytes_ = fetch_pac_bytes;
119
120 // Save the |wait_delay| as a non-negative value.
121 wait_delay_ = wait_delay;
122 if (wait_delay_ < base::TimeDelta())
123 wait_delay_ = base::TimeDelta();
124
125 pac_mandatory_ = config.value().pac_mandatory();
126 have_custom_pac_url_ = config.value().has_pac_url();
127
128 pac_sources_ = BuildPacSourcesFallbackList(config.value());
129 DCHECK(!pac_sources_.empty());
130
131 traffic_annotation_ =
132 net::MutableNetworkTrafficAnnotationTag(config.traffic_annotation());
133 next_state_ = STATE_WAIT;
134
135 int rv = DoLoop(OK);
136 if (rv == ERR_IO_PENDING)
137 callback_ = std::move(callback);
138 else
139 DidComplete();
140
141 return rv;
142 }
143
OnShutdown()144 void PacFileDecider::OnShutdown() {
145 // Don't do anything if idle.
146 if (next_state_ == STATE_NONE)
147 return;
148
149 // Just cancel any pending work.
150 Cancel();
151 }
152
effective_config() const153 const ProxyConfigWithAnnotation& PacFileDecider::effective_config() const {
154 DCHECK_EQ(STATE_NONE, next_state_);
155 return effective_config_;
156 }
157
script_data() const158 const PacFileDataWithSource& PacFileDecider::script_data() const {
159 DCHECK_EQ(STATE_NONE, next_state_);
160 return script_data_;
161 }
162
163 // Initialize the fallback rules.
164 // (1) WPAD (DHCP).
165 // (2) WPAD (DNS).
166 // (3) Custom PAC URL.
BuildPacSourcesFallbackList(const ProxyConfig & config) const167 PacFileDecider::PacSourceList PacFileDecider::BuildPacSourcesFallbackList(
168 const ProxyConfig& config) const {
169 PacSourceList pac_sources;
170 if (config.auto_detect()) {
171 pac_sources.push_back(PacSource(PacSource::WPAD_DHCP, GURL(kWpadUrl)));
172 pac_sources.push_back(PacSource(PacSource::WPAD_DNS, GURL(kWpadUrl)));
173 }
174 if (config.has_pac_url())
175 pac_sources.push_back(PacSource(PacSource::CUSTOM, config.pac_url()));
176 return pac_sources;
177 }
178
OnIOCompletion(int result)179 void PacFileDecider::OnIOCompletion(int result) {
180 DCHECK_NE(STATE_NONE, next_state_);
181 int rv = DoLoop(result);
182 if (rv != ERR_IO_PENDING) {
183 DidComplete();
184 std::move(callback_).Run(rv);
185 }
186 }
187
DoLoop(int result)188 int PacFileDecider::DoLoop(int result) {
189 DCHECK_NE(next_state_, STATE_NONE);
190 int rv = result;
191 do {
192 State state = next_state_;
193 next_state_ = STATE_NONE;
194 switch (state) {
195 case STATE_WAIT:
196 DCHECK_EQ(OK, rv);
197 rv = DoWait();
198 break;
199 case STATE_WAIT_COMPLETE:
200 rv = DoWaitComplete(rv);
201 break;
202 case STATE_QUICK_CHECK:
203 DCHECK_EQ(OK, rv);
204 rv = DoQuickCheck();
205 break;
206 case STATE_QUICK_CHECK_COMPLETE:
207 rv = DoQuickCheckComplete(rv);
208 break;
209 case STATE_FETCH_PAC_SCRIPT:
210 DCHECK_EQ(OK, rv);
211 rv = DoFetchPacScript();
212 break;
213 case STATE_FETCH_PAC_SCRIPT_COMPLETE:
214 rv = DoFetchPacScriptComplete(rv);
215 break;
216 case STATE_VERIFY_PAC_SCRIPT:
217 DCHECK_EQ(OK, rv);
218 rv = DoVerifyPacScript();
219 break;
220 case STATE_VERIFY_PAC_SCRIPT_COMPLETE:
221 rv = DoVerifyPacScriptComplete(rv);
222 break;
223 default:
224 NOTREACHED() << "bad state";
225 rv = ERR_UNEXPECTED;
226 break;
227 }
228 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
229 return rv;
230 }
231
DoWait()232 int PacFileDecider::DoWait() {
233 next_state_ = STATE_WAIT_COMPLETE;
234
235 // If no waiting is required, continue on to the next state.
236 if (wait_delay_.ToInternalValue() == 0)
237 return OK;
238
239 // Otherwise wait the specified amount of time.
240 wait_timer_.Start(FROM_HERE, wait_delay_, this,
241 &PacFileDecider::OnWaitTimerFired);
242 net_log_.BeginEvent(NetLogEventType::PAC_FILE_DECIDER_WAIT);
243 return ERR_IO_PENDING;
244 }
245
DoWaitComplete(int result)246 int PacFileDecider::DoWaitComplete(int result) {
247 DCHECK_EQ(OK, result);
248 if (wait_delay_.ToInternalValue() != 0) {
249 net_log_.EndEventWithNetErrorCode(NetLogEventType::PAC_FILE_DECIDER_WAIT,
250 result);
251 }
252 if (quick_check_enabled_ && current_pac_source().type == PacSource::WPAD_DNS)
253 next_state_ = STATE_QUICK_CHECK;
254 else
255 next_state_ = GetStartState();
256 return OK;
257 }
258
DoQuickCheck()259 int PacFileDecider::DoQuickCheck() {
260 DCHECK(quick_check_enabled_);
261 if (!pac_file_fetcher_ || !pac_file_fetcher_->GetRequestContext() ||
262 !pac_file_fetcher_->GetRequestContext()->host_resolver()) {
263 // If we have no resolver, skip QuickCheck altogether.
264 next_state_ = GetStartState();
265 return OK;
266 }
267
268 std::string host = current_pac_source().url.host();
269
270 HostResolver::ResolveHostParameters parameters;
271 // We use HIGHEST here because proxy decision blocks doing any other requests.
272 parameters.initial_priority = HIGHEST;
273 // Only resolve via the system resolver for maximum compatibility with DNS
274 // suffix search paths, because for security, we are relying on suffix search
275 // paths rather than WPAD-standard DNS devolution.
276 parameters.source = HostResolverSource::SYSTEM;
277
278 HostResolver* host_resolver =
279 pac_file_fetcher_->GetRequestContext()->host_resolver();
280 // It's safe to use an empty NetworkIsolationKey() here, since this is only
281 // for fetching the PAC script, so can't usefully leak data to web-initiated
282 // requests (Which can't use an empty NIK for resolving IPs other than that of
283 // the proxy).
284 resolve_request_ = host_resolver->CreateRequest(
285 HostPortPair(host, 80), NetworkIsolationKey(), net_log_, parameters);
286
287 CompletionRepeatingCallback callback = base::BindRepeating(
288 &PacFileDecider::OnIOCompletion, base::Unretained(this));
289
290 next_state_ = STATE_QUICK_CHECK_COMPLETE;
291 quick_check_timer_.Start(
292 FROM_HERE, base::TimeDelta::FromMilliseconds(kQuickCheckDelayMs),
293 base::BindOnce(callback, ERR_NAME_NOT_RESOLVED));
294
295 return resolve_request_->Start(callback);
296 }
297
DoQuickCheckComplete(int result)298 int PacFileDecider::DoQuickCheckComplete(int result) {
299 DCHECK(quick_check_enabled_);
300 resolve_request_.reset();
301 quick_check_timer_.Stop();
302 if (result != OK)
303 return TryToFallbackPacSource(result);
304 next_state_ = GetStartState();
305 return result;
306 }
307
DoFetchPacScript()308 int PacFileDecider::DoFetchPacScript() {
309 DCHECK(fetch_pac_bytes_);
310
311 next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE;
312
313 const PacSource& pac_source = current_pac_source();
314
315 GURL effective_pac_url;
316 DetermineURL(pac_source, &effective_pac_url);
317
318 net_log_.BeginEvent(NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT, [&] {
319 return pac_source.NetLogParams(effective_pac_url);
320 });
321
322 if (pac_source.type == PacSource::WPAD_DHCP) {
323 if (!dhcp_pac_file_fetcher_) {
324 net_log_.AddEvent(NetLogEventType::PAC_FILE_DECIDER_HAS_NO_FETCHER);
325 return ERR_UNEXPECTED;
326 }
327
328 return dhcp_pac_file_fetcher_->Fetch(
329 &pac_script_,
330 base::BindOnce(&PacFileDecider::OnIOCompletion, base::Unretained(this)),
331 net_log_, NetworkTrafficAnnotationTag(traffic_annotation_));
332 }
333
334 if (!pac_file_fetcher_) {
335 net_log_.AddEvent(NetLogEventType::PAC_FILE_DECIDER_HAS_NO_FETCHER);
336 return ERR_UNEXPECTED;
337 }
338
339 return pac_file_fetcher_->Fetch(
340 effective_pac_url, &pac_script_,
341 base::BindOnce(&PacFileDecider::OnIOCompletion, base::Unretained(this)),
342 NetworkTrafficAnnotationTag(traffic_annotation_));
343 }
344
DoFetchPacScriptComplete(int result)345 int PacFileDecider::DoFetchPacScriptComplete(int result) {
346 DCHECK(fetch_pac_bytes_);
347
348 net_log_.EndEventWithNetErrorCode(
349 NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT, result);
350 if (result != OK)
351 return TryToFallbackPacSource(result);
352
353 next_state_ = STATE_VERIFY_PAC_SCRIPT;
354 return result;
355 }
356
DoVerifyPacScript()357 int PacFileDecider::DoVerifyPacScript() {
358 next_state_ = STATE_VERIFY_PAC_SCRIPT_COMPLETE;
359
360 // This is just a heuristic. Ideally we would try to parse the script.
361 if (fetch_pac_bytes_ && !LooksLikePacScript(pac_script_))
362 return ERR_PAC_SCRIPT_FAILED;
363
364 return OK;
365 }
366
DoVerifyPacScriptComplete(int result)367 int PacFileDecider::DoVerifyPacScriptComplete(int result) {
368 if (result != OK)
369 return TryToFallbackPacSource(result);
370
371 const PacSource& pac_source = current_pac_source();
372
373 // Extract the current script data.
374 script_data_.from_auto_detect = pac_source.type != PacSource::CUSTOM;
375 if (fetch_pac_bytes_) {
376 script_data_.data = PacFileData::FromUTF16(pac_script_);
377 } else {
378 script_data_.data = pac_source.type == PacSource::CUSTOM
379 ? PacFileData::FromURL(pac_source.url)
380 : PacFileData::ForAutoDetect();
381 }
382
383 // Let the caller know which automatic setting we ended up initializing the
384 // resolver for (there may have been multiple fallbacks to choose from.)
385 ProxyConfig config;
386 if (current_pac_source().type == PacSource::CUSTOM) {
387 config = ProxyConfig::CreateFromCustomPacURL(current_pac_source().url);
388 config.set_pac_mandatory(pac_mandatory_);
389 } else {
390 if (fetch_pac_bytes_) {
391 GURL auto_detected_url;
392
393 switch (current_pac_source().type) {
394 case PacSource::WPAD_DHCP:
395 auto_detected_url = dhcp_pac_file_fetcher_->GetPacURL();
396 break;
397
398 case PacSource::WPAD_DNS:
399 auto_detected_url = GURL(kWpadUrl);
400 break;
401
402 default:
403 NOTREACHED();
404 }
405
406 config = ProxyConfig::CreateFromCustomPacURL(auto_detected_url);
407 } else {
408 // The resolver does its own resolution so we cannot know the
409 // URL. Just do the best we can and state that the configuration
410 // is to auto-detect proxy settings.
411 config = ProxyConfig::CreateAutoDetect();
412 }
413 }
414
415 effective_config_ = ProxyConfigWithAnnotation(
416 config, net::NetworkTrafficAnnotationTag(traffic_annotation_));
417
418 return OK;
419 }
420
TryToFallbackPacSource(int error)421 int PacFileDecider::TryToFallbackPacSource(int error) {
422 DCHECK_LT(error, 0);
423
424 if (current_pac_source_index_ + 1 >= pac_sources_.size()) {
425 // Nothing left to fall back to.
426 return error;
427 }
428
429 // Advance to next URL in our list.
430 ++current_pac_source_index_;
431
432 net_log_.AddEvent(
433 NetLogEventType::PAC_FILE_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE);
434 if (quick_check_enabled_ && current_pac_source().type == PacSource::WPAD_DNS)
435 next_state_ = STATE_QUICK_CHECK;
436 else
437 next_state_ = GetStartState();
438
439 return OK;
440 }
441
GetStartState() const442 PacFileDecider::State PacFileDecider::GetStartState() const {
443 return fetch_pac_bytes_ ? STATE_FETCH_PAC_SCRIPT : STATE_VERIFY_PAC_SCRIPT;
444 }
445
DetermineURL(const PacSource & pac_source,GURL * effective_pac_url)446 void PacFileDecider::DetermineURL(const PacSource& pac_source,
447 GURL* effective_pac_url) {
448 DCHECK(effective_pac_url);
449
450 switch (pac_source.type) {
451 case PacSource::WPAD_DHCP:
452 break;
453 case PacSource::WPAD_DNS:
454 *effective_pac_url = GURL(kWpadUrl);
455 break;
456 case PacSource::CUSTOM:
457 *effective_pac_url = pac_source.url;
458 break;
459 }
460 }
461
current_pac_source() const462 const PacFileDecider::PacSource& PacFileDecider::current_pac_source() const {
463 DCHECK_LT(current_pac_source_index_, pac_sources_.size());
464 return pac_sources_[current_pac_source_index_];
465 }
466
OnWaitTimerFired()467 void PacFileDecider::OnWaitTimerFired() {
468 OnIOCompletion(OK);
469 }
470
DidComplete()471 void PacFileDecider::DidComplete() {
472 net_log_.EndEvent(NetLogEventType::PAC_FILE_DECIDER);
473 }
474
Cancel()475 void PacFileDecider::Cancel() {
476 DCHECK_NE(STATE_NONE, next_state_);
477
478 net_log_.AddEvent(NetLogEventType::CANCELLED);
479
480 switch (next_state_) {
481 case STATE_QUICK_CHECK_COMPLETE:
482 resolve_request_.reset();
483 break;
484 case STATE_WAIT_COMPLETE:
485 wait_timer_.Stop();
486 break;
487 case STATE_FETCH_PAC_SCRIPT_COMPLETE:
488 pac_file_fetcher_->Cancel();
489 break;
490 default:
491 break;
492 }
493
494 next_state_ = STATE_NONE;
495
496 // This is safe to call in any state.
497 if (dhcp_pac_file_fetcher_)
498 dhcp_pac_file_fetcher_->Cancel();
499
500 DCHECK(!resolve_request_);
501
502 DidComplete();
503 }
504
505 } // namespace net
506