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