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 }
221 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
222 return rv;
223 }
224
DoWait()225 int PacFileDecider::DoWait() {
226 next_state_ = STATE_WAIT_COMPLETE;
227
228 // If no waiting is required, continue on to the next state.
229 if (wait_delay_.ToInternalValue() == 0)
230 return OK;
231
232 // Otherwise wait the specified amount of time.
233 wait_timer_.Start(FROM_HERE, wait_delay_, this,
234 &PacFileDecider::OnWaitTimerFired);
235 net_log_.BeginEvent(NetLogEventType::PAC_FILE_DECIDER_WAIT);
236 return ERR_IO_PENDING;
237 }
238
DoWaitComplete(int result)239 int PacFileDecider::DoWaitComplete(int result) {
240 DCHECK_EQ(OK, result);
241 if (wait_delay_.ToInternalValue() != 0) {
242 net_log_.EndEventWithNetErrorCode(NetLogEventType::PAC_FILE_DECIDER_WAIT,
243 result);
244 }
245 if (quick_check_enabled_ && current_pac_source().type == PacSource::WPAD_DNS)
246 next_state_ = STATE_QUICK_CHECK;
247 else
248 next_state_ = GetStartState();
249 return OK;
250 }
251
DoQuickCheck()252 int PacFileDecider::DoQuickCheck() {
253 DCHECK(quick_check_enabled_);
254 if (!pac_file_fetcher_ || !pac_file_fetcher_->GetRequestContext() ||
255 !pac_file_fetcher_->GetRequestContext()->host_resolver()) {
256 // If we have no resolver, skip QuickCheck altogether.
257 next_state_ = GetStartState();
258 return OK;
259 }
260
261 std::string host = current_pac_source().url.host();
262
263 HostResolver::ResolveHostParameters parameters;
264 // We use HIGHEST here because proxy decision blocks doing any other requests.
265 parameters.initial_priority = HIGHEST;
266 // Only resolve via the system resolver for maximum compatibility with DNS
267 // suffix search paths, because for security, we are relying on suffix search
268 // paths rather than WPAD-standard DNS devolution.
269 parameters.source = HostResolverSource::SYSTEM;
270
271 // For most users, the WPAD DNS query will have no results. Allowing the query
272 // to go out via LLMNR or mDNS (which usually have no quick negative response)
273 // would therefore typically result in waiting the full timeout before
274 // `quick_check_timer_` fires. Given that a lot of Chrome requests could be
275 // blocked on completing these checks, it is better to avoid multicast
276 // resolution for WPAD.
277 // See crbug.com/1176970.
278 parameters.avoid_multicast_resolution = true;
279
280 HostResolver* host_resolver =
281 pac_file_fetcher_->GetRequestContext()->host_resolver();
282 resolve_request_ = host_resolver->CreateRequest(
283 HostPortPair(host, 80),
284 pac_file_fetcher_->isolation_info().network_anonymization_key(), net_log_,
285 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(FROM_HERE, base::Milliseconds(kQuickCheckDelayMs),
292 base::BindOnce(callback, ERR_NAME_NOT_RESOLVED));
293
294 return resolve_request_->Start(callback);
295 }
296
DoQuickCheckComplete(int result)297 int PacFileDecider::DoQuickCheckComplete(int result) {
298 DCHECK(quick_check_enabled_);
299 resolve_request_.reset();
300 quick_check_timer_.Stop();
301 if (result != OK)
302 return TryToFallbackPacSource(result);
303 next_state_ = GetStartState();
304 return result;
305 }
306
DoFetchPacScript()307 int PacFileDecider::DoFetchPacScript() {
308 DCHECK(fetch_pac_bytes_);
309
310 next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE;
311
312 const PacSource& pac_source = current_pac_source();
313
314 GURL effective_pac_url;
315 DetermineURL(pac_source, &effective_pac_url);
316
317 net_log_.BeginEvent(NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT, [&] {
318 return pac_source.NetLogParams(effective_pac_url);
319 });
320
321 if (pac_source.type == PacSource::WPAD_DHCP) {
322 if (!dhcp_pac_file_fetcher_) {
323 net_log_.AddEvent(NetLogEventType::PAC_FILE_DECIDER_HAS_NO_FETCHER);
324 return ERR_UNEXPECTED;
325 }
326
327 return dhcp_pac_file_fetcher_->Fetch(
328 &pac_script_,
329 base::BindOnce(&PacFileDecider::OnIOCompletion, base::Unretained(this)),
330 net_log_, NetworkTrafficAnnotationTag(traffic_annotation_));
331 }
332
333 if (!pac_file_fetcher_) {
334 net_log_.AddEvent(NetLogEventType::PAC_FILE_DECIDER_HAS_NO_FETCHER);
335 return ERR_UNEXPECTED;
336 }
337
338 return pac_file_fetcher_->Fetch(
339 effective_pac_url, &pac_script_,
340 base::BindOnce(&PacFileDecider::OnIOCompletion, base::Unretained(this)),
341 NetworkTrafficAnnotationTag(traffic_annotation_));
342 }
343
DoFetchPacScriptComplete(int result)344 int PacFileDecider::DoFetchPacScriptComplete(int result) {
345 DCHECK(fetch_pac_bytes_);
346
347 net_log_.EndEventWithNetErrorCode(
348 NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT, result);
349 if (result != OK)
350 return TryToFallbackPacSource(result);
351
352 next_state_ = STATE_VERIFY_PAC_SCRIPT;
353 return result;
354 }
355
DoVerifyPacScript()356 int PacFileDecider::DoVerifyPacScript() {
357 next_state_ = STATE_VERIFY_PAC_SCRIPT_COMPLETE;
358
359 // This is just a heuristic. Ideally we would try to parse the script.
360 if (fetch_pac_bytes_ && !LooksLikePacScript(pac_script_))
361 return ERR_PAC_SCRIPT_FAILED;
362
363 return OK;
364 }
365
DoVerifyPacScriptComplete(int result)366 int PacFileDecider::DoVerifyPacScriptComplete(int result) {
367 if (result != OK)
368 return TryToFallbackPacSource(result);
369
370 const PacSource& pac_source = current_pac_source();
371
372 // Extract the current script data.
373 script_data_.from_auto_detect = pac_source.type != PacSource::CUSTOM;
374 if (fetch_pac_bytes_) {
375 script_data_.data = PacFileData::FromUTF16(pac_script_);
376 } else {
377 script_data_.data = pac_source.type == PacSource::CUSTOM
378 ? PacFileData::FromURL(pac_source.url)
379 : PacFileData::ForAutoDetect();
380 }
381
382 // Let the caller know which automatic setting we ended up initializing the
383 // resolver for (there may have been multiple fallbacks to choose from.)
384 ProxyConfig config;
385 if (current_pac_source().type == PacSource::CUSTOM) {
386 config = ProxyConfig::CreateFromCustomPacURL(current_pac_source().url);
387 config.set_pac_mandatory(pac_mandatory_);
388 } else {
389 if (fetch_pac_bytes_) {
390 GURL auto_detected_url;
391
392 switch (current_pac_source().type) {
393 case PacSource::WPAD_DHCP:
394 auto_detected_url = dhcp_pac_file_fetcher_->GetPacURL();
395 break;
396
397 case PacSource::WPAD_DNS:
398 auto_detected_url = GURL(kWpadUrl);
399 break;
400
401 default:
402 NOTREACHED();
403 }
404
405 config = ProxyConfig::CreateFromCustomPacURL(auto_detected_url);
406 } else {
407 // The resolver does its own resolution so we cannot know the
408 // URL. Just do the best we can and state that the configuration
409 // is to auto-detect proxy settings.
410 config = ProxyConfig::CreateAutoDetect();
411 }
412 }
413
414 effective_config_ = ProxyConfigWithAnnotation(
415 config, net::NetworkTrafficAnnotationTag(traffic_annotation_));
416
417 return OK;
418 }
419
TryToFallbackPacSource(int error)420 int PacFileDecider::TryToFallbackPacSource(int error) {
421 DCHECK_LT(error, 0);
422
423 if (current_pac_source_index_ + 1 >= pac_sources_.size()) {
424 // Nothing left to fall back to.
425 return error;
426 }
427
428 // Advance to next URL in our list.
429 ++current_pac_source_index_;
430
431 net_log_.AddEvent(
432 NetLogEventType::PAC_FILE_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE);
433 if (quick_check_enabled_ && current_pac_source().type == PacSource::WPAD_DNS)
434 next_state_ = STATE_QUICK_CHECK;
435 else
436 next_state_ = GetStartState();
437
438 return OK;
439 }
440
GetStartState() const441 PacFileDecider::State PacFileDecider::GetStartState() const {
442 return fetch_pac_bytes_ ? STATE_FETCH_PAC_SCRIPT : STATE_VERIFY_PAC_SCRIPT;
443 }
444
DetermineURL(const PacSource & pac_source,GURL * effective_pac_url)445 void PacFileDecider::DetermineURL(const PacSource& pac_source,
446 GURL* effective_pac_url) {
447 DCHECK(effective_pac_url);
448
449 switch (pac_source.type) {
450 case PacSource::WPAD_DHCP:
451 break;
452 case PacSource::WPAD_DNS:
453 *effective_pac_url = GURL(kWpadUrl);
454 break;
455 case PacSource::CUSTOM:
456 *effective_pac_url = pac_source.url;
457 break;
458 }
459 }
460
current_pac_source() const461 const PacFileDecider::PacSource& PacFileDecider::current_pac_source() const {
462 DCHECK_LT(current_pac_source_index_, pac_sources_.size());
463 return pac_sources_[current_pac_source_index_];
464 }
465
OnWaitTimerFired()466 void PacFileDecider::OnWaitTimerFired() {
467 OnIOCompletion(OK);
468 }
469
DidComplete()470 void PacFileDecider::DidComplete() {
471 net_log_.EndEvent(NetLogEventType::PAC_FILE_DECIDER);
472 }
473
Cancel()474 void PacFileDecider::Cancel() {
475 DCHECK_NE(STATE_NONE, next_state_);
476
477 net_log_.AddEvent(NetLogEventType::CANCELLED);
478
479 switch (next_state_) {
480 case STATE_QUICK_CHECK_COMPLETE:
481 resolve_request_.reset();
482 break;
483 case STATE_WAIT_COMPLETE:
484 wait_timer_.Stop();
485 break;
486 case STATE_FETCH_PAC_SCRIPT_COMPLETE:
487 pac_file_fetcher_->Cancel();
488 break;
489 default:
490 break;
491 }
492
493 next_state_ = STATE_NONE;
494
495 // This is safe to call in any state.
496 if (dhcp_pac_file_fetcher_)
497 dhcp_pac_file_fetcher_->Cancel();
498
499 DCHECK(!resolve_request_);
500
501 DidComplete();
502 }
503
504 } // namespace net
505