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