• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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