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