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