1 // Copyright 2014 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 "components/data_reduction_proxy/browser/data_reduction_proxy_protocol.h"
6
7 #include <utility>
8
9 #include "base/memory/ref_counted.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/run_loop.h"
12 #include "base/strings/stringprintf.h"
13 #include "components/data_reduction_proxy/browser/data_reduction_proxy_params.h"
14 #include "net/base/completion_callback.h"
15 #include "net/base/host_port_pair.h"
16 #include "net/base/network_delegate.h"
17 #include "net/http/http_response_headers.h"
18 #include "net/http/http_transaction_test_util.h"
19 #include "net/socket/socket_test_util.h"
20 #include "net/url_request/static_http_user_agent_settings.h"
21 #include "net/url_request/url_request.h"
22 #include "net/url_request/url_request_context.h"
23 #include "net/url_request/url_request_test_util.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25
26 using net::HttpResponseHeaders;
27 using net::HostPortPair;
28 using net::MockRead;
29 using net::MockWrite;
30 using net::ProxyRetryInfoMap;
31 using net::ProxyService;
32 using net::StaticSocketDataProvider;
33 using net::TestDelegate;
34 using net::URLRequest;
35 using net::TestURLRequestContext;
36
37
38 namespace {
39 // Transform "normal"-looking headers (\n-separated) to the appropriate
40 // input format for ParseRawHeaders (\0-separated).
HeadersToRaw(std::string * headers)41 void HeadersToRaw(std::string* headers) {
42 std::replace(headers->begin(), headers->end(), '\n', '\0');
43 if (!headers->empty())
44 *headers += '\0';
45 }
46
GetDataReductionProxy()47 std::string GetDataReductionProxy() {
48 return "https://proxy1.com:443/";
49 }
50
GetDataReductionProxyFallback()51 std::string GetDataReductionProxyFallback() {
52 return "http://proxy2.com:80/";
53 }
54 } // namespace
55
56
57 namespace data_reduction_proxy {
58 namespace {
59 class TestDataReductionProxyParams : public DataReductionProxyParams {
60 public:
TestDataReductionProxyParams()61 TestDataReductionProxyParams() : DataReductionProxyParams(0, false) {}
62
63 virtual bool WasDataReductionProxyUsed(
64 const net::URLRequest* request,
65 std::pair<GURL, GURL>* proxy_servers) const OVERRIDE;
66 };
67
WasDataReductionProxyUsed(const net::URLRequest * request,std::pair<GURL,GURL> * proxy_servers) const68 bool TestDataReductionProxyParams::WasDataReductionProxyUsed(
69 const net::URLRequest* request,
70 std::pair<GURL, GURL>* proxy_servers) const {
71 if (net::HostPortPair::FromURL(GURL(GetDataReductionProxy())).Equals(
72 request->proxy_server())) {
73 proxy_servers->first = GURL(GetDataReductionProxy());
74 proxy_servers->second = GURL(GetDataReductionProxyFallback());
75 return true;
76 }
77 if (net::HostPortPair::FromURL(
78 GURL(GetDataReductionProxyFallback())).Equals(
79 request->proxy_server())) {
80 proxy_servers->first = GURL(GetDataReductionProxyFallback());
81 proxy_servers->second = GURL();
82 return true;
83 }
84 return false;
85 }
86 } // namespace
87
88 // A test network delegate that exercises the bypass logic of the data
89 // reduction proxy.
90 class TestDataReductionProxyNetworkDelegate : public net::NetworkDelegate {
91 public:
TestDataReductionProxyNetworkDelegate(TestDataReductionProxyParams * test_params)92 TestDataReductionProxyNetworkDelegate(
93 TestDataReductionProxyParams* test_params)
94 : net::NetworkDelegate(), test_data_reduction_proxy_params_(test_params) {
95 }
96
OnHeadersReceived(URLRequest * request,const net::CompletionCallback & callback,const HttpResponseHeaders * original_response_headers,scoped_refptr<HttpResponseHeaders> * override_response_headers,GURL * allowed_unsafe_redirect_url)97 virtual int OnHeadersReceived(
98 URLRequest* request,
99 const net::CompletionCallback& callback,
100 const HttpResponseHeaders* original_response_headers,
101 scoped_refptr<HttpResponseHeaders>* override_response_headers,
102 GURL* allowed_unsafe_redirect_url) OVERRIDE {
103 data_reduction_proxy::MaybeBypassProxyAndPrepareToRetry(
104 test_data_reduction_proxy_params_,
105 request,
106 original_response_headers,
107 override_response_headers);
108 return net::OK;
109 }
110
111 TestDataReductionProxyParams* test_data_reduction_proxy_params_;
112 };
113
114 // Constructs a |TestURLRequestContext| that uses a |MockSocketFactory| to
115 // simulate requests and responses.
116 class DataReductionProxyProtocolTest : public testing::Test {
117 public:
DataReductionProxyProtocolTest()118 DataReductionProxyProtocolTest() : http_user_agent_settings_("", "") {}
119
120 // Sets up the |TestURLRequestContext| with the provided |ProxyService|.
ConfigureTestDependencies(ProxyService * proxy_service)121 void ConfigureTestDependencies(ProxyService* proxy_service) {
122 // Create a context with delayed initialization.
123 context_.reset(new TestURLRequestContext(true));
124
125 proxy_service_.reset(proxy_service);
126 proxy_params_.reset(new TestDataReductionProxyParams());
127 network_delegate_.reset(new TestDataReductionProxyNetworkDelegate(
128 proxy_params_.get()));
129
130 context_->set_client_socket_factory(&mock_socket_factory_);
131 context_->set_proxy_service(proxy_service_.get());
132 context_->set_network_delegate(network_delegate_.get());
133 // This is needed to prevent the test context from adding language headers
134 // to requests.
135 context_->set_http_user_agent_settings(&http_user_agent_settings_);
136
137 context_->Init();
138 }
139
140 // Simulates a request to a data reduction proxy that may result in bypassing
141 // the proxy and retrying the the request.
142 // Runs a test with the given request |method| that expects the first response
143 // from the server to be |first_response|. If |expected_retry|, the test
144 // will expect a retry of the request. A response body will be expected
145 // if |expect_response_body|.
TestProxyFallback(const char * method,const char * first_response,bool expected_retry,bool expect_response_body)146 void TestProxyFallback(const char* method,
147 const char* first_response,
148 bool expected_retry,
149 bool expect_response_body) {
150 std::string payload1 =
151 (expected_retry ? "Bypass message" : "content");
152 MockRead data_reads[] = {
153 MockRead(first_response),
154 MockRead(payload1.c_str()),
155 MockRead(net::SYNCHRONOUS, net::OK),
156 };
157 std::string m(method);
158 std::string trailer =
159 (m == "HEAD" || m == "PUT" || m == "POST") ?
160 "Content-Length: 0\r\n" : "";
161
162 std::string request1 =
163 base::StringPrintf("%s http://www.google.com/ HTTP/1.1\r\n"
164 "Host: www.google.com\r\n"
165 "Proxy-Connection: keep-alive\r\n%s"
166 "User-Agent:\r\n"
167 "Accept-Encoding: gzip,deflate\r\n\r\n",
168 method, trailer.c_str());
169 MockWrite data_writes[] = {
170 MockWrite(request1.c_str()),
171 };
172 StaticSocketDataProvider data1(data_reads, arraysize(data_reads),
173 data_writes, arraysize(data_writes));
174 mock_socket_factory_.AddSocketDataProvider(&data1);
175
176 MockRead data_reads2[] = {
177 MockRead("HTTP/1.0 200 OK\r\n"
178 "Server: not-proxy\r\n\r\n"),
179 MockRead("content"),
180 MockRead(net::SYNCHRONOUS, net::OK),
181 };
182 std::string request2 =
183 base::StringPrintf("%s / HTTP/1.1\r\n"
184 "Host: www.google.com\r\n"
185 "Connection: keep-alive\r\n%s"
186 "User-Agent:\r\n"
187 "Accept-Encoding: gzip,deflate\r\n\r\n",
188 method, trailer.c_str());
189 MockWrite data_writes2[] = {
190 MockWrite(request2.c_str()),
191 };
192 StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
193 data_writes2, arraysize(data_writes2));
194 if (expected_retry) {
195 mock_socket_factory_.AddSocketDataProvider(&data2);
196 }
197
198 // Expect that we get "content" and not "Bypass message", and that there's
199 // a "not-proxy" "Server:" header in the final response.
200 ExecuteRequestExpectingContentAndHeader(
201 method,
202 (expect_response_body ? "content" : ""),
203 "server",
204 (expected_retry == 0 ? "proxy" : "not-proxy"),
205 expected_retry);
206 }
207
208 // Starts a request with the given |method| and checks that the response
209 // contains |content| and the the header |header|: |value|, if |header| is
210 // non-empty. Verifies that the request's URL chain is the right length
211 // depending on whether or not a retry was expected (|expected_retry|).
ExecuteRequestExpectingContentAndHeader(const std::string & method,const std::string & content,const std::string & header,const std::string & value,bool expected_retry)212 void ExecuteRequestExpectingContentAndHeader(const std::string& method,
213 const std::string& content,
214 const std::string& header,
215 const std::string& value,
216 bool expected_retry) {
217 TestDelegate d;
218 URLRequest r(GURL("http://www.google.com/"),
219 net::DEFAULT_PRIORITY,
220 &d,
221 context_.get());
222 r.set_method(method);
223 r.SetLoadFlags(net::LOAD_NORMAL);
224
225 r.Start();
226 base::RunLoop().Run();
227
228 EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status());
229 EXPECT_EQ(net::OK, r.status().error());
230 if (expected_retry)
231 EXPECT_EQ(2U, r.url_chain().size());
232 else
233 EXPECT_EQ(1U, r.url_chain().size());
234
235 if (!header.empty()) {
236 // We also have a server header here that isn't set by the proxy.
237 EXPECT_TRUE(r.response_headers()->HasHeaderValue(header, value));
238 }
239
240 EXPECT_EQ(content, d.data_received());
241 }
242
243 // Returns the key to the |ProxyRetryInfoMap|.
GetProxyKey(std::string proxy)244 std::string GetProxyKey(std::string proxy) {
245 GURL gurl(proxy);
246 std::string host_port = HostPortPair::FromURL(GURL(proxy)).ToString();
247 if (gurl.SchemeIs("https"))
248 return "https://" + host_port;
249 return host_port;
250 }
251
252 // Checks that |expected_num_bad_proxies| proxies are on the proxy retry list.
253 // If the list has one proxy, it should match |bad_proxy|. If it has two
254 // proxies, it should match |bad_proxy| and |bad_proxy2|. Checks also that
255 // the current delay associated with each bad proxy is |duration_seconds|.
TestBadProxies(unsigned int expected_num_bad_proxies,int duration_seconds,const std::string & bad_proxy,const std::string & bad_proxy2)256 void TestBadProxies(unsigned int expected_num_bad_proxies,
257 int duration_seconds,
258 const std::string& bad_proxy,
259 const std::string& bad_proxy2) {
260 const ProxyRetryInfoMap& retry_info = proxy_service_->proxy_retry_info();
261 ASSERT_EQ(expected_num_bad_proxies, retry_info.size());
262
263 base::TimeDelta expected_min_duration;
264 base::TimeDelta expected_max_duration;
265 if (duration_seconds == 0) {
266 expected_min_duration = base::TimeDelta::FromMinutes(1);
267 expected_max_duration = base::TimeDelta::FromMinutes(5);
268 }
269 else {
270 expected_min_duration = base::TimeDelta::FromSeconds(duration_seconds);
271 expected_max_duration = base::TimeDelta::FromSeconds(duration_seconds);
272 }
273
274 if (expected_num_bad_proxies >= 1u) {
275 ProxyRetryInfoMap::const_iterator i =
276 retry_info.find(GetProxyKey(bad_proxy));
277 ASSERT_TRUE(i != retry_info.end());
278 EXPECT_TRUE(expected_min_duration <= (*i).second.current_delay);
279 EXPECT_TRUE((*i).second.current_delay <= expected_max_duration);
280 }
281 if (expected_num_bad_proxies == 2u) {
282 ProxyRetryInfoMap::const_iterator i =
283 retry_info.find(GetProxyKey(bad_proxy2));
284 ASSERT_TRUE(i != retry_info.end());
285 EXPECT_TRUE(expected_min_duration <= (*i).second.current_delay);
286 EXPECT_TRUE((*i).second.current_delay <= expected_max_duration);
287 }
288 }
289
290 protected:
291 base::MessageLoopForIO loop_;
292
293 net::MockClientSocketFactory mock_socket_factory_;
294 scoped_ptr<ProxyService> proxy_service_;
295 scoped_ptr<TestDataReductionProxyParams> proxy_params_;
296 scoped_ptr<TestDataReductionProxyNetworkDelegate> network_delegate_;
297 net::StaticHttpUserAgentSettings http_user_agent_settings_;
298
299 scoped_ptr<TestURLRequestContext> context_;
300 };
301
302 // Tests that request are deemed idempotent or not according to the method used.
TEST_F(DataReductionProxyProtocolTest,TestIdempotency)303 TEST_F(DataReductionProxyProtocolTest, TestIdempotency) {
304 net::TestURLRequestContext context;
305 const struct {
306 const char* method;
307 bool expected_result;
308 } tests[] = {
309 { "GET", true },
310 { "OPTIONS", true },
311 { "HEAD", true },
312 { "PUT", true },
313 { "DELETE", true },
314 { "TRACE", true },
315 { "POST", false },
316 { "CONNECT", false },
317 };
318 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
319 net::TestURLRequest request(GURL("http://www.google.com/"),
320 net::DEFAULT_PRIORITY,
321 NULL,
322 &context);
323 request.set_method(tests[i].method);
324 EXPECT_EQ(tests[i].expected_result, IsRequestIdempotent(&request));
325 }
326 }
327
328 // Tests that the response is correctly overwritten as a redirect.
TEST_F(DataReductionProxyProtocolTest,OverrideResponseAsRedirect)329 TEST_F(DataReductionProxyProtocolTest, OverrideResponseAsRedirect) {
330 net::TestURLRequestContext context;
331 const struct {
332 const char* headers;
333 const char* expected_headers;
334 } tests[] = {
335 { "HTTP/1.1 200 0K\n"
336 "Chrome-Proxy: block=1\n"
337 "Via: 1.1 Chrome-Compression-Proxy\n",
338
339 "HTTP/1.1 302 Found\n"
340 "Chrome-Proxy: block=1\n"
341 "Via: 1.1 Chrome-Compression-Proxy\n"
342 "Location: http://www.google.com/\n"
343 },
344 { "HTTP/1.1 302 Found\n"
345 "Location: http://foo.com/\n",
346
347 "HTTP/1.1 302 Found\n"
348 "Location: http://www.google.com/\n"
349 },
350 };
351
352 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
353 std::string headers(tests[i].headers);
354 HeadersToRaw(&headers);
355 scoped_refptr<HttpResponseHeaders> original_response_headers(
356 new HttpResponseHeaders(headers));
357 scoped_refptr<HttpResponseHeaders> override_response_headers;
358 TestDelegate test_delegate;
359 net::TestURLRequest request(GURL("http://www.google.com/"),
360 net::DEFAULT_PRIORITY,
361 NULL,
362 &context);
363 OverrideResponseAsRedirect(&request,
364 original_response_headers,
365 &override_response_headers);
366 int expected_flags = net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_PROXY;
367 EXPECT_EQ(expected_flags, request.load_flags());
368 std::string override_headers;
369 override_response_headers->GetNormalizedHeaders(&override_headers);
370 EXPECT_EQ(std::string(tests[i].expected_headers), override_headers);
371 }
372 }
373
374
375 // After each test, the proxy retry info will contain zero, one, or two of the
376 // data reduction proxies depending on whether no bypass was indicated by the
377 // initial response, a single proxy bypass was indicated, or a double bypass
378 // was indicated. In both the single and double bypass cases, if the request
379 // was idempotent, it will be retried over a direct connection.
TEST_F(DataReductionProxyProtocolTest,BypassLogic)380 TEST_F(DataReductionProxyProtocolTest, BypassLogic) {
381 std::string primary = GetDataReductionProxy();
382 std::string fallback = GetDataReductionProxyFallback();
383 const struct {
384 const char* method;
385 const char* first_response;
386 bool expected_retry;
387 size_t expected_bad_proxy_count;
388 bool expect_response_body;
389 int expected_duration;
390 } tests[] = {
391 // Valid data reduction proxy response with no bypass message.
392 { "GET",
393 "HTTP/1.1 200 OK\r\n"
394 "Server: proxy\r\n"
395 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
396 false,
397 0u,
398 true,
399 -1
400 },
401 // Valid data reduction proxy response with older, but still valid via
402 // header.
403 { "GET",
404 "HTTP/1.1 200 OK\r\n"
405 "Server: proxy\r\n"
406 "Via: 1.1 Chrome Compression Proxy\r\n\r\n",
407 false,
408 0u,
409 true,
410 -1
411 },
412 // Valid data reduction proxy response with chained via header,
413 // no bypass message.
414 { "GET",
415 "HTTP/1.1 200 OK\r\n"
416 "Server: proxy\r\n"
417 "Via: 1.1 Chrome-Compression-Proxy, 1.0 some-other-proxy\r\n\r\n",
418 false,
419 0u,
420 true,
421 -1
422 },
423 // Valid data reduction proxy response with a bypass message.
424 { "GET",
425 "HTTP/1.1 200 OK\r\n"
426 "Server: proxy\r\n"
427 "Chrome-Proxy: bypass=0\r\n"
428 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
429 true,
430 1u,
431 true,
432 0
433 },
434 // Valid data reduction proxy response with a bypass message.
435 { "GET",
436 "HTTP/1.1 200 OK\r\n"
437 "Server: proxy\r\n"
438 "Chrome-Proxy: bypass=1\r\n"
439 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
440 true,
441 1u,
442 true,
443 1
444 },
445 // Same as above with the OPTIONS method, which is idempotent.
446 { "OPTIONS",
447 "HTTP/1.1 200 OK\r\n"
448 "Server: proxy\r\n"
449 "Chrome-Proxy: bypass=0\r\n"
450 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
451 true,
452 1u,
453 true,
454 0
455 },
456 // Same as above with the HEAD method, which is idempotent.
457 { "HEAD",
458 "HTTP/1.1 200 OK\r\n"
459 "Server: proxy\r\n"
460 "Chrome-Proxy: bypass=0\r\n"
461 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
462 true,
463 1u,
464 false,
465 0
466 },
467 // Same as above with the PUT method, which is idempotent.
468 { "PUT",
469 "HTTP/1.1 200 OK\r\n"
470 "Server: proxy\r\n"
471 "Chrome-Proxy: bypass=0\r\n"
472 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
473 true,
474 1u,
475 true,
476 0
477 },
478 // Same as above with the DELETE method, which is idempotent.
479 { "DELETE",
480 "HTTP/1.1 200 OK\r\n"
481 "Server: proxy\r\n"
482 "Chrome-Proxy: bypass=0\r\n"
483 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
484 true,
485 1u,
486 true,
487 0
488 },
489 // Same as above with the TRACE method, which is idempotent.
490 { "TRACE",
491 "HTTP/1.1 200 OK\r\n"
492 "Server: proxy\r\n"
493 "Chrome-Proxy: bypass=0\r\n"
494 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
495 true,
496 1u,
497 true,
498 0
499 },
500 // 500 responses should be bypassed.
501 { "GET",
502 "HTTP/1.1 500 Internal Server Error\r\n"
503 "Server: proxy\r\n"
504 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
505 true,
506 1u,
507 true,
508 0
509 },
510 // 502 responses should be bypassed.
511 { "GET",
512 "HTTP/1.1 502 Internal Server Error\r\n"
513 "Server: proxy\r\n"
514 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
515 true,
516 1u,
517 true,
518 0
519 },
520 // 503 responses should be bypassed.
521 { "GET",
522 "HTTP/1.1 503 Internal Server Error\r\n"
523 "Server: proxy\r\n"
524 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
525 true,
526 1u,
527 true,
528 0
529 },
530 // Invalid data reduction proxy response. Missing Via header.
531 { "GET",
532 "HTTP/1.1 200 OK\r\n"
533 "Server: proxy\r\n\r\n",
534 true,
535 1u,
536 true,
537 0
538 },
539 // Invalid data reduction proxy response. Wrong Via header.
540 { "GET",
541 "HTTP/1.1 200 OK\r\n"
542 "Server: proxy\r\n"
543 "Via: 1.0 some-other-proxy\r\n\r\n",
544 true,
545 1u,
546 true,
547 0
548 },
549 // Valid data reduction proxy response. 304 missing Via header.
550 { "GET",
551 "HTTP/1.1 304 Not Modified\r\n"
552 "Server: proxy\r\n\r\n",
553 false,
554 0u,
555 false,
556 0
557 },
558 // Valid data reduction proxy response with a bypass message. It will
559 // not be retried because the request is non-idempotent.
560 { "POST",
561 "HTTP/1.1 200 OK\r\n"
562 "Server: proxy\r\n"
563 "Chrome-Proxy: bypass=0\r\n"
564 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
565 false,
566 1u,
567 true,
568 0
569 },
570 // Valid data reduction proxy response with block message. Both proxies
571 // should be on the retry list when it completes.
572 { "GET",
573 "HTTP/1.1 200 OK\r\n"
574 "Server: proxy\r\n"
575 "Chrome-Proxy: block=1\r\n"
576 "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
577 true,
578 2u,
579 true,
580 1
581 }
582 };
583 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
584 ConfigureTestDependencies(
585 ProxyService::CreateFixedFromPacResult("PROXY " +
586 HostPortPair::FromURL(GURL(primary)).ToString() + "; PROXY " +
587 HostPortPair::FromURL(GURL(fallback)).ToString() + "; DIRECT"));
588 TestProxyFallback(tests[i].method,
589 tests[i].first_response,
590 tests[i].expected_retry,
591 tests[i].expect_response_body);
592
593 // We should also observe the bad proxy in the retry list.
594 TestBadProxies(tests[i].expected_bad_proxy_count,
595 tests[i].expected_duration,
596 primary, fallback);
597 }
598 }
599
TEST_F(DataReductionProxyProtocolTest,ProxyBypassIgnoredOnDirectConnection)600 TEST_F(DataReductionProxyProtocolTest,
601 ProxyBypassIgnoredOnDirectConnection) {
602 // Verify that a Chrome-Proxy header is ignored when returned from a directly
603 // connected origin server.
604 ConfigureTestDependencies(ProxyService::CreateFixedFromPacResult("DIRECT"));
605
606 MockRead data_reads[] = {
607 MockRead("HTTP/1.1 200 OK\r\n"
608 "Chrome-Proxy: bypass=0\r\n\r\n"),
609 MockRead("Bypass message"),
610 MockRead(net::SYNCHRONOUS, net::OK),
611 };
612 MockWrite data_writes[] = {
613 MockWrite("GET / HTTP/1.1\r\n"
614 "Host: www.google.com\r\n"
615 "Connection: keep-alive\r\n"
616 "User-Agent:\r\n"
617 "Accept-Encoding: gzip,deflate\r\n\r\n"),
618 };
619 StaticSocketDataProvider data1(data_reads, arraysize(data_reads),
620 data_writes, arraysize(data_writes));
621 mock_socket_factory_.AddSocketDataProvider(&data1);
622
623 TestDelegate d;
624 URLRequest r(GURL("http://www.google.com/"),
625 net::DEFAULT_PRIORITY,
626 &d,
627 context_.get());
628 r.set_method("GET");
629 r.SetLoadFlags(net::LOAD_NORMAL);
630
631 r.Start();
632 base::RunLoop().Run();
633
634 EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status());
635 EXPECT_EQ(net::OK, r.status().error());
636
637 EXPECT_EQ("Bypass message", d.data_received());
638
639 // We should have no entries in our bad proxy list.
640 TestBadProxies(0, -1, "", "");
641 }
642
643 } // namespace data_reduction_proxy
644