| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h" | 
 |  | 
 | #include "base/synchronization/waitable_event.h" | 
 | #include "base/test/test_timeouts.h" | 
 | #include "base/threading/sequenced_worker_pool.h" | 
 | #include "base/timer/elapsed_timer.h" | 
 | #include "base/timer/timer.h" | 
 | #include "net/base/net_errors.h" | 
 | #include "net/base/test_completion_callback.h" | 
 | #include "net/proxy/mock_proxy_script_fetcher.h" | 
 | #include "net/proxy/proxy_script_fetcher_impl.h" | 
 | #include "net/test/spawned_test_server/spawned_test_server.h" | 
 | #include "net/url_request/url_request_test_util.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | namespace net { | 
 |  | 
 | namespace { | 
 |  | 
 | const char* const kPacUrl = "http://pacserver/script.pac"; | 
 |  | 
 | // In net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc there are a few | 
 | // tests that exercise DhcpProxyScriptAdapterFetcher end-to-end along with | 
 | // DhcpProxyScriptFetcherWin, i.e. it tests the end-to-end usage of Win32 | 
 | // APIs and the network.  In this file we test only by stubbing out | 
 | // functionality. | 
 |  | 
 | // Version of DhcpProxyScriptAdapterFetcher that mocks out dependencies | 
 | // to allow unit testing. | 
 | class MockDhcpProxyScriptAdapterFetcher | 
 |     : public DhcpProxyScriptAdapterFetcher { | 
 |  public: | 
 |   explicit MockDhcpProxyScriptAdapterFetcher( | 
 |       URLRequestContext* context, | 
 |       scoped_refptr<base::TaskRunner> task_runner) | 
 |       : DhcpProxyScriptAdapterFetcher(context, task_runner), | 
 |         dhcp_delay_(base::TimeDelta::FromMilliseconds(1)), | 
 |         timeout_(TestTimeouts::action_timeout()), | 
 |         configured_url_(kPacUrl), | 
 |         fetcher_delay_ms_(1), | 
 |         fetcher_result_(OK), | 
 |         pac_script_("bingo") { | 
 |   } | 
 |  | 
 |   void Cancel() { | 
 |     DhcpProxyScriptAdapterFetcher::Cancel(); | 
 |     fetcher_ = NULL; | 
 |   } | 
 |  | 
 |   virtual ProxyScriptFetcher* ImplCreateScriptFetcher() override { | 
 |     // We don't maintain ownership of the fetcher, it is transferred to | 
 |     // the caller. | 
 |     fetcher_ = new MockProxyScriptFetcher(); | 
 |     if (fetcher_delay_ms_ != -1) { | 
 |       fetcher_timer_.Start(FROM_HERE, | 
 |           base::TimeDelta::FromMilliseconds(fetcher_delay_ms_), | 
 |           this, &MockDhcpProxyScriptAdapterFetcher::OnFetcherTimer); | 
 |     } | 
 |     return fetcher_; | 
 |   } | 
 |  | 
 |   class DelayingDhcpQuery : public DhcpQuery { | 
 |    public: | 
 |     explicit DelayingDhcpQuery() | 
 |         : DhcpQuery(), | 
 |           test_finished_event_(true, false) { | 
 |     } | 
 |  | 
 |     std::string ImplGetPacURLFromDhcp( | 
 |         const std::string& adapter_name) override { | 
 |       base::ElapsedTimer timer; | 
 |       test_finished_event_.TimedWait(dhcp_delay_); | 
 |       return configured_url_; | 
 |     } | 
 |  | 
 |     base::WaitableEvent test_finished_event_; | 
 |     base::TimeDelta dhcp_delay_; | 
 |     std::string configured_url_; | 
 |   }; | 
 |  | 
 |   virtual DhcpQuery* ImplCreateDhcpQuery() override { | 
 |     dhcp_query_ = new DelayingDhcpQuery(); | 
 |     dhcp_query_->dhcp_delay_ = dhcp_delay_; | 
 |     dhcp_query_->configured_url_ = configured_url_; | 
 |     return dhcp_query_; | 
 |   } | 
 |  | 
 |   // Use a shorter timeout so tests can finish more quickly. | 
 |   virtual base::TimeDelta ImplGetTimeout() const override { | 
 |     return timeout_; | 
 |   } | 
 |  | 
 |   void OnFetcherTimer() { | 
 |     // Note that there is an assumption by this mock implementation that | 
 |     // DhcpProxyScriptAdapterFetcher::Fetch will call ImplCreateScriptFetcher | 
 |     // and call Fetch on the fetcher before the message loop is re-entered. | 
 |     // This holds true today, but if you hit this DCHECK the problem can | 
 |     // possibly be resolved by having a separate subclass of | 
 |     // MockProxyScriptFetcher that adds the delay internally (instead of | 
 |     // the simple approach currently used in ImplCreateScriptFetcher above). | 
 |     DCHECK(fetcher_ && fetcher_->has_pending_request()); | 
 |     fetcher_->NotifyFetchCompletion(fetcher_result_, pac_script_); | 
 |     fetcher_ = NULL; | 
 |   } | 
 |  | 
 |   bool IsWaitingForFetcher() const { | 
 |     return state() == STATE_WAIT_URL; | 
 |   } | 
 |  | 
 |   bool WasCancelled() const { | 
 |     return state() == STATE_CANCEL; | 
 |   } | 
 |  | 
 |   void FinishTest() { | 
 |     DCHECK(dhcp_query_); | 
 |     dhcp_query_->test_finished_event_.Signal(); | 
 |   } | 
 |  | 
 |   base::TimeDelta dhcp_delay_; | 
 |   base::TimeDelta timeout_; | 
 |   std::string configured_url_; | 
 |   int fetcher_delay_ms_; | 
 |   int fetcher_result_; | 
 |   std::string pac_script_; | 
 |   MockProxyScriptFetcher* fetcher_; | 
 |   base::OneShotTimer<MockDhcpProxyScriptAdapterFetcher> fetcher_timer_; | 
 |   scoped_refptr<DelayingDhcpQuery> dhcp_query_; | 
 | }; | 
 |  | 
 | class FetcherClient { | 
 |  public: | 
 |   FetcherClient() | 
 |       : url_request_context_(new TestURLRequestContext()), | 
 |         worker_pool_( | 
 |             new base::SequencedWorkerPool(4, "DhcpAdapterFetcherTest")), | 
 |         fetcher_(new MockDhcpProxyScriptAdapterFetcher( | 
 |             url_request_context_.get(), | 
 |             worker_pool_->GetTaskRunnerWithShutdownBehavior( | 
 |                 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN))) { | 
 |   } | 
 |  | 
 |   ~FetcherClient() { | 
 |     worker_pool_->Shutdown(); | 
 |   } | 
 |  | 
 |   void WaitForResult(int expected_error) { | 
 |     EXPECT_EQ(expected_error, callback_.WaitForResult()); | 
 |   } | 
 |  | 
 |   void RunTest() { | 
 |     fetcher_->Fetch("adapter name", callback_.callback()); | 
 |   } | 
 |  | 
 |   void FinishTestAllowCleanup() { | 
 |     fetcher_->FinishTest(); | 
 |     base::MessageLoop::current()->RunUntilIdle(); | 
 |   } | 
 |  | 
 |   TestCompletionCallback callback_; | 
 |   scoped_ptr<URLRequestContext> url_request_context_; | 
 |   scoped_refptr<base::SequencedWorkerPool> worker_pool_; | 
 |   scoped_ptr<MockDhcpProxyScriptAdapterFetcher> fetcher_; | 
 |   base::string16 pac_text_; | 
 | }; | 
 |  | 
 | TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLNotInDhcp) { | 
 |   FetcherClient client; | 
 |   client.fetcher_->configured_url_ = ""; | 
 |   client.RunTest(); | 
 |   client.WaitForResult(ERR_PAC_NOT_IN_DHCP); | 
 |   ASSERT_TRUE(client.fetcher_->DidFinish()); | 
 |   EXPECT_EQ(ERR_PAC_NOT_IN_DHCP, client.fetcher_->GetResult()); | 
 |   EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); | 
 | } | 
 |  | 
 | TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLInDhcp) { | 
 |   FetcherClient client; | 
 |   client.RunTest(); | 
 |   client.WaitForResult(OK); | 
 |   ASSERT_TRUE(client.fetcher_->DidFinish()); | 
 |   EXPECT_EQ(OK, client.fetcher_->GetResult()); | 
 |   EXPECT_EQ(base::string16(L"bingo"), client.fetcher_->GetPacScript()); | 
 |   EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); | 
 | } | 
 |  | 
 | TEST(DhcpProxyScriptAdapterFetcher, TimeoutDuringDhcp) { | 
 |   // Does a Fetch() with a long enough delay on accessing DHCP that the | 
 |   // fetcher should time out.  This is to test a case manual testing found, | 
 |   // where under certain circumstances (e.g. adapter enabled for DHCP and | 
 |   // needs to retrieve its configuration from DHCP, but no DHCP server | 
 |   // present on the network) accessing DHCP can take on the order of tens | 
 |   // of seconds. | 
 |   FetcherClient client; | 
 |   client.fetcher_->dhcp_delay_ = TestTimeouts::action_max_timeout(); | 
 |   client.fetcher_->timeout_ = base::TimeDelta::FromMilliseconds(25); | 
 |  | 
 |   base::ElapsedTimer timer; | 
 |   client.RunTest(); | 
 |   // An error different from this would be received if the timeout didn't | 
 |   // kick in. | 
 |   client.WaitForResult(ERR_TIMED_OUT); | 
 |  | 
 |   ASSERT_TRUE(client.fetcher_->DidFinish()); | 
 |   EXPECT_EQ(ERR_TIMED_OUT, client.fetcher_->GetResult()); | 
 |   EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); | 
 |   EXPECT_EQ(GURL(), client.fetcher_->GetPacURL()); | 
 |   client.FinishTestAllowCleanup(); | 
 | } | 
 |  | 
 | TEST(DhcpProxyScriptAdapterFetcher, CancelWhileDhcp) { | 
 |   FetcherClient client; | 
 |   client.RunTest(); | 
 |   client.fetcher_->Cancel(); | 
 |   base::MessageLoop::current()->RunUntilIdle(); | 
 |   ASSERT_FALSE(client.fetcher_->DidFinish()); | 
 |   ASSERT_TRUE(client.fetcher_->WasCancelled()); | 
 |   EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult()); | 
 |   EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); | 
 |   EXPECT_EQ(GURL(), client.fetcher_->GetPacURL()); | 
 |   client.FinishTestAllowCleanup(); | 
 | } | 
 |  | 
 | TEST(DhcpProxyScriptAdapterFetcher, CancelWhileFetcher) { | 
 |   FetcherClient client; | 
 |   // This causes the mock fetcher not to pretend the | 
 |   // fetcher finishes after a timeout. | 
 |   client.fetcher_->fetcher_delay_ms_ = -1; | 
 |   client.RunTest(); | 
 |   int max_loops = 4; | 
 |   while (!client.fetcher_->IsWaitingForFetcher() && max_loops--) { | 
 |     base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); | 
 |     base::MessageLoop::current()->RunUntilIdle(); | 
 |   } | 
 |   client.fetcher_->Cancel(); | 
 |   base::MessageLoop::current()->RunUntilIdle(); | 
 |   ASSERT_FALSE(client.fetcher_->DidFinish()); | 
 |   ASSERT_TRUE(client.fetcher_->WasCancelled()); | 
 |   EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult()); | 
 |   EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); | 
 |   // GetPacURL() still returns the URL fetched in this case. | 
 |   EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); | 
 |   client.FinishTestAllowCleanup(); | 
 | } | 
 |  | 
 | TEST(DhcpProxyScriptAdapterFetcher, CancelAtCompletion) { | 
 |   FetcherClient client; | 
 |   client.RunTest(); | 
 |   client.WaitForResult(OK); | 
 |   client.fetcher_->Cancel(); | 
 |   // Canceling after you're done should have no effect, so these | 
 |   // are identical expectations to the NormalCaseURLInDhcp test. | 
 |   ASSERT_TRUE(client.fetcher_->DidFinish()); | 
 |   EXPECT_EQ(OK, client.fetcher_->GetResult()); | 
 |   EXPECT_EQ(base::string16(L"bingo"), client.fetcher_->GetPacScript()); | 
 |   EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); | 
 |   client.FinishTestAllowCleanup(); | 
 | } | 
 |  | 
 | // Does a real fetch on a mock DHCP configuration. | 
 | class MockDhcpRealFetchProxyScriptAdapterFetcher | 
 |     : public MockDhcpProxyScriptAdapterFetcher { | 
 |  public: | 
 |   explicit MockDhcpRealFetchProxyScriptAdapterFetcher( | 
 |       URLRequestContext* context, | 
 |       scoped_refptr<base::TaskRunner> task_runner) | 
 |       : MockDhcpProxyScriptAdapterFetcher(context, task_runner), | 
 |         url_request_context_(context) { | 
 |   } | 
 |  | 
 |   // Returns a real proxy script fetcher. | 
 |   ProxyScriptFetcher* ImplCreateScriptFetcher() override { | 
 |     ProxyScriptFetcher* fetcher = | 
 |         new ProxyScriptFetcherImpl(url_request_context_); | 
 |     return fetcher; | 
 |   } | 
 |  | 
 |   URLRequestContext* url_request_context_; | 
 | }; | 
 |  | 
 | TEST(DhcpProxyScriptAdapterFetcher, MockDhcpRealFetch) { | 
 |   SpawnedTestServer test_server( | 
 |       SpawnedTestServer::TYPE_HTTP, | 
 |       SpawnedTestServer::kLocalhost, | 
 |       base::FilePath( | 
 |           FILE_PATH_LITERAL("net/data/proxy_script_fetcher_unittest"))); | 
 |   ASSERT_TRUE(test_server.Start()); | 
 |  | 
 |   GURL configured_url = test_server.GetURL("files/downloadable.pac"); | 
 |  | 
 |   FetcherClient client; | 
 |   TestURLRequestContext url_request_context; | 
 |   scoped_refptr<base::TaskRunner> runner = | 
 |       client.worker_pool_->GetTaskRunnerWithShutdownBehavior( | 
 |           base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); | 
 |   client.fetcher_.reset( | 
 |       new MockDhcpRealFetchProxyScriptAdapterFetcher( | 
 |           &url_request_context, runner)); | 
 |   client.fetcher_->configured_url_ = configured_url.spec(); | 
 |   client.RunTest(); | 
 |   client.WaitForResult(OK); | 
 |   ASSERT_TRUE(client.fetcher_->DidFinish()); | 
 |   EXPECT_EQ(OK, client.fetcher_->GetResult()); | 
 |   EXPECT_EQ(base::string16(L"-downloadable.pac-\n"), | 
 |             client.fetcher_->GetPacScript()); | 
 |   EXPECT_EQ(configured_url, | 
 |             client.fetcher_->GetPacURL()); | 
 | } | 
 |  | 
 | #define BASE_URL "http://corpserver/proxy.pac" | 
 |  | 
 | TEST(DhcpProxyScriptAdapterFetcher, SanitizeDhcpApiString) { | 
 |   const size_t kBaseUrlLen = strlen(BASE_URL); | 
 |  | 
 |   // Default case. | 
 |   EXPECT_EQ(BASE_URL, | 
 |             DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( | 
 |                 BASE_URL, kBaseUrlLen)); | 
 |  | 
 |   // Trailing \n and no null-termination. | 
 |   EXPECT_EQ(BASE_URL, | 
 |             DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( | 
 |                 BASE_URL "\nblablabla", kBaseUrlLen + 1)); | 
 |  | 
 |   // Embedded NULLs. | 
 |   EXPECT_EQ(BASE_URL, | 
 |             DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( | 
 |                 BASE_URL "\0foo\0blat", kBaseUrlLen + 9)); | 
 | } | 
 |  | 
 | #undef BASE_URL | 
 |  | 
 | }  // namespace | 
 |  | 
 | }  // namespace net |