1 // Copyright 2012 The Chromium Authors
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/check_op.h"
10 #include "base/compiler_specific.h"
11 #include "base/format_macros.h"
12 #include "base/functional/bind.h"
13 #include "base/functional/callback_helpers.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/notreached.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/values.h"
19 #include "net/base/completion_repeating_callback.h"
20 #include "net/base/host_port_pair.h"
21 #include "net/base/isolation_info.h"
22 #include "net/base/net_errors.h"
23 #include "net/base/request_priority.h"
24 #include "net/log/net_log_capture_mode.h"
25 #include "net/log/net_log_event_type.h"
26 #include "net/log/net_log_source_type.h"
27 #include "net/proxy_resolution/dhcp_pac_file_fetcher.h"
28 #include "net/proxy_resolution/pac_file_fetcher.h"
29 #include "net/url_request/url_request_context.h"
30
31 namespace net {
32
33 namespace {
34
LooksLikePacScript(const std::u16string & script)35 bool LooksLikePacScript(const std::u16string& script) {
36 // Note: this is only an approximation! It may not always work correctly,
37 // however it is very likely that legitimate scripts have this exact string,
38 // since they must minimally define a function of this name. Conversely, a
39 // file not containing the string is not likely to be a PAC script.
40 //
41 // An exact test would have to load the script in a javascript evaluator.
42 return script.find(u"FindProxyForURL") != std::u16string::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::Dict PacFileDecider::PacSource::NetLogParams(
70 const GURL& effective_pac_url) const {
71 base::Value::Dict dict;
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.Set("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 net_log_(NetLogWithSource::Make(net_log,
96 NetLogSourceType::PAC_FILE_DECIDER)) {}
97
~PacFileDecider()98 PacFileDecider::~PacFileDecider() {
99 if (next_state_ != STATE_NONE)
100 Cancel();
101 }
102
Start(const ProxyConfigWithAnnotation & config,const base::TimeDelta wait_delay,bool fetch_pac_bytes,CompletionOnceCallback callback)103 int PacFileDecider::Start(const ProxyConfigWithAnnotation& config,
104 const base::TimeDelta wait_delay,
105 bool fetch_pac_bytes,
106 CompletionOnceCallback callback) {
107 DCHECK_EQ(STATE_NONE, next_state_);
108 DCHECK(!callback.is_null());
109 DCHECK(config.value().HasAutomaticSettings());
110
111 net_log_.BeginEvent(NetLogEventType::PAC_FILE_DECIDER);
112
113 fetch_pac_bytes_ = fetch_pac_bytes;
114
115 // Save the |wait_delay| as a non-negative value.
116 wait_delay_ = wait_delay;
117 if (wait_delay_.is_negative())
118 wait_delay_ = base::TimeDelta();
119
120 pac_mandatory_ = config.value().pac_mandatory();
121 have_custom_pac_url_ = config.value().has_pac_url();
122
123 pac_sources_ = BuildPacSourcesFallbackList(config.value());
124 DCHECK(!pac_sources_.empty());
125
126 traffic_annotation_ =
127 net::MutableNetworkTrafficAnnotationTag(config.traffic_annotation());
128 next_state_ = STATE_WAIT;
129
130 int rv = DoLoop(OK);
131 if (rv == ERR_IO_PENDING)
132 callback_ = std::move(callback);
133 else
134 DidComplete();
135
136 return rv;
137 }
138
OnShutdown()139 void PacFileDecider::OnShutdown() {
140 // Don't do anything if idle.
141 if (next_state_ == STATE_NONE)
142 return;
143
144 // Just cancel any pending work.
145 Cancel();
146 }
147
effective_config() const148 const ProxyConfigWithAnnotation& PacFileDecider::effective_config() const {
149 DCHECK_EQ(STATE_NONE, next_state_);
150 return effective_config_;
151 }
152
script_data() const153 const PacFileDataWithSource& PacFileDecider::script_data() const {
154 DCHECK_EQ(STATE_NONE, next_state_);
155 return script_data_;
156 }
157
158 // Initialize the fallback rules.
159 // (1) WPAD (DHCP).
160 // (2) WPAD (DNS).
161 // (3) Custom PAC URL.
BuildPacSourcesFallbackList(const ProxyConfig & config) const162 PacFileDecider::PacSourceList PacFileDecider::BuildPacSourcesFallbackList(
163 const ProxyConfig& config) const {
164 PacSourceList pac_sources;
165 if (config.auto_detect()) {
166 pac_sources.push_back(PacSource(PacSource::WPAD_DHCP, GURL(kWpadUrl)));
167 pac_sources.push_back(PacSource(PacSource::WPAD_DNS, GURL(kWpadUrl)));
168 }
169 if (config.has_pac_url())
170 pac_sources.push_back(PacSource(PacSource::CUSTOM, config.pac_url()));
171 return pac_sources;
172 }
173
OnIOCompletion(int result)174 void PacFileDecider::OnIOCompletion(int result) {
175 DCHECK_NE(STATE_NONE, next_state_);
176 int rv = DoLoop(result);
177 if (rv != ERR_IO_PENDING) {
178 DidComplete();
179 std::move(callback_).Run(rv);
180 }
181 }
182
DoLoop(int result)183 int PacFileDecider::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;
222 }
223 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
224 return rv;
225 }
226
DoWait()227 int PacFileDecider::DoWait() {
228 next_state_ = STATE_WAIT_COMPLETE;
229
230 // If no waiting is required, continue on to the next state.
231 if (wait_delay_.ToInternalValue() == 0)
232 return OK;
233
234 // Otherwise wait the specified amount of time.
235 wait_timer_.Start(FROM_HERE, wait_delay_, this,
236 &PacFileDecider::OnWaitTimerFired);
237 net_log_.BeginEvent(NetLogEventType::PAC_FILE_DECIDER_WAIT);
238 return ERR_IO_PENDING;
239 }
240
DoWaitComplete(int result)241 int PacFileDecider::DoWaitComplete(int result) {
242 DCHECK_EQ(OK, result);
243 if (wait_delay_.ToInternalValue() != 0) {
244 net_log_.EndEventWithNetErrorCode(NetLogEventType::PAC_FILE_DECIDER_WAIT,
245 result);
246 }
247 if (quick_check_enabled_ && current_pac_source().type == PacSource::WPAD_DNS)
248 next_state_ = STATE_QUICK_CHECK;
249 else
250 next_state_ = GetStartState();
251 return OK;
252 }
253
DoQuickCheck()254 int PacFileDecider::DoQuickCheck() {
255 DCHECK(quick_check_enabled_);
256 if (!pac_file_fetcher_ || !pac_file_fetcher_->GetRequestContext() ||
257 !pac_file_fetcher_->GetRequestContext()->host_resolver()) {
258 // If we have no resolver, skip QuickCheck altogether.
259 next_state_ = GetStartState();
260 return OK;
261 }
262
263 std::string host = current_pac_source().url.host();
264
265 HostResolver::ResolveHostParameters parameters;
266 // We use HIGHEST here because proxy decision blocks doing any other requests.
267 parameters.initial_priority = HIGHEST;
268 // Only resolve via the system resolver for maximum compatibility with DNS
269 // suffix search paths, because for security, we are relying on suffix search
270 // paths rather than WPAD-standard DNS devolution.
271 parameters.source = HostResolverSource::SYSTEM;
272
273 // For most users, the WPAD DNS query will have no results. Allowing the query
274 // to go out via LLMNR or mDNS (which usually have no quick negative response)
275 // would therefore typically result in waiting the full timeout before
276 // `quick_check_timer_` fires. Given that a lot of Chrome requests could be
277 // blocked on completing these checks, it is better to avoid multicast
278 // resolution for WPAD.
279 // See crbug.com/1176970.
280 parameters.avoid_multicast_resolution = true;
281
282 HostResolver* host_resolver =
283 pac_file_fetcher_->GetRequestContext()->host_resolver();
284 resolve_request_ = host_resolver->CreateRequest(
285 HostPortPair(host, 80),
286 pac_file_fetcher_->isolation_info().network_anonymization_key(), net_log_,
287 parameters);
288
289 CompletionRepeatingCallback callback = base::BindRepeating(
290 &PacFileDecider::OnIOCompletion, base::Unretained(this));
291
292 next_state_ = STATE_QUICK_CHECK_COMPLETE;
293 quick_check_timer_.Start(FROM_HERE, base::Milliseconds(kQuickCheckDelayMs),
294 base::BindOnce(callback, ERR_NAME_NOT_RESOLVED));
295
296 return resolve_request_->Start(callback);
297 }
298
DoQuickCheckComplete(int result)299 int PacFileDecider::DoQuickCheckComplete(int result) {
300 DCHECK(quick_check_enabled_);
301 resolve_request_.reset();
302 quick_check_timer_.Stop();
303 if (result != OK)
304 return TryToFallbackPacSource(result);
305 next_state_ = GetStartState();
306 return result;
307 }
308
DoFetchPacScript()309 int PacFileDecider::DoFetchPacScript() {
310 DCHECK(fetch_pac_bytes_);
311
312 next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE;
313
314 const PacSource& pac_source = current_pac_source();
315
316 GURL effective_pac_url;
317 DetermineURL(pac_source, &effective_pac_url);
318
319 net_log_.BeginEvent(NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT, [&] {
320 return pac_source.NetLogParams(effective_pac_url);
321 });
322
323 if (pac_source.type == PacSource::WPAD_DHCP) {
324 if (!dhcp_pac_file_fetcher_) {
325 net_log_.AddEvent(NetLogEventType::PAC_FILE_DECIDER_HAS_NO_FETCHER);
326 return ERR_UNEXPECTED;
327 }
328
329 return dhcp_pac_file_fetcher_->Fetch(
330 &pac_script_,
331 base::BindOnce(&PacFileDecider::OnIOCompletion, base::Unretained(this)),
332 net_log_, NetworkTrafficAnnotationTag(traffic_annotation_));
333 }
334
335 if (!pac_file_fetcher_) {
336 net_log_.AddEvent(NetLogEventType::PAC_FILE_DECIDER_HAS_NO_FETCHER);
337 return ERR_UNEXPECTED;
338 }
339
340 return pac_file_fetcher_->Fetch(
341 effective_pac_url, &pac_script_,
342 base::BindOnce(&PacFileDecider::OnIOCompletion, base::Unretained(this)),
343 NetworkTrafficAnnotationTag(traffic_annotation_));
344 }
345
DoFetchPacScriptComplete(int result)346 int PacFileDecider::DoFetchPacScriptComplete(int result) {
347 DCHECK(fetch_pac_bytes_);
348
349 net_log_.EndEventWithNetErrorCode(
350 NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT, result);
351 if (result != OK)
352 return TryToFallbackPacSource(result);
353
354 next_state_ = STATE_VERIFY_PAC_SCRIPT;
355 return result;
356 }
357
DoVerifyPacScript()358 int PacFileDecider::DoVerifyPacScript() {
359 next_state_ = STATE_VERIFY_PAC_SCRIPT_COMPLETE;
360
361 // This is just a heuristic. Ideally we would try to parse the script.
362 if (fetch_pac_bytes_ && !LooksLikePacScript(pac_script_))
363 return ERR_PAC_SCRIPT_FAILED;
364
365 return OK;
366 }
367
DoVerifyPacScriptComplete(int result)368 int PacFileDecider::DoVerifyPacScriptComplete(int result) {
369 if (result != OK)
370 return TryToFallbackPacSource(result);
371
372 const PacSource& pac_source = current_pac_source();
373
374 // Extract the current script data.
375 script_data_.from_auto_detect = pac_source.type != PacSource::CUSTOM;
376 if (fetch_pac_bytes_) {
377 script_data_.data = PacFileData::FromUTF16(pac_script_);
378 } else {
379 script_data_.data = pac_source.type == PacSource::CUSTOM
380 ? PacFileData::FromURL(pac_source.url)
381 : PacFileData::ForAutoDetect();
382 }
383
384 // Let the caller know which automatic setting we ended up initializing the
385 // resolver for (there may have been multiple fallbacks to choose from.)
386 ProxyConfig config;
387 if (current_pac_source().type == PacSource::CUSTOM) {
388 config = ProxyConfig::CreateFromCustomPacURL(current_pac_source().url);
389 config.set_pac_mandatory(pac_mandatory_);
390 } else {
391 if (fetch_pac_bytes_) {
392 GURL auto_detected_url;
393
394 switch (current_pac_source().type) {
395 case PacSource::WPAD_DHCP:
396 auto_detected_url = dhcp_pac_file_fetcher_->GetPacURL();
397 break;
398
399 case PacSource::WPAD_DNS:
400 auto_detected_url = GURL(kWpadUrl);
401 break;
402
403 default:
404 NOTREACHED();
405 }
406
407 config = ProxyConfig::CreateFromCustomPacURL(auto_detected_url);
408 } else {
409 // The resolver does its own resolution so we cannot know the
410 // URL. Just do the best we can and state that the configuration
411 // is to auto-detect proxy settings.
412 config = ProxyConfig::CreateAutoDetect();
413 }
414 }
415
416 effective_config_ = ProxyConfigWithAnnotation(
417 config, net::NetworkTrafficAnnotationTag(traffic_annotation_));
418
419 return OK;
420 }
421
TryToFallbackPacSource(int error)422 int PacFileDecider::TryToFallbackPacSource(int error) {
423 DCHECK_LT(error, 0);
424
425 if (current_pac_source_index_ + 1 >= pac_sources_.size()) {
426 // Nothing left to fall back to.
427 return error;
428 }
429
430 // Advance to next URL in our list.
431 ++current_pac_source_index_;
432
433 net_log_.AddEvent(
434 NetLogEventType::PAC_FILE_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE);
435 if (quick_check_enabled_ && current_pac_source().type == PacSource::WPAD_DNS)
436 next_state_ = STATE_QUICK_CHECK;
437 else
438 next_state_ = GetStartState();
439
440 return OK;
441 }
442
GetStartState() const443 PacFileDecider::State PacFileDecider::GetStartState() const {
444 return fetch_pac_bytes_ ? STATE_FETCH_PAC_SCRIPT : STATE_VERIFY_PAC_SCRIPT;
445 }
446
DetermineURL(const PacSource & pac_source,GURL * effective_pac_url)447 void PacFileDecider::DetermineURL(const PacSource& pac_source,
448 GURL* effective_pac_url) {
449 DCHECK(effective_pac_url);
450
451 switch (pac_source.type) {
452 case PacSource::WPAD_DHCP:
453 break;
454 case PacSource::WPAD_DNS:
455 *effective_pac_url = GURL(kWpadUrl);
456 break;
457 case PacSource::CUSTOM:
458 *effective_pac_url = pac_source.url;
459 break;
460 }
461 }
462
current_pac_source() const463 const PacFileDecider::PacSource& PacFileDecider::current_pac_source() const {
464 DCHECK_LT(current_pac_source_index_, pac_sources_.size());
465 return pac_sources_[current_pac_source_index_];
466 }
467
OnWaitTimerFired()468 void PacFileDecider::OnWaitTimerFired() {
469 OnIOCompletion(OK);
470 }
471
DidComplete()472 void PacFileDecider::DidComplete() {
473 net_log_.EndEvent(NetLogEventType::PAC_FILE_DECIDER);
474 }
475
Cancel()476 void PacFileDecider::Cancel() {
477 DCHECK_NE(STATE_NONE, next_state_);
478
479 net_log_.AddEvent(NetLogEventType::CANCELLED);
480
481 switch (next_state_) {
482 case STATE_QUICK_CHECK_COMPLETE:
483 resolve_request_.reset();
484 break;
485 case STATE_WAIT_COMPLETE:
486 wait_timer_.Stop();
487 break;
488 case STATE_FETCH_PAC_SCRIPT_COMPLETE:
489 pac_file_fetcher_->Cancel();
490 break;
491 default:
492 break;
493 }
494
495 next_state_ = STATE_NONE;
496
497 // This is safe to call in any state.
498 if (dhcp_pac_file_fetcher_)
499 dhcp_pac_file_fetcher_->Cancel();
500
501 DCHECK(!resolve_request_);
502
503 DidComplete();
504 }
505
506 } // namespace net
507