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 "net/proxy_resolution/win/dhcp_pac_file_adapter_fetcher_win.h"
6
7 #include <memory>
8
9 #include "base/memory/ptr_util.h"
10 #include "base/memory/raw_ptr.h"
11 #include "base/run_loop.h"
12 #include "base/synchronization/waitable_event.h"
13 #include "base/task/thread_pool.h"
14 #include "base/test/task_environment.h"
15 #include "base/test/test_timeouts.h"
16 #include "base/threading/thread_restrictions.h"
17 #include "base/time/time.h"
18 #include "base/timer/elapsed_timer.h"
19 #include "base/timer/timer.h"
20 #include "net/base/net_errors.h"
21 #include "net/base/test_completion_callback.h"
22 #include "net/proxy_resolution/mock_pac_file_fetcher.h"
23 #include "net/proxy_resolution/pac_file_fetcher_impl.h"
24 #include "net/test/embedded_test_server/embedded_test_server.h"
25 #include "net/test/gtest_util.h"
26 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
27 #include "net/url_request/url_request_context.h"
28 #include "net/url_request/url_request_context_builder.h"
29 #include "net/url_request/url_request_test_util.h"
30 #include "testing/gmock/include/gmock/gmock.h"
31 #include "testing/gtest/include/gtest/gtest.h"
32
33 using net::test::IsError;
34 using net::test::IsOk;
35
36 namespace net {
37
38 namespace {
39
40 const char kPacUrl[] = "http://pacserver/script.pac";
41
42 // In dhcp_pac_file_fetcher_win_unittest.cc there are
43 // a few tests that exercise DhcpPacFileAdapterFetcher end-to-end along with
44 // DhcpPacFileFetcherWin, i.e. it tests the end-to-end usage of Win32 APIs
45 // and the network. In this file we test only by stubbing out functionality.
46
47 // Version of DhcpPacFileAdapterFetcher that mocks out dependencies
48 // to allow unit testing.
49 class MockDhcpPacFileAdapterFetcher : public DhcpPacFileAdapterFetcher {
50 public:
MockDhcpPacFileAdapterFetcher(URLRequestContext * context,scoped_refptr<base::TaskRunner> task_runner)51 explicit MockDhcpPacFileAdapterFetcher(
52 URLRequestContext* context,
53 scoped_refptr<base::TaskRunner> task_runner)
54 : DhcpPacFileAdapterFetcher(context, task_runner),
55 timeout_(TestTimeouts::action_timeout()),
56 pac_script_("bingo") {}
57
Cancel()58 void Cancel() override {
59 DhcpPacFileAdapterFetcher::Cancel();
60 fetcher_ = nullptr;
61 }
62
ImplCreateScriptFetcher()63 std::unique_ptr<PacFileFetcher> ImplCreateScriptFetcher() override {
64 // We don't maintain ownership of the fetcher, it is transferred to
65 // the caller.
66 auto fetcher = std::make_unique<MockPacFileFetcher>();
67 fetcher_ = fetcher.get();
68 if (fetcher_delay_ms_ != -1) {
69 fetcher_timer_.Start(FROM_HERE, base::Milliseconds(fetcher_delay_ms_),
70 this,
71 &MockDhcpPacFileAdapterFetcher::OnFetcherTimer);
72 }
73 return fetcher;
74 }
75
76 class DelayingDhcpQuery : public DhcpQuery {
77 public:
DelayingDhcpQuery()78 explicit DelayingDhcpQuery()
79 : DhcpQuery(),
80 test_finished_event_(
81 base::WaitableEvent::ResetPolicy::MANUAL,
82 base::WaitableEvent::InitialState::NOT_SIGNALED) {}
83
ImplGetPacURLFromDhcp(const std::string & adapter_name)84 std::string ImplGetPacURLFromDhcp(
85 const std::string& adapter_name) override {
86 base::ElapsedTimer timer;
87 {
88 base::ScopedAllowBaseSyncPrimitivesForTesting
89 scoped_allow_base_sync_primitives;
90 test_finished_event_.TimedWait(dhcp_delay_);
91 }
92 return configured_url_;
93 }
94
95 base::WaitableEvent test_finished_event_;
96 base::TimeDelta dhcp_delay_;
97 std::string configured_url_;
98
99 private:
~DelayingDhcpQuery()100 ~DelayingDhcpQuery() override {}
101 };
102
ImplCreateDhcpQuery()103 scoped_refptr<DhcpQuery> ImplCreateDhcpQuery() override {
104 dhcp_query_ = base::MakeRefCounted<DelayingDhcpQuery>();
105 dhcp_query_->dhcp_delay_ = dhcp_delay_;
106 dhcp_query_->configured_url_ = configured_url_;
107 return dhcp_query_;
108 }
109
110 // Use a shorter timeout so tests can finish more quickly.
ImplGetTimeout() const111 base::TimeDelta ImplGetTimeout() const override { return timeout_; }
112
OnFetcherTimer()113 void OnFetcherTimer() {
114 // Note that there is an assumption by this mock implementation that
115 // DhcpPacFileAdapterFetcher::Fetch will call ImplCreateScriptFetcher
116 // and call Fetch on the fetcher before the message loop is re-entered.
117 // This holds true today, but if you hit this DCHECK the problem can
118 // possibly be resolved by having a separate subclass of
119 // MockPacFileFetcher that adds the delay internally (instead of
120 // the simple approach currently used in ImplCreateScriptFetcher above).
121 DCHECK(fetcher_ && fetcher_->has_pending_request());
122 fetcher_->NotifyFetchCompletion(fetcher_result_, pac_script_);
123 fetcher_ = nullptr;
124 }
125
IsWaitingForFetcher() const126 bool IsWaitingForFetcher() const {
127 return state() == STATE_WAIT_URL;
128 }
129
WasCancelled() const130 bool WasCancelled() const {
131 return state() == STATE_CANCEL;
132 }
133
FinishTest()134 void FinishTest() {
135 DCHECK(dhcp_query_.get());
136 dhcp_query_->test_finished_event_.Signal();
137 }
138
139 base::TimeDelta dhcp_delay_ = base::Milliseconds(1);
140 base::TimeDelta timeout_;
141 std::string configured_url_{kPacUrl};
142 int fetcher_delay_ms_ = 1;
143 int fetcher_result_ = OK;
144 std::string pac_script_;
145 raw_ptr<MockPacFileFetcher, DanglingUntriaged> fetcher_;
146 base::OneShotTimer fetcher_timer_;
147 scoped_refptr<DelayingDhcpQuery> dhcp_query_;
148 };
149
150 class FetcherClient {
151 public:
FetcherClient()152 FetcherClient()
153 : url_request_context_(CreateTestURLRequestContextBuilder()->Build()),
154 fetcher_(std::make_unique<MockDhcpPacFileAdapterFetcher>(
155 url_request_context_.get(),
156 base::ThreadPool::CreateSequencedTaskRunner(
157 {base::MayBlock(),
158 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}))) {}
159
WaitForResult(int expected_error)160 void WaitForResult(int expected_error) {
161 EXPECT_EQ(expected_error, callback_.WaitForResult());
162 }
163
RunTest()164 void RunTest() {
165 fetcher_->Fetch("adapter name", callback_.callback(),
166 TRAFFIC_ANNOTATION_FOR_TESTS);
167 }
168
FinishTestAllowCleanup()169 void FinishTestAllowCleanup() {
170 fetcher_->FinishTest();
171 base::RunLoop().RunUntilIdle();
172 }
173
174 TestCompletionCallback callback_;
175 std::unique_ptr<URLRequestContext> url_request_context_;
176 std::unique_ptr<MockDhcpPacFileAdapterFetcher> fetcher_;
177 std::u16string pac_text_;
178 };
179
TEST(DhcpPacFileAdapterFetcher,NormalCaseURLNotInDhcp)180 TEST(DhcpPacFileAdapterFetcher, NormalCaseURLNotInDhcp) {
181 base::test::TaskEnvironment task_environment;
182
183 FetcherClient client;
184 client.fetcher_->configured_url_ = "";
185 client.RunTest();
186 client.WaitForResult(ERR_PAC_NOT_IN_DHCP);
187 ASSERT_TRUE(client.fetcher_->DidFinish());
188 EXPECT_THAT(client.fetcher_->GetResult(), IsError(ERR_PAC_NOT_IN_DHCP));
189 EXPECT_EQ(std::u16string(), client.fetcher_->GetPacScript());
190 }
191
TEST(DhcpPacFileAdapterFetcher,NormalCaseURLInDhcp)192 TEST(DhcpPacFileAdapterFetcher, NormalCaseURLInDhcp) {
193 base::test::TaskEnvironment task_environment;
194
195 FetcherClient client;
196 client.RunTest();
197 client.WaitForResult(OK);
198 ASSERT_TRUE(client.fetcher_->DidFinish());
199 EXPECT_THAT(client.fetcher_->GetResult(), IsOk());
200 EXPECT_EQ(std::u16string(u"bingo"), client.fetcher_->GetPacScript());
201 EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL());
202 }
203
TEST(DhcpPacFileAdapterFetcher,TimeoutDuringDhcp)204 TEST(DhcpPacFileAdapterFetcher, TimeoutDuringDhcp) {
205 base::test::TaskEnvironment task_environment;
206
207 // Does a Fetch() with a long enough delay on accessing DHCP that the
208 // fetcher should time out. This is to test a case manual testing found,
209 // where under certain circumstances (e.g. adapter enabled for DHCP and
210 // needs to retrieve its configuration from DHCP, but no DHCP server
211 // present on the network) accessing DHCP can take on the order of tens
212 // of seconds.
213 FetcherClient client;
214 client.fetcher_->dhcp_delay_ = TestTimeouts::action_max_timeout();
215 client.fetcher_->timeout_ = base::Milliseconds(25);
216
217 base::ElapsedTimer timer;
218 client.RunTest();
219 // An error different from this would be received if the timeout didn't
220 // kick in.
221 client.WaitForResult(ERR_TIMED_OUT);
222
223 ASSERT_TRUE(client.fetcher_->DidFinish());
224 EXPECT_THAT(client.fetcher_->GetResult(), IsError(ERR_TIMED_OUT));
225 EXPECT_EQ(std::u16string(), client.fetcher_->GetPacScript());
226 EXPECT_EQ(GURL(), client.fetcher_->GetPacURL());
227 client.FinishTestAllowCleanup();
228 }
229
TEST(DhcpPacFileAdapterFetcher,CancelWhileDhcp)230 TEST(DhcpPacFileAdapterFetcher, CancelWhileDhcp) {
231 base::test::TaskEnvironment task_environment;
232
233 FetcherClient client;
234 client.RunTest();
235 client.fetcher_->Cancel();
236 base::RunLoop().RunUntilIdle();
237 ASSERT_FALSE(client.fetcher_->DidFinish());
238 ASSERT_TRUE(client.fetcher_->WasCancelled());
239 EXPECT_THAT(client.fetcher_->GetResult(), IsError(ERR_ABORTED));
240 EXPECT_EQ(std::u16string(), client.fetcher_->GetPacScript());
241 EXPECT_EQ(GURL(), client.fetcher_->GetPacURL());
242 client.FinishTestAllowCleanup();
243 }
244
TEST(DhcpPacFileAdapterFetcher,CancelWhileFetcher)245 TEST(DhcpPacFileAdapterFetcher, CancelWhileFetcher) {
246 base::test::TaskEnvironment task_environment;
247
248 FetcherClient client;
249 // This causes the mock fetcher not to pretend the
250 // fetcher finishes after a timeout.
251 client.fetcher_->fetcher_delay_ms_ = -1;
252 client.RunTest();
253 int max_loops = 4;
254 while (!client.fetcher_->IsWaitingForFetcher() && max_loops--) {
255 base::PlatformThread::Sleep(base::Milliseconds(10));
256 base::RunLoop().RunUntilIdle();
257 }
258 client.fetcher_->Cancel();
259 base::RunLoop().RunUntilIdle();
260 ASSERT_FALSE(client.fetcher_->DidFinish());
261 ASSERT_TRUE(client.fetcher_->WasCancelled());
262 EXPECT_THAT(client.fetcher_->GetResult(), IsError(ERR_ABORTED));
263 EXPECT_EQ(std::u16string(), client.fetcher_->GetPacScript());
264 // GetPacURL() still returns the URL fetched in this case.
265 EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL());
266 client.FinishTestAllowCleanup();
267 }
268
TEST(DhcpPacFileAdapterFetcher,CancelAtCompletion)269 TEST(DhcpPacFileAdapterFetcher, CancelAtCompletion) {
270 base::test::TaskEnvironment task_environment;
271
272 FetcherClient client;
273 client.RunTest();
274 client.WaitForResult(OK);
275 client.fetcher_->Cancel();
276 // Canceling after you're done should have no effect, so these
277 // are identical expectations to the NormalCaseURLInDhcp test.
278 ASSERT_TRUE(client.fetcher_->DidFinish());
279 EXPECT_THAT(client.fetcher_->GetResult(), IsOk());
280 EXPECT_EQ(std::u16string(u"bingo"), client.fetcher_->GetPacScript());
281 EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL());
282 client.FinishTestAllowCleanup();
283 }
284
285 // Does a real fetch on a mock DHCP configuration.
286 class MockDhcpRealFetchPacFileAdapterFetcher
287 : public MockDhcpPacFileAdapterFetcher {
288 public:
MockDhcpRealFetchPacFileAdapterFetcher(URLRequestContext * context,scoped_refptr<base::TaskRunner> task_runner)289 explicit MockDhcpRealFetchPacFileAdapterFetcher(
290 URLRequestContext* context,
291 scoped_refptr<base::TaskRunner> task_runner)
292 : MockDhcpPacFileAdapterFetcher(context, task_runner),
293 url_request_context_(context) {}
294
295 // Returns a real PAC file fetcher.
ImplCreateScriptFetcher()296 std::unique_ptr<PacFileFetcher> ImplCreateScriptFetcher() override {
297 return PacFileFetcherImpl::Create(url_request_context_);
298 }
299
300 raw_ptr<URLRequestContext, DanglingUntriaged> url_request_context_;
301 };
302
TEST(DhcpPacFileAdapterFetcher,MockDhcpRealFetch)303 TEST(DhcpPacFileAdapterFetcher, MockDhcpRealFetch) {
304 base::test::TaskEnvironment task_environment;
305
306 EmbeddedTestServer test_server;
307 test_server.ServeFilesFromSourceDirectory(
308 "net/data/pac_file_fetcher_unittest");
309 ASSERT_TRUE(test_server.Start());
310
311 GURL configured_url = test_server.GetURL("/downloadable.pac");
312
313 FetcherClient client;
314 auto url_request_context = CreateTestURLRequestContextBuilder()->Build();
315 client.fetcher_ = std::make_unique<MockDhcpRealFetchPacFileAdapterFetcher>(
316 url_request_context.get(),
317 base::ThreadPool::CreateTaskRunner(
318 {base::MayBlock(),
319 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
320 client.fetcher_->configured_url_ = configured_url.spec();
321 client.RunTest();
322 client.WaitForResult(OK);
323 ASSERT_TRUE(client.fetcher_->DidFinish());
324 EXPECT_THAT(client.fetcher_->GetResult(), IsOk());
325 EXPECT_EQ(std::u16string(u"-downloadable.pac-\n"),
326 client.fetcher_->GetPacScript());
327 EXPECT_EQ(configured_url,
328 client.fetcher_->GetPacURL());
329 }
330
331 #define BASE_URL "http://corpserver/proxy.pac"
332
TEST(DhcpPacFileAdapterFetcher,SanitizeDhcpApiString)333 TEST(DhcpPacFileAdapterFetcher, SanitizeDhcpApiString) {
334 base::test::TaskEnvironment task_environment;
335
336 const size_t kBaseUrlLen = strlen(BASE_URL);
337
338 // Default case.
339 EXPECT_EQ(BASE_URL, DhcpPacFileAdapterFetcher::SanitizeDhcpApiString(
340 BASE_URL, kBaseUrlLen));
341
342 // Trailing \n and no null-termination.
343 EXPECT_EQ(BASE_URL, DhcpPacFileAdapterFetcher::SanitizeDhcpApiString(
344 BASE_URL "\nblablabla", kBaseUrlLen + 1));
345
346 // Embedded NULLs.
347 EXPECT_EQ(BASE_URL, DhcpPacFileAdapterFetcher::SanitizeDhcpApiString(
348 BASE_URL "\0foo\0blat", kBaseUrlLen + 9));
349 }
350
351 #undef BASE_URL
352
353 } // namespace
354
355 } // namespace net
356