xref: /aosp_15_r20/external/cronet/net/proxy_resolution/pac_file_decider_unittest.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 <memory>
6 #include <vector>
7 
8 #include "base/functional/bind.h"
9 #include "base/location.h"
10 #include "base/memory/raw_ptr.h"
11 #include "base/memory/weak_ptr.h"
12 #include "base/run_loop.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/task/single_thread_task_runner.h"
16 #include "base/test/task_environment.h"
17 #include "base/time/time.h"
18 #include "build/build_config.h"
19 #include "net/base/address_family.h"
20 #include "net/base/net_errors.h"
21 #include "net/base/test_completion_callback.h"
22 #include "net/dns/mock_host_resolver.h"
23 #include "net/log/net_log.h"
24 #include "net/log/net_log_event_type.h"
25 #include "net/log/test_net_log.h"
26 #include "net/log/test_net_log_util.h"
27 #include "net/proxy_resolution/dhcp_pac_file_fetcher.h"
28 #include "net/proxy_resolution/mock_pac_file_fetcher.h"
29 #include "net/proxy_resolution/pac_file_decider.h"
30 #include "net/proxy_resolution/pac_file_fetcher.h"
31 #include "net/proxy_resolution/proxy_config.h"
32 #include "net/proxy_resolution/proxy_resolver.h"
33 #include "net/test/gtest_util.h"
34 #include "net/test/test_with_task_environment.h"
35 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
36 #include "net/url_request/url_request_context.h"
37 #include "net/url_request/url_request_context_builder.h"
38 #include "net/url_request/url_request_test_util.h"
39 #include "testing/gmock/include/gmock/gmock.h"
40 #include "testing/gtest/include/gtest/gtest.h"
41 
42 using net::test::IsError;
43 using net::test::IsOk;
44 
45 namespace net {
46 namespace {
47 
48 enum Error {
49   kFailedDownloading = ERR_CONNECTION_CLOSED,
50   kFailedParsing = ERR_PAC_SCRIPT_FAILED,
51 };
52 
53 class Rules {
54  public:
55   struct Rule {
Rulenet::__anon5494e2b00111::Rules::Rule56     Rule(const GURL& url, int fetch_error, bool is_valid_script)
57         : url(url),
58           fetch_error(fetch_error),
59           is_valid_script(is_valid_script) {}
60 
textnet::__anon5494e2b00111::Rules::Rule61     std::u16string text() const {
62       if (is_valid_script)
63         return base::UTF8ToUTF16(url.spec() + "!FindProxyForURL");
64       if (fetch_error == OK)
65         return base::UTF8ToUTF16(url.spec() + "!invalid-script");
66       return std::u16string();
67     }
68 
69     GURL url;
70     int fetch_error;
71     bool is_valid_script;
72   };
73 
AddSuccessRule(const char * url)74   Rule AddSuccessRule(const char* url) {
75     Rule rule(GURL(url), OK /*fetch_error*/, true);
76     rules_.push_back(rule);
77     return rule;
78   }
79 
AddFailDownloadRule(const char * url)80   void AddFailDownloadRule(const char* url) {
81     rules_.push_back(
82         Rule(GURL(url), kFailedDownloading /*fetch_error*/, false));
83   }
84 
AddFailParsingRule(const char * url)85   void AddFailParsingRule(const char* url) {
86     rules_.push_back(Rule(GURL(url), OK /*fetch_error*/, false));
87   }
88 
GetRuleByUrl(const GURL & url) const89   const Rule& GetRuleByUrl(const GURL& url) const {
90     for (const auto& rule : rules_) {
91       if (rule.url == url)
92         return rule;
93     }
94     LOG(FATAL) << "Rule not found for " << url;
95   }
96 
97  private:
98   typedef std::vector<Rule> RuleList;
99   RuleList rules_;
100 };
101 
102 class RuleBasedPacFileFetcher : public PacFileFetcher {
103  public:
RuleBasedPacFileFetcher(const Rules * rules)104   explicit RuleBasedPacFileFetcher(const Rules* rules) : rules_(rules) {}
105 
SetRequestContext(URLRequestContext * context)106   virtual void SetRequestContext(URLRequestContext* context) {
107     request_context_ = context;
108   }
109 
110   // PacFileFetcher implementation.
Fetch(const GURL & url,std::u16string * text,CompletionOnceCallback callback,const NetworkTrafficAnnotationTag traffic_annotation)111   int Fetch(const GURL& url,
112             std::u16string* text,
113             CompletionOnceCallback callback,
114             const NetworkTrafficAnnotationTag traffic_annotation) override {
115     const Rules::Rule& rule = rules_->GetRuleByUrl(url);
116     int rv = rule.fetch_error;
117     EXPECT_NE(ERR_UNEXPECTED, rv);
118     if (rv == OK)
119       *text = rule.text();
120     return rv;
121   }
122 
Cancel()123   void Cancel() override {}
124 
OnShutdown()125   void OnShutdown() override { request_context_ = nullptr; }
126 
GetRequestContext() const127   URLRequestContext* GetRequestContext() const override {
128     return request_context_;
129   }
130 
131  private:
132   raw_ptr<const Rules> rules_;
133   raw_ptr<URLRequestContext, DanglingUntriaged> request_context_ = nullptr;
134 };
135 
136 // A mock retriever, returns asynchronously when CompleteRequests() is called.
137 class MockDhcpPacFileFetcher : public DhcpPacFileFetcher {
138  public:
139   MockDhcpPacFileFetcher();
140 
141   MockDhcpPacFileFetcher(const MockDhcpPacFileFetcher&) = delete;
142   MockDhcpPacFileFetcher& operator=(const MockDhcpPacFileFetcher&) = delete;
143 
144   ~MockDhcpPacFileFetcher() override;
145 
146   int Fetch(std::u16string* utf16_text,
147             CompletionOnceCallback callback,
148             const NetLogWithSource& net_log,
149             const NetworkTrafficAnnotationTag traffic_annotation) override;
150   void Cancel() override;
151   void OnShutdown() override;
152   const GURL& GetPacURL() const override;
153 
154   virtual void SetPacURL(const GURL& url);
155 
156   virtual void CompleteRequests(int result, const std::u16string& script);
157 
158  private:
159   CompletionOnceCallback callback_;
160   raw_ptr<std::u16string> utf16_text_;
161   GURL gurl_;
162 };
163 
164 MockDhcpPacFileFetcher::MockDhcpPacFileFetcher() = default;
165 
166 MockDhcpPacFileFetcher::~MockDhcpPacFileFetcher() = default;
167 
Fetch(std::u16string * utf16_text,CompletionOnceCallback callback,const NetLogWithSource & net_log,const NetworkTrafficAnnotationTag traffic_annotation)168 int MockDhcpPacFileFetcher::Fetch(
169     std::u16string* utf16_text,
170     CompletionOnceCallback callback,
171     const NetLogWithSource& net_log,
172     const NetworkTrafficAnnotationTag traffic_annotation) {
173   utf16_text_ = utf16_text;
174   callback_ = std::move(callback);
175   return ERR_IO_PENDING;
176 }
177 
Cancel()178 void MockDhcpPacFileFetcher::Cancel() {}
179 
OnShutdown()180 void MockDhcpPacFileFetcher::OnShutdown() {}
181 
GetPacURL() const182 const GURL& MockDhcpPacFileFetcher::GetPacURL() const {
183   return gurl_;
184 }
185 
SetPacURL(const GURL & url)186 void MockDhcpPacFileFetcher::SetPacURL(const GURL& url) {
187   gurl_ = url;
188 }
189 
CompleteRequests(int result,const std::u16string & script)190 void MockDhcpPacFileFetcher::CompleteRequests(int result,
191                                               const std::u16string& script) {
192   *utf16_text_ = script;
193   std::move(callback_).Run(result);
194 }
195 
196 // Succeed using custom PAC script.
TEST(PacFileDeciderTest,CustomPacSucceeds)197 TEST(PacFileDeciderTest, CustomPacSucceeds) {
198   Rules rules;
199   RuleBasedPacFileFetcher fetcher(&rules);
200   DoNothingDhcpPacFileFetcher dhcp_fetcher;
201 
202   ProxyConfig config;
203   config.set_pac_url(GURL("http://custom/proxy.pac"));
204 
205   Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
206 
207   TestCompletionCallback callback;
208   RecordingNetLogObserver observer;
209   PacFileDecider decider(&fetcher, &dhcp_fetcher, net::NetLog::Get());
210   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
211                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
212                             base::TimeDelta(), true, callback.callback()),
213               IsOk());
214   EXPECT_EQ(rule.text(), decider.script_data().data->utf16());
215   EXPECT_FALSE(decider.script_data().from_auto_detect);
216 
217   // Check the NetLog was filled correctly.
218   auto entries = observer.GetEntries();
219 
220   EXPECT_EQ(4u, entries.size());
221   EXPECT_TRUE(
222       LogContainsBeginEvent(entries, 0, NetLogEventType::PAC_FILE_DECIDER));
223   EXPECT_TRUE(LogContainsBeginEvent(
224       entries, 1, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
225   EXPECT_TRUE(LogContainsEndEvent(
226       entries, 2, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
227   EXPECT_TRUE(
228       LogContainsEndEvent(entries, 3, NetLogEventType::PAC_FILE_DECIDER));
229 
230   EXPECT_TRUE(decider.effective_config().value().has_pac_url());
231   EXPECT_EQ(config.pac_url(), decider.effective_config().value().pac_url());
232 }
233 
234 // Fail downloading the custom PAC script.
TEST(PacFileDeciderTest,CustomPacFails1)235 TEST(PacFileDeciderTest, CustomPacFails1) {
236   Rules rules;
237   RuleBasedPacFileFetcher fetcher(&rules);
238   DoNothingDhcpPacFileFetcher dhcp_fetcher;
239 
240   ProxyConfig config;
241   config.set_pac_url(GURL("http://custom/proxy.pac"));
242 
243   rules.AddFailDownloadRule("http://custom/proxy.pac");
244 
245   TestCompletionCallback callback;
246   RecordingNetLogObserver observer;
247   PacFileDecider decider(&fetcher, &dhcp_fetcher, net::NetLog::Get());
248   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
249                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
250                             base::TimeDelta(), true, callback.callback()),
251               IsError(kFailedDownloading));
252   EXPECT_FALSE(decider.script_data().data);
253 
254   // Check the NetLog was filled correctly.
255   auto entries = observer.GetEntries();
256 
257   EXPECT_EQ(4u, entries.size());
258   EXPECT_TRUE(
259       LogContainsBeginEvent(entries, 0, NetLogEventType::PAC_FILE_DECIDER));
260   EXPECT_TRUE(LogContainsBeginEvent(
261       entries, 1, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
262   EXPECT_TRUE(LogContainsEndEvent(
263       entries, 2, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
264   EXPECT_TRUE(
265       LogContainsEndEvent(entries, 3, NetLogEventType::PAC_FILE_DECIDER));
266 
267   EXPECT_FALSE(decider.effective_config().value().has_pac_url());
268 }
269 
270 // Fail parsing the custom PAC script.
TEST(PacFileDeciderTest,CustomPacFails2)271 TEST(PacFileDeciderTest, CustomPacFails2) {
272   Rules rules;
273   RuleBasedPacFileFetcher fetcher(&rules);
274   DoNothingDhcpPacFileFetcher dhcp_fetcher;
275 
276   ProxyConfig config;
277   config.set_pac_url(GURL("http://custom/proxy.pac"));
278 
279   rules.AddFailParsingRule("http://custom/proxy.pac");
280 
281   TestCompletionCallback callback;
282   PacFileDecider decider(&fetcher, &dhcp_fetcher, nullptr);
283   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
284                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
285                             base::TimeDelta(), true, callback.callback()),
286               IsError(kFailedParsing));
287   EXPECT_FALSE(decider.script_data().data);
288 }
289 
290 // Fail downloading the custom PAC script, because the fetcher was NULL.
TEST(PacFileDeciderTest,HasNullPacFileFetcher)291 TEST(PacFileDeciderTest, HasNullPacFileFetcher) {
292   Rules rules;
293   DoNothingDhcpPacFileFetcher dhcp_fetcher;
294 
295   ProxyConfig config;
296   config.set_pac_url(GURL("http://custom/proxy.pac"));
297 
298   TestCompletionCallback callback;
299   PacFileDecider decider(nullptr, &dhcp_fetcher, nullptr);
300   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
301                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
302                             base::TimeDelta(), true, callback.callback()),
303               IsError(ERR_UNEXPECTED));
304   EXPECT_FALSE(decider.script_data().data);
305 }
306 
307 // Succeeds in choosing autodetect (WPAD DNS).
TEST(PacFileDeciderTest,AutodetectSuccess)308 TEST(PacFileDeciderTest, AutodetectSuccess) {
309   Rules rules;
310   RuleBasedPacFileFetcher fetcher(&rules);
311   DoNothingDhcpPacFileFetcher dhcp_fetcher;
312 
313   ProxyConfig config;
314   config.set_auto_detect(true);
315 
316   Rules::Rule rule = rules.AddSuccessRule("http://wpad/wpad.dat");
317 
318   TestCompletionCallback callback;
319   PacFileDecider decider(&fetcher, &dhcp_fetcher, nullptr);
320   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
321                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
322                             base::TimeDelta(), true, callback.callback()),
323               IsOk());
324   EXPECT_EQ(rule.text(), decider.script_data().data->utf16());
325   EXPECT_TRUE(decider.script_data().from_auto_detect);
326 
327   EXPECT_TRUE(decider.effective_config().value().has_pac_url());
328   EXPECT_EQ(rule.url, decider.effective_config().value().pac_url());
329 }
330 
331 class PacFileDeciderQuickCheckTest : public ::testing::Test,
332                                      public WithTaskEnvironment {
333  public:
PacFileDeciderQuickCheckTest()334   PacFileDeciderQuickCheckTest()
335       : WithTaskEnvironment(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
336         rule_(rules_.AddSuccessRule("http://wpad/wpad.dat")),
337         fetcher_(&rules_) {
338     auto builder = CreateTestURLRequestContextBuilder();
339     builder->set_host_resolver(std::make_unique<MockHostResolver>());
340     request_context_ = builder->Build();
341   }
342 
SetUp()343   void SetUp() override {
344     fetcher_.SetRequestContext(request_context_.get());
345     config_.set_auto_detect(true);
346     decider_ =
347         std::make_unique<PacFileDecider>(&fetcher_, &dhcp_fetcher_, nullptr);
348   }
349 
StartDecider()350   int StartDecider() {
351     return decider_->Start(
352         ProxyConfigWithAnnotation(config_, TRAFFIC_ANNOTATION_FOR_TESTS),
353         base::TimeDelta(), true, callback_.callback());
354   }
355 
host_resolver()356   MockHostResolver& host_resolver() {
357     // This cast is safe because we set a MockHostResolver in the constructor.
358     return *static_cast<MockHostResolver*>(request_context_->host_resolver());
359   }
360 
361  protected:
362   Rules rules_;
363   Rules::Rule rule_;
364   TestCompletionCallback callback_;
365   RuleBasedPacFileFetcher fetcher_;
366   ProxyConfig config_;
367   DoNothingDhcpPacFileFetcher dhcp_fetcher_;
368   std::unique_ptr<PacFileDecider> decider_;
369 
370  private:
371   std::unique_ptr<URLRequestContext> request_context_;
372 };
373 
374 // Fails if a synchronous DNS lookup success for wpad causes QuickCheck to fail.
TEST_F(PacFileDeciderQuickCheckTest,SyncSuccess)375 TEST_F(PacFileDeciderQuickCheckTest, SyncSuccess) {
376   host_resolver().set_synchronous_mode(true);
377   host_resolver().rules()->AddRule("wpad", "1.2.3.4");
378 
379   EXPECT_THAT(StartDecider(), IsOk());
380   EXPECT_EQ(rule_.text(), decider_->script_data().data->utf16());
381   EXPECT_TRUE(decider_->script_data().from_auto_detect);
382 
383   EXPECT_TRUE(decider_->effective_config().value().has_pac_url());
384   EXPECT_EQ(rule_.url, decider_->effective_config().value().pac_url());
385 }
386 
387 // Fails if an asynchronous DNS lookup success for wpad causes QuickCheck to
388 // fail.
TEST_F(PacFileDeciderQuickCheckTest,AsyncSuccess)389 TEST_F(PacFileDeciderQuickCheckTest, AsyncSuccess) {
390   host_resolver().set_ondemand_mode(true);
391   host_resolver().rules()->AddRule("wpad", "1.2.3.4");
392 
393   EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING));
394   ASSERT_TRUE(host_resolver().has_pending_requests());
395 
396   // The DNS lookup should be pending, and be using the same
397   // NetworkAnonymizationKey as the PacFileFetcher, so wpad fetches can reuse
398   // the DNS lookup result from the wpad quick check, if it succeeds.
399   ASSERT_EQ(1u, host_resolver().last_id());
400   EXPECT_EQ(fetcher_.isolation_info().network_anonymization_key(),
401             host_resolver().request_network_anonymization_key(1));
402 
403   host_resolver().ResolveAllPending();
404   callback_.WaitForResult();
405   EXPECT_FALSE(host_resolver().has_pending_requests());
406   EXPECT_EQ(rule_.text(), decider_->script_data().data->utf16());
407   EXPECT_TRUE(decider_->script_data().from_auto_detect);
408   EXPECT_TRUE(decider_->effective_config().value().has_pac_url());
409   EXPECT_EQ(rule_.url, decider_->effective_config().value().pac_url());
410 }
411 
412 // Fails if an asynchronous DNS lookup failure (i.e. an NXDOMAIN) still causes
413 // PacFileDecider to yield a PAC URL.
TEST_F(PacFileDeciderQuickCheckTest,AsyncFail)414 TEST_F(PacFileDeciderQuickCheckTest, AsyncFail) {
415   host_resolver().set_ondemand_mode(true);
416   host_resolver().rules()->AddRule("wpad", ERR_NAME_NOT_RESOLVED);
417   EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING));
418   ASSERT_TRUE(host_resolver().has_pending_requests());
419 
420   // The DNS lookup should be pending, and be using the same
421   // NetworkAnonymizationKey as the PacFileFetcher, so wpad fetches can reuse
422   // the DNS lookup result from the wpad quick check, if it succeeds.
423   ASSERT_EQ(1u, host_resolver().last_id());
424   EXPECT_EQ(fetcher_.isolation_info().network_anonymization_key(),
425             host_resolver().request_network_anonymization_key(1));
426 
427   host_resolver().ResolveAllPending();
428   callback_.WaitForResult();
429   EXPECT_FALSE(decider_->effective_config().value().has_pac_url());
430 }
431 
432 // Fails if a DNS lookup timeout either causes PacFileDecider to yield a PAC
433 // URL or causes PacFileDecider not to cancel its pending resolution.
TEST_F(PacFileDeciderQuickCheckTest,AsyncTimeout)434 TEST_F(PacFileDeciderQuickCheckTest, AsyncTimeout) {
435   host_resolver().set_ondemand_mode(true);
436   EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING));
437   ASSERT_TRUE(host_resolver().has_pending_requests());
438   FastForwardUntilNoTasksRemain();
439   callback_.WaitForResult();
440   EXPECT_FALSE(host_resolver().has_pending_requests());
441   EXPECT_FALSE(decider_->effective_config().value().has_pac_url());
442 }
443 
444 // Fails if DHCP check doesn't take place before QuickCheck.
TEST_F(PacFileDeciderQuickCheckTest,QuickCheckInhibitsDhcp)445 TEST_F(PacFileDeciderQuickCheckTest, QuickCheckInhibitsDhcp) {
446   MockDhcpPacFileFetcher dhcp_fetcher;
447   const char* kPac = "function FindProxyForURL(u,h) { return \"DIRECT\"; }";
448   std::u16string pac_contents = base::UTF8ToUTF16(kPac);
449   GURL url("http://foobar/baz");
450   dhcp_fetcher.SetPacURL(url);
451   decider_ =
452       std::make_unique<PacFileDecider>(&fetcher_, &dhcp_fetcher, nullptr);
453   EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING));
454   dhcp_fetcher.CompleteRequests(OK, pac_contents);
455   EXPECT_TRUE(decider_->effective_config().value().has_pac_url());
456   EXPECT_EQ(decider_->effective_config().value().pac_url(), url);
457 }
458 
459 // Fails if QuickCheck still happens when disabled. To ensure QuickCheck is not
460 // happening, we add a synchronous failing resolver, which would ordinarily
461 // mean a QuickCheck failure, then ensure that our PacFileFetcher is still
462 // asked to fetch.
TEST_F(PacFileDeciderQuickCheckTest,QuickCheckDisabled)463 TEST_F(PacFileDeciderQuickCheckTest, QuickCheckDisabled) {
464   const char* kPac = "function FindProxyForURL(u,h) { return \"DIRECT\"; }";
465   host_resolver().set_synchronous_mode(true);
466   MockPacFileFetcher fetcher;
467   decider_ =
468       std::make_unique<PacFileDecider>(&fetcher, &dhcp_fetcher_, nullptr);
469   EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING));
470   EXPECT_TRUE(fetcher.has_pending_request());
471   fetcher.NotifyFetchCompletion(OK, kPac);
472 }
473 
TEST_F(PacFileDeciderQuickCheckTest,ExplicitPacUrl)474 TEST_F(PacFileDeciderQuickCheckTest, ExplicitPacUrl) {
475   const char* kCustomUrl = "http://custom/proxy.pac";
476   config_.set_pac_url(GURL(kCustomUrl));
477   Rules::Rule rule = rules_.AddSuccessRule(kCustomUrl);
478   host_resolver().rules()->AddRule("wpad", ERR_NAME_NOT_RESOLVED);
479   host_resolver().rules()->AddRule("custom", "1.2.3.4");
480   EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING));
481   callback_.WaitForResult();
482   EXPECT_TRUE(decider_->effective_config().value().has_pac_url());
483   EXPECT_EQ(rule.url, decider_->effective_config().value().pac_url());
484 }
485 
TEST_F(PacFileDeciderQuickCheckTest,ShutdownDuringResolve)486 TEST_F(PacFileDeciderQuickCheckTest, ShutdownDuringResolve) {
487   host_resolver().set_ondemand_mode(true);
488 
489   EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING));
490   EXPECT_TRUE(host_resolver().has_pending_requests());
491 
492   decider_->OnShutdown();
493   EXPECT_FALSE(host_resolver().has_pending_requests());
494   base::RunLoop().RunUntilIdle();
495   EXPECT_FALSE(callback_.have_result());
496 }
497 
498 // Regression test for http://crbug.com/409698.
499 // This test lets the state machine get into state QUICK_CHECK_COMPLETE, then
500 // destroys the decider, causing a cancel.
TEST_F(PacFileDeciderQuickCheckTest,CancelPartway)501 TEST_F(PacFileDeciderQuickCheckTest, CancelPartway) {
502   host_resolver().set_ondemand_mode(true);
503   EXPECT_THAT(StartDecider(), IsError(ERR_IO_PENDING));
504   decider_.reset(nullptr);
505 }
506 
507 // Fails at WPAD (downloading), but succeeds in choosing the custom PAC.
TEST(PacFileDeciderTest,AutodetectFailCustomSuccess1)508 TEST(PacFileDeciderTest, AutodetectFailCustomSuccess1) {
509   Rules rules;
510   RuleBasedPacFileFetcher fetcher(&rules);
511   DoNothingDhcpPacFileFetcher dhcp_fetcher;
512 
513   ProxyConfig config;
514   config.set_auto_detect(true);
515   config.set_pac_url(GURL("http://custom/proxy.pac"));
516 
517   rules.AddFailDownloadRule("http://wpad/wpad.dat");
518   Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
519 
520   TestCompletionCallback callback;
521   PacFileDecider decider(&fetcher, &dhcp_fetcher, nullptr);
522   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
523                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
524                             base::TimeDelta(), true, callback.callback()),
525               IsOk());
526   EXPECT_EQ(rule.text(), decider.script_data().data->utf16());
527   EXPECT_FALSE(decider.script_data().from_auto_detect);
528 
529   EXPECT_TRUE(decider.effective_config().value().has_pac_url());
530   EXPECT_EQ(rule.url, decider.effective_config().value().pac_url());
531 }
532 
533 // Fails at WPAD (no DHCP config, DNS PAC fails parsing), but succeeds in
534 // choosing the custom PAC.
TEST(PacFileDeciderTest,AutodetectFailCustomSuccess2)535 TEST(PacFileDeciderTest, AutodetectFailCustomSuccess2) {
536   Rules rules;
537   RuleBasedPacFileFetcher fetcher(&rules);
538   DoNothingDhcpPacFileFetcher dhcp_fetcher;
539 
540   ProxyConfig config;
541   config.set_auto_detect(true);
542   config.set_pac_url(GURL("http://custom/proxy.pac"));
543   config.proxy_rules().ParseFromString("unused-manual-proxy:99");
544 
545   rules.AddFailParsingRule("http://wpad/wpad.dat");
546   Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
547 
548   TestCompletionCallback callback;
549   RecordingNetLogObserver observer;
550 
551   PacFileDecider decider(&fetcher, &dhcp_fetcher, net::NetLog::Get());
552   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
553                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
554                             base::TimeDelta(), true, callback.callback()),
555               IsOk());
556   EXPECT_EQ(rule.text(), decider.script_data().data->utf16());
557   EXPECT_FALSE(decider.script_data().from_auto_detect);
558 
559   // Verify that the effective configuration no longer contains auto detect or
560   // any of the manual settings.
561   EXPECT_TRUE(decider.effective_config().value().Equals(
562       ProxyConfig::CreateFromCustomPacURL(GURL("http://custom/proxy.pac"))));
563 
564   // Check the NetLog was filled correctly.
565   // (Note that various states are repeated since both WPAD and custom
566   // PAC scripts are tried).
567   auto entries = observer.GetEntries();
568 
569   EXPECT_EQ(10u, entries.size());
570   EXPECT_TRUE(
571       LogContainsBeginEvent(entries, 0, NetLogEventType::PAC_FILE_DECIDER));
572   // This is the DHCP phase, which fails fetching rather than parsing, so
573   // there is no pair of SET_PAC_SCRIPT events.
574   EXPECT_TRUE(LogContainsBeginEvent(
575       entries, 1, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
576   EXPECT_TRUE(LogContainsEndEvent(
577       entries, 2, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
578   EXPECT_TRUE(LogContainsEvent(
579       entries, 3,
580       NetLogEventType::PAC_FILE_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE,
581       NetLogEventPhase::NONE));
582   // This is the DNS phase, which attempts a fetch but fails.
583   EXPECT_TRUE(LogContainsBeginEvent(
584       entries, 4, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
585   EXPECT_TRUE(LogContainsEndEvent(
586       entries, 5, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
587   EXPECT_TRUE(LogContainsEvent(
588       entries, 6,
589       NetLogEventType::PAC_FILE_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE,
590       NetLogEventPhase::NONE));
591   // Finally, the custom PAC URL phase.
592   EXPECT_TRUE(LogContainsBeginEvent(
593       entries, 7, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
594   EXPECT_TRUE(LogContainsEndEvent(
595       entries, 8, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
596   EXPECT_TRUE(
597       LogContainsEndEvent(entries, 9, NetLogEventType::PAC_FILE_DECIDER));
598 }
599 
600 // Fails at WPAD (downloading), and fails at custom PAC (downloading).
TEST(PacFileDeciderTest,AutodetectFailCustomFails1)601 TEST(PacFileDeciderTest, AutodetectFailCustomFails1) {
602   Rules rules;
603   RuleBasedPacFileFetcher fetcher(&rules);
604   DoNothingDhcpPacFileFetcher dhcp_fetcher;
605 
606   ProxyConfig config;
607   config.set_auto_detect(true);
608   config.set_pac_url(GURL("http://custom/proxy.pac"));
609 
610   rules.AddFailDownloadRule("http://wpad/wpad.dat");
611   rules.AddFailDownloadRule("http://custom/proxy.pac");
612 
613   TestCompletionCallback callback;
614   PacFileDecider decider(&fetcher, &dhcp_fetcher, nullptr);
615   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
616                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
617                             base::TimeDelta(), true, callback.callback()),
618               IsError(kFailedDownloading));
619   EXPECT_FALSE(decider.script_data().data);
620 }
621 
622 // Fails at WPAD (downloading), and fails at custom PAC (parsing).
TEST(PacFileDeciderTest,AutodetectFailCustomFails2)623 TEST(PacFileDeciderTest, AutodetectFailCustomFails2) {
624   Rules rules;
625   RuleBasedPacFileFetcher fetcher(&rules);
626   DoNothingDhcpPacFileFetcher dhcp_fetcher;
627 
628   ProxyConfig config;
629   config.set_auto_detect(true);
630   config.set_pac_url(GURL("http://custom/proxy.pac"));
631 
632   rules.AddFailDownloadRule("http://wpad/wpad.dat");
633   rules.AddFailParsingRule("http://custom/proxy.pac");
634 
635   TestCompletionCallback callback;
636   PacFileDecider decider(&fetcher, &dhcp_fetcher, nullptr);
637   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
638                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
639                             base::TimeDelta(), true, callback.callback()),
640               IsError(kFailedParsing));
641   EXPECT_FALSE(decider.script_data().data);
642 }
643 
644 // This is a copy-paste of CustomPacFails1, with the exception that we give it
645 // a 1 millisecond delay. This means it will now complete asynchronously.
646 // Moreover, we test the NetLog to make sure it logged the pause.
TEST(PacFileDeciderTest,CustomPacFails1_WithPositiveDelay)647 TEST(PacFileDeciderTest, CustomPacFails1_WithPositiveDelay) {
648   base::test::TaskEnvironment task_environment;
649 
650   Rules rules;
651   RuleBasedPacFileFetcher fetcher(&rules);
652   DoNothingDhcpPacFileFetcher dhcp_fetcher;
653 
654   ProxyConfig config;
655   config.set_pac_url(GURL("http://custom/proxy.pac"));
656 
657   rules.AddFailDownloadRule("http://custom/proxy.pac");
658 
659   TestCompletionCallback callback;
660 
661   RecordingNetLogObserver observer;
662   PacFileDecider decider(&fetcher, &dhcp_fetcher, net::NetLog::Get());
663   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
664                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
665                             base::Milliseconds(1), true, callback.callback()),
666               IsError(ERR_IO_PENDING));
667 
668   EXPECT_THAT(callback.WaitForResult(), IsError(kFailedDownloading));
669   EXPECT_FALSE(decider.script_data().data);
670 
671   // Check the NetLog was filled correctly.
672   auto entries = observer.GetEntries();
673 
674   EXPECT_EQ(6u, entries.size());
675   EXPECT_TRUE(
676       LogContainsBeginEvent(entries, 0, NetLogEventType::PAC_FILE_DECIDER));
677   EXPECT_TRUE(LogContainsBeginEvent(entries, 1,
678                                     NetLogEventType::PAC_FILE_DECIDER_WAIT));
679   EXPECT_TRUE(
680       LogContainsEndEvent(entries, 2, NetLogEventType::PAC_FILE_DECIDER_WAIT));
681   EXPECT_TRUE(LogContainsBeginEvent(
682       entries, 3, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
683   EXPECT_TRUE(LogContainsEndEvent(
684       entries, 4, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
685   EXPECT_TRUE(
686       LogContainsEndEvent(entries, 5, NetLogEventType::PAC_FILE_DECIDER));
687 }
688 
689 // This is a copy-paste of CustomPacFails1, with the exception that we give it
690 // a -5 second delay instead of a 0 ms delay. This change should have no effect
691 // so the rest of the test is unchanged.
TEST(PacFileDeciderTest,CustomPacFails1_WithNegativeDelay)692 TEST(PacFileDeciderTest, CustomPacFails1_WithNegativeDelay) {
693   Rules rules;
694   RuleBasedPacFileFetcher fetcher(&rules);
695   DoNothingDhcpPacFileFetcher dhcp_fetcher;
696 
697   ProxyConfig config;
698   config.set_pac_url(GURL("http://custom/proxy.pac"));
699 
700   rules.AddFailDownloadRule("http://custom/proxy.pac");
701 
702   TestCompletionCallback callback;
703   RecordingNetLogObserver observer;
704   PacFileDecider decider(&fetcher, &dhcp_fetcher, net::NetLog::Get());
705   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
706                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
707                             base::Seconds(-5), true, callback.callback()),
708               IsError(kFailedDownloading));
709   EXPECT_FALSE(decider.script_data().data);
710 
711   // Check the NetLog was filled correctly.
712   auto entries = observer.GetEntries();
713 
714   EXPECT_EQ(4u, entries.size());
715   EXPECT_TRUE(
716       LogContainsBeginEvent(entries, 0, NetLogEventType::PAC_FILE_DECIDER));
717   EXPECT_TRUE(LogContainsBeginEvent(
718       entries, 1, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
719   EXPECT_TRUE(LogContainsEndEvent(
720       entries, 2, NetLogEventType::PAC_FILE_DECIDER_FETCH_PAC_SCRIPT));
721   EXPECT_TRUE(
722       LogContainsEndEvent(entries, 3, NetLogEventType::PAC_FILE_DECIDER));
723 }
724 
725 class SynchronousSuccessDhcpFetcher : public DhcpPacFileFetcher {
726  public:
SynchronousSuccessDhcpFetcher(const std::u16string & expected_text)727   explicit SynchronousSuccessDhcpFetcher(const std::u16string& expected_text)
728       : gurl_("http://dhcppac/"), expected_text_(expected_text) {}
729 
730   SynchronousSuccessDhcpFetcher(const SynchronousSuccessDhcpFetcher&) = delete;
731   SynchronousSuccessDhcpFetcher& operator=(
732       const SynchronousSuccessDhcpFetcher&) = delete;
733 
Fetch(std::u16string * utf16_text,CompletionOnceCallback callback,const NetLogWithSource & net_log,const NetworkTrafficAnnotationTag traffic_annotation)734   int Fetch(std::u16string* utf16_text,
735             CompletionOnceCallback callback,
736             const NetLogWithSource& net_log,
737             const NetworkTrafficAnnotationTag traffic_annotation) override {
738     *utf16_text = expected_text_;
739     return OK;
740   }
741 
Cancel()742   void Cancel() override {}
743 
OnShutdown()744   void OnShutdown() override {}
745 
GetPacURL() const746   const GURL& GetPacURL() const override { return gurl_; }
747 
expected_text() const748   const std::u16string& expected_text() const { return expected_text_; }
749 
750  private:
751   GURL gurl_;
752   std::u16string expected_text_;
753 };
754 
755 // All of the tests above that use PacFileDecider have tested
756 // failure to fetch a PAC file via DHCP configuration, so we now test
757 // success at downloading and parsing, and then success at downloading,
758 // failure at parsing.
759 
TEST(PacFileDeciderTest,AutodetectDhcpSuccess)760 TEST(PacFileDeciderTest, AutodetectDhcpSuccess) {
761   Rules rules;
762   RuleBasedPacFileFetcher fetcher(&rules);
763   SynchronousSuccessDhcpFetcher dhcp_fetcher(u"http://bingo/!FindProxyForURL");
764 
765   ProxyConfig config;
766   config.set_auto_detect(true);
767 
768   rules.AddSuccessRule("http://bingo/");
769   rules.AddFailDownloadRule("http://wpad/wpad.dat");
770 
771   TestCompletionCallback callback;
772   PacFileDecider decider(&fetcher, &dhcp_fetcher, nullptr);
773   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
774                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
775                             base::TimeDelta(), true, callback.callback()),
776               IsOk());
777   EXPECT_EQ(dhcp_fetcher.expected_text(), decider.script_data().data->utf16());
778   EXPECT_TRUE(decider.script_data().from_auto_detect);
779 
780   EXPECT_TRUE(decider.effective_config().value().has_pac_url());
781   EXPECT_EQ(GURL("http://dhcppac/"),
782             decider.effective_config().value().pac_url());
783 }
784 
TEST(PacFileDeciderTest,AutodetectDhcpFailParse)785 TEST(PacFileDeciderTest, AutodetectDhcpFailParse) {
786   Rules rules;
787   RuleBasedPacFileFetcher fetcher(&rules);
788   SynchronousSuccessDhcpFetcher dhcp_fetcher(u"http://bingo/!invalid-script");
789 
790   ProxyConfig config;
791   config.set_auto_detect(true);
792 
793   rules.AddFailParsingRule("http://bingo/");
794   rules.AddFailDownloadRule("http://wpad/wpad.dat");
795 
796   TestCompletionCallback callback;
797   PacFileDecider decider(&fetcher, &dhcp_fetcher, nullptr);
798   // Since there is fallback to DNS-based WPAD, the final error will be that
799   // it failed downloading, not that it failed parsing.
800   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
801                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
802                             base::TimeDelta(), true, callback.callback()),
803               IsError(kFailedDownloading));
804   EXPECT_FALSE(decider.script_data().data);
805 
806   EXPECT_FALSE(decider.effective_config().value().has_pac_url());
807 }
808 
809 class AsyncFailDhcpFetcher final : public DhcpPacFileFetcher {
810  public:
811   AsyncFailDhcpFetcher() = default;
812   ~AsyncFailDhcpFetcher() override = default;
813 
Fetch(std::u16string * utf16_text,CompletionOnceCallback callback,const NetLogWithSource & net_log,const NetworkTrafficAnnotationTag traffic_annotation)814   int Fetch(std::u16string* utf16_text,
815             CompletionOnceCallback callback,
816             const NetLogWithSource& net_log,
817             const NetworkTrafficAnnotationTag traffic_annotation) override {
818     callback_ = std::move(callback);
819     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
820         FROM_HERE, base::BindOnce(&AsyncFailDhcpFetcher::CallbackWithFailure,
821                                   weak_ptr_factory_.GetWeakPtr()));
822     return ERR_IO_PENDING;
823   }
824 
Cancel()825   void Cancel() override { callback_.Reset(); }
826 
OnShutdown()827   void OnShutdown() override {}
828 
GetPacURL() const829   const GURL& GetPacURL() const override { return dummy_gurl_; }
830 
CallbackWithFailure()831   void CallbackWithFailure() {
832     if (!callback_.is_null())
833       std::move(callback_).Run(ERR_PAC_NOT_IN_DHCP);
834   }
835 
836  private:
837   GURL dummy_gurl_;
838   CompletionOnceCallback callback_;
839   base::WeakPtrFactory<AsyncFailDhcpFetcher> weak_ptr_factory_{this};
840 };
841 
TEST(PacFileDeciderTest,DhcpCancelledByDestructor)842 TEST(PacFileDeciderTest, DhcpCancelledByDestructor) {
843   // This regression test would crash before
844   // http://codereview.chromium.org/7044058/
845   // Thus, we don't care much about actual results (hence no EXPECT or ASSERT
846   // macros below), just that it doesn't crash.
847   base::test::TaskEnvironment task_environment;
848 
849   Rules rules;
850   RuleBasedPacFileFetcher fetcher(&rules);
851 
852   auto dhcp_fetcher = std::make_unique<AsyncFailDhcpFetcher>();
853 
854   ProxyConfig config;
855   config.set_auto_detect(true);
856   rules.AddFailDownloadRule("http://wpad/wpad.dat");
857 
858   TestCompletionCallback callback;
859 
860   // Scope so PacFileDecider gets destroyed early.
861   {
862     PacFileDecider decider(&fetcher, dhcp_fetcher.get(), nullptr);
863     decider.Start(
864         ProxyConfigWithAnnotation(config, TRAFFIC_ANNOTATION_FOR_TESTS),
865         base::TimeDelta(), true, callback.callback());
866   }
867 
868   // Run the message loop to let the DHCP fetch complete and post the results
869   // back. Before the fix linked to above, this would try to invoke on
870   // the callback object provided by PacFileDecider after it was
871   // no longer valid.
872   base::RunLoop().RunUntilIdle();
873 }
874 
875 }  // namespace
876 }  // namespace net
877