xref: /aosp_15_r20/external/cronet/net/proxy_resolution/pac_file_decider.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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