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