1 // Copyright (c) 2011 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 "net/http/http_proxy_client_socket_pool.h"
6
7 #include "base/callback.h"
8 #include "base/compiler_specific.h"
9 #include "base/string_util.h"
10 #include "base/utf_string_conversions.h"
11 #include "net/base/mock_host_resolver.h"
12 #include "net/base/net_errors.h"
13 #include "net/base/ssl_config_service_defaults.h"
14 #include "net/base/test_completion_callback.h"
15 #include "net/http/http_auth_handler_factory.h"
16 #include "net/http/http_network_session.h"
17 #include "net/http/http_proxy_client_socket.h"
18 #include "net/proxy/proxy_service.h"
19 #include "net/socket/client_socket_handle.h"
20 #include "net/socket/client_socket_pool_histograms.h"
21 #include "net/socket/socket_test_util.h"
22 #include "net/spdy/spdy_protocol.h"
23 #include "net/spdy/spdy_test_util.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25
26 namespace net {
27
28 namespace {
29
30 const int kMaxSockets = 32;
31 const int kMaxSocketsPerGroup = 6;
32 const char * const kAuthHeaders[] = {
33 "proxy-authorization", "Basic Zm9vOmJhcg=="
34 };
35 const int kAuthHeadersSize = arraysize(kAuthHeaders) / 2;
36
37 enum HttpProxyType {
38 HTTP,
39 HTTPS,
40 SPDY
41 };
42
43 typedef ::testing::TestWithParam<HttpProxyType> TestWithHttpParam;
44
45 } // namespace
46
47 class HttpProxyClientSocketPoolTest : public TestWithHttpParam {
48 protected:
HttpProxyClientSocketPoolTest()49 HttpProxyClientSocketPoolTest()
50 : ssl_config_(),
51 ignored_transport_socket_params_(new TransportSocketParams(
52 HostPortPair("proxy", 80), LOWEST, GURL(), false, false)),
53 ignored_ssl_socket_params_(new SSLSocketParams(
54 ignored_transport_socket_params_, NULL, NULL,
55 ProxyServer::SCHEME_DIRECT, HostPortPair("www.google.com", 443),
56 ssl_config_, 0, false, false)),
57 tcp_histograms_("MockTCP"),
58 transport_socket_pool_(
59 kMaxSockets, kMaxSocketsPerGroup,
60 &tcp_histograms_,
61 &socket_factory_),
62 ssl_histograms_("MockSSL"),
63 proxy_service_(ProxyService::CreateDirect()),
64 ssl_config_service_(new SSLConfigServiceDefaults),
65 ssl_socket_pool_(kMaxSockets, kMaxSocketsPerGroup,
66 &ssl_histograms_,
67 &host_resolver_,
68 &cert_verifier_,
69 NULL /* dnsrr_resolver */,
70 NULL /* dns_cert_checker */,
71 NULL /* ssl_host_info_factory */,
72 &socket_factory_,
73 &transport_socket_pool_,
74 NULL,
75 NULL,
76 ssl_config_service_.get(),
77 BoundNetLog().net_log()),
78 http_auth_handler_factory_(
79 HttpAuthHandlerFactory::CreateDefault(&host_resolver_)),
80 session_(CreateNetworkSession()),
81 http_proxy_histograms_("HttpProxyUnitTest"),
82 ssl_data_(NULL),
83 data_(NULL),
84 pool_(kMaxSockets, kMaxSocketsPerGroup,
85 &http_proxy_histograms_,
86 NULL,
87 &transport_socket_pool_,
88 &ssl_socket_pool_,
89 NULL) {
90 }
91
~HttpProxyClientSocketPoolTest()92 virtual ~HttpProxyClientSocketPoolTest() {
93 }
94
AddAuthToCache()95 void AddAuthToCache() {
96 const string16 kFoo(ASCIIToUTF16("foo"));
97 const string16 kBar(ASCIIToUTF16("bar"));
98 session_->http_auth_cache()->Add(GURL("http://proxy/"),
99 "MyRealm1",
100 HttpAuth::AUTH_SCHEME_BASIC,
101 "Basic realm=MyRealm1",
102 kFoo,
103 kBar,
104 "/");
105 }
106
GetTcpParams()107 scoped_refptr<TransportSocketParams> GetTcpParams() {
108 if (GetParam() != HTTP)
109 return scoped_refptr<TransportSocketParams>();
110 return ignored_transport_socket_params_;
111 }
112
GetSslParams()113 scoped_refptr<SSLSocketParams> GetSslParams() {
114 if (GetParam() == HTTP)
115 return scoped_refptr<SSLSocketParams>();
116 return ignored_ssl_socket_params_;
117 }
118
119 // Returns the a correctly constructed HttpProxyParms
120 // for the HTTP or HTTPS proxy.
GetParams(bool tunnel)121 scoped_refptr<HttpProxySocketParams> GetParams(bool tunnel) {
122 return scoped_refptr<HttpProxySocketParams>(
123 new HttpProxySocketParams(
124 GetTcpParams(),
125 GetSslParams(),
126 GURL(tunnel ? "https://www.google.com/" : "http://www.google.com"),
127 "",
128 HostPortPair("www.google.com", tunnel ? 443 : 80),
129 session_->http_auth_cache(),
130 session_->http_auth_handler_factory(),
131 session_->spdy_session_pool(),
132 tunnel));
133 }
134
GetTunnelParams()135 scoped_refptr<HttpProxySocketParams> GetTunnelParams() {
136 return GetParams(true);
137 }
138
GetNoTunnelParams()139 scoped_refptr<HttpProxySocketParams> GetNoTunnelParams() {
140 return GetParams(false);
141 }
142
socket_factory()143 DeterministicMockClientSocketFactory& socket_factory() {
144 return socket_factory_;
145 }
146
Initialize(bool async,MockRead * reads,size_t reads_count,MockWrite * writes,size_t writes_count,MockRead * spdy_reads,size_t spdy_reads_count,MockWrite * spdy_writes,size_t spdy_writes_count)147 void Initialize(bool async, MockRead* reads, size_t reads_count,
148 MockWrite* writes, size_t writes_count,
149 MockRead* spdy_reads, size_t spdy_reads_count,
150 MockWrite* spdy_writes, size_t spdy_writes_count) {
151 if (GetParam() == SPDY)
152 data_ = new DeterministicSocketData(spdy_reads, spdy_reads_count,
153 spdy_writes, spdy_writes_count);
154 else
155 data_ = new DeterministicSocketData(reads, reads_count, writes,
156 writes_count);
157
158 data_->set_connect_data(MockConnect(async, 0));
159 data_->StopAfter(2); // Request / Response
160
161 socket_factory_.AddSocketDataProvider(data_.get());
162
163 if (GetParam() != HTTP) {
164 ssl_data_.reset(new SSLSocketDataProvider(async, OK));
165 if (GetParam() == SPDY) {
166 InitializeSpdySsl();
167 }
168 socket_factory_.AddSSLSocketDataProvider(ssl_data_.get());
169 }
170 }
171
InitializeSpdySsl()172 void InitializeSpdySsl() {
173 spdy::SpdyFramer::set_enable_compression_default(false);
174 ssl_data_->next_proto_status = SSLClientSocket::kNextProtoNegotiated;
175 ssl_data_->next_proto = "spdy/2";
176 ssl_data_->was_npn_negotiated = true;
177 }
178
CreateNetworkSession()179 HttpNetworkSession* CreateNetworkSession() {
180 HttpNetworkSession::Params params;
181 params.host_resolver = &host_resolver_;
182 params.cert_verifier = &cert_verifier_;
183 params.proxy_service = proxy_service_;
184 params.client_socket_factory = &socket_factory_;
185 params.ssl_config_service = ssl_config_service_;
186 params.http_auth_handler_factory = http_auth_handler_factory_.get();
187 return new HttpNetworkSession(params);
188 }
189
190 private:
191 SSLConfig ssl_config_;
192
193 scoped_refptr<TransportSocketParams> ignored_transport_socket_params_;
194 scoped_refptr<SSLSocketParams> ignored_ssl_socket_params_;
195 ClientSocketPoolHistograms tcp_histograms_;
196 DeterministicMockClientSocketFactory socket_factory_;
197 MockTransportClientSocketPool transport_socket_pool_;
198 ClientSocketPoolHistograms ssl_histograms_;
199 MockHostResolver host_resolver_;
200 CertVerifier cert_verifier_;
201 const scoped_refptr<ProxyService> proxy_service_;
202 const scoped_refptr<SSLConfigService> ssl_config_service_;
203 SSLClientSocketPool ssl_socket_pool_;
204
205 const scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory_;
206 const scoped_refptr<HttpNetworkSession> session_;
207 ClientSocketPoolHistograms http_proxy_histograms_;
208
209 protected:
210 scoped_ptr<SSLSocketDataProvider> ssl_data_;
211 scoped_refptr<DeterministicSocketData> data_;
212 HttpProxyClientSocketPool pool_;
213 ClientSocketHandle handle_;
214 TestCompletionCallback callback_;
215 };
216
217 //-----------------------------------------------------------------------------
218 // All tests are run with three different proxy types: HTTP, HTTPS (non-SPDY)
219 // and SPDY.
220 INSTANTIATE_TEST_CASE_P(HttpProxyClientSocketPoolTests,
221 HttpProxyClientSocketPoolTest,
222 ::testing::Values(HTTP, HTTPS, SPDY));
223
TEST_P(HttpProxyClientSocketPoolTest,NoTunnel)224 TEST_P(HttpProxyClientSocketPoolTest, NoTunnel) {
225 Initialize(false, NULL, 0, NULL, 0, NULL, 0, NULL, 0);
226
227 int rv = handle_.Init("a", GetNoTunnelParams(), LOW, NULL, &pool_,
228 BoundNetLog());
229 EXPECT_EQ(OK, rv);
230 EXPECT_TRUE(handle_.is_initialized());
231 ASSERT_TRUE(handle_.socket());
232 HttpProxyClientSocket* tunnel_socket =
233 static_cast<HttpProxyClientSocket*>(handle_.socket());
234 EXPECT_TRUE(tunnel_socket->IsConnected());
235 }
236
TEST_P(HttpProxyClientSocketPoolTest,NeedAuth)237 TEST_P(HttpProxyClientSocketPoolTest, NeedAuth) {
238 MockWrite writes[] = {
239 MockWrite(true, 0, "CONNECT www.google.com:443 HTTP/1.1\r\n"
240 "Host: www.google.com\r\n"
241 "Proxy-Connection: keep-alive\r\n\r\n"),
242 };
243 MockRead reads[] = {
244 // No credentials.
245 MockRead(true, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"),
246 MockRead(true, 2, "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
247 MockRead(true, 3, "Content-Length: 10\r\n\r\n"),
248 MockRead(true, 4, "0123456789"),
249 };
250 scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(NULL, 0, 1));
251 scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL));
252 MockWrite spdy_writes[] = {
253 CreateMockWrite(*req, 0, true),
254 CreateMockWrite(*rst, 2, true),
255 };
256 scoped_ptr<spdy::SpdyFrame> resp(
257 ConstructSpdySynReplyError(
258 "407 Proxy Authentication Required", NULL, 0, 1));
259 MockRead spdy_reads[] = {
260 CreateMockWrite(*resp, 1, true),
261 MockRead(true, 0, 3)
262 };
263
264 Initialize(false, reads, arraysize(reads), writes, arraysize(writes),
265 spdy_reads, arraysize(spdy_reads), spdy_writes,
266 arraysize(spdy_writes));
267
268 data_->StopAfter(4);
269 int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
270 BoundNetLog());
271 EXPECT_EQ(ERR_IO_PENDING, rv);
272 EXPECT_FALSE(handle_.is_initialized());
273 EXPECT_FALSE(handle_.socket());
274
275 data_->RunFor(4);
276 rv = callback_.WaitForResult();
277 if (GetParam() != SPDY) {
278 EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, rv);
279 EXPECT_TRUE(handle_.is_initialized());
280 ASSERT_TRUE(handle_.socket());
281 HttpProxyClientSocket* tunnel_socket =
282 static_cast<HttpProxyClientSocket*>(handle_.socket());
283 EXPECT_FALSE(tunnel_socket->IsConnected());
284 EXPECT_FALSE(tunnel_socket->using_spdy());
285 } else {
286 // Proxy auth is not really implemented for SPDY yet
287 EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
288 EXPECT_FALSE(handle_.is_initialized());
289 EXPECT_FALSE(handle_.socket());
290 }
291 }
292
TEST_P(HttpProxyClientSocketPoolTest,HaveAuth)293 TEST_P(HttpProxyClientSocketPoolTest, HaveAuth) {
294 // It's pretty much impossible to make the SPDY case becave synchronously
295 // so we skip this test for SPDY
296 if (GetParam() == SPDY)
297 return;
298 MockWrite writes[] = {
299 MockWrite(false, 0,
300 "CONNECT www.google.com:443 HTTP/1.1\r\n"
301 "Host: www.google.com\r\n"
302 "Proxy-Connection: keep-alive\r\n"
303 "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
304 };
305 MockRead reads[] = {
306 MockRead(false, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"),
307 };
308
309 Initialize(false, reads, arraysize(reads), writes, arraysize(writes), NULL, 0,
310 NULL, 0);
311 AddAuthToCache();
312
313 int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
314 BoundNetLog());
315 EXPECT_EQ(OK, rv);
316 EXPECT_TRUE(handle_.is_initialized());
317 ASSERT_TRUE(handle_.socket());
318 HttpProxyClientSocket* tunnel_socket =
319 static_cast<HttpProxyClientSocket*>(handle_.socket());
320 EXPECT_TRUE(tunnel_socket->IsConnected());
321 }
322
TEST_P(HttpProxyClientSocketPoolTest,AsyncHaveAuth)323 TEST_P(HttpProxyClientSocketPoolTest, AsyncHaveAuth) {
324 MockWrite writes[] = {
325 MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
326 "Host: www.google.com\r\n"
327 "Proxy-Connection: keep-alive\r\n"
328 "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
329 };
330 MockRead reads[] = {
331 MockRead(false, "HTTP/1.1 200 Connection Established\r\n\r\n"),
332 };
333
334 scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders,
335 kAuthHeadersSize, 1));
336 MockWrite spdy_writes[] = {
337 CreateMockWrite(*req, 0, true)
338 };
339 scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
340 MockRead spdy_reads[] = {
341 CreateMockRead(*resp, 1, true),
342 MockRead(true, 0, 2)
343 };
344
345 Initialize(false, reads, arraysize(reads), writes, arraysize(writes),
346 spdy_reads, arraysize(spdy_reads), spdy_writes,
347 arraysize(spdy_writes));
348 AddAuthToCache();
349
350 int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
351 BoundNetLog());
352 EXPECT_EQ(ERR_IO_PENDING, rv);
353 EXPECT_FALSE(handle_.is_initialized());
354 EXPECT_FALSE(handle_.socket());
355
356 data_->RunFor(2);
357 EXPECT_EQ(OK, callback_.WaitForResult());
358 EXPECT_TRUE(handle_.is_initialized());
359 ASSERT_TRUE(handle_.socket());
360 HttpProxyClientSocket* tunnel_socket =
361 static_cast<HttpProxyClientSocket*>(handle_.socket());
362 EXPECT_TRUE(tunnel_socket->IsConnected());
363 }
364
TEST_P(HttpProxyClientSocketPoolTest,TCPError)365 TEST_P(HttpProxyClientSocketPoolTest, TCPError) {
366 if (GetParam() == SPDY) return;
367 data_ = new DeterministicSocketData(NULL, 0, NULL, 0);
368 data_->set_connect_data(MockConnect(true, ERR_CONNECTION_CLOSED));
369
370 socket_factory().AddSocketDataProvider(data_.get());
371
372 int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
373 BoundNetLog());
374 EXPECT_EQ(ERR_IO_PENDING, rv);
375 EXPECT_FALSE(handle_.is_initialized());
376 EXPECT_FALSE(handle_.socket());
377
378 EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, callback_.WaitForResult());
379
380 EXPECT_FALSE(handle_.is_initialized());
381 EXPECT_FALSE(handle_.socket());
382 }
383
TEST_P(HttpProxyClientSocketPoolTest,SSLError)384 TEST_P(HttpProxyClientSocketPoolTest, SSLError) {
385 if (GetParam() == HTTP) return;
386 data_ = new DeterministicSocketData(NULL, 0, NULL, 0);
387 data_->set_connect_data(MockConnect(true, OK));
388 socket_factory().AddSocketDataProvider(data_.get());
389
390 ssl_data_.reset(new SSLSocketDataProvider(true,
391 ERR_CERT_AUTHORITY_INVALID));
392 if (GetParam() == SPDY) {
393 InitializeSpdySsl();
394 }
395 socket_factory().AddSSLSocketDataProvider(ssl_data_.get());
396
397 int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
398 BoundNetLog());
399 EXPECT_EQ(ERR_IO_PENDING, rv);
400 EXPECT_FALSE(handle_.is_initialized());
401 EXPECT_FALSE(handle_.socket());
402
403 EXPECT_EQ(ERR_PROXY_CERTIFICATE_INVALID, callback_.WaitForResult());
404
405 EXPECT_FALSE(handle_.is_initialized());
406 EXPECT_FALSE(handle_.socket());
407 }
408
TEST_P(HttpProxyClientSocketPoolTest,SslClientAuth)409 TEST_P(HttpProxyClientSocketPoolTest, SslClientAuth) {
410 if (GetParam() == HTTP) return;
411 data_ = new DeterministicSocketData(NULL, 0, NULL, 0);
412 data_->set_connect_data(MockConnect(true, OK));
413 socket_factory().AddSocketDataProvider(data_.get());
414
415 ssl_data_.reset(new SSLSocketDataProvider(true,
416 ERR_SSL_CLIENT_AUTH_CERT_NEEDED));
417 if (GetParam() == SPDY) {
418 InitializeSpdySsl();
419 }
420 socket_factory().AddSSLSocketDataProvider(ssl_data_.get());
421
422 int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
423 BoundNetLog());
424 EXPECT_EQ(ERR_IO_PENDING, rv);
425 EXPECT_FALSE(handle_.is_initialized());
426 EXPECT_FALSE(handle_.socket());
427
428 EXPECT_EQ(ERR_SSL_CLIENT_AUTH_CERT_NEEDED, callback_.WaitForResult());
429
430 EXPECT_FALSE(handle_.is_initialized());
431 EXPECT_FALSE(handle_.socket());
432 }
433
TEST_P(HttpProxyClientSocketPoolTest,TunnelUnexpectedClose)434 TEST_P(HttpProxyClientSocketPoolTest, TunnelUnexpectedClose) {
435 MockWrite writes[] = {
436 MockWrite(true, 0,
437 "CONNECT www.google.com:443 HTTP/1.1\r\n"
438 "Host: www.google.com\r\n"
439 "Proxy-Connection: keep-alive\r\n"
440 "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
441 };
442 MockRead reads[] = {
443 MockRead(true, 1, "HTTP/1.1 200 Conn"),
444 MockRead(true, ERR_CONNECTION_CLOSED, 2),
445 };
446 scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders,
447 kAuthHeadersSize, 1));
448 MockWrite spdy_writes[] = {
449 CreateMockWrite(*req, 0, true)
450 };
451 MockRead spdy_reads[] = {
452 MockRead(true, ERR_CONNECTION_CLOSED, 1),
453 };
454
455 Initialize(false, reads, arraysize(reads), writes, arraysize(writes),
456 spdy_reads, arraysize(spdy_reads), spdy_writes,
457 arraysize(spdy_writes));
458 AddAuthToCache();
459
460 int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
461 BoundNetLog());
462 EXPECT_EQ(ERR_IO_PENDING, rv);
463 EXPECT_FALSE(handle_.is_initialized());
464 EXPECT_FALSE(handle_.socket());
465
466 data_->RunFor(3);
467 EXPECT_EQ(ERR_CONNECTION_CLOSED, callback_.WaitForResult());
468 EXPECT_FALSE(handle_.is_initialized());
469 EXPECT_FALSE(handle_.socket());
470 }
471
TEST_P(HttpProxyClientSocketPoolTest,TunnelSetupError)472 TEST_P(HttpProxyClientSocketPoolTest, TunnelSetupError) {
473 MockWrite writes[] = {
474 MockWrite(true, 0,
475 "CONNECT www.google.com:443 HTTP/1.1\r\n"
476 "Host: www.google.com\r\n"
477 "Proxy-Connection: keep-alive\r\n"
478 "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
479 };
480 MockRead reads[] = {
481 MockRead(true, 1, "HTTP/1.1 304 Not Modified\r\n\r\n"),
482 };
483 scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders,
484 kAuthHeadersSize, 1));
485 scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL));
486 MockWrite spdy_writes[] = {
487 CreateMockWrite(*req, 0, true),
488 CreateMockWrite(*rst, 2, true),
489 };
490 scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdySynReplyError(1));
491 MockRead spdy_reads[] = {
492 CreateMockRead(*resp, 1, true),
493 MockRead(true, 0, 3),
494 };
495
496 Initialize(false, reads, arraysize(reads), writes, arraysize(writes),
497 spdy_reads, arraysize(spdy_reads), spdy_writes,
498 arraysize(spdy_writes));
499 AddAuthToCache();
500
501 int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
502 BoundNetLog());
503 EXPECT_EQ(ERR_IO_PENDING, rv);
504 EXPECT_FALSE(handle_.is_initialized());
505 EXPECT_FALSE(handle_.socket());
506
507 data_->RunFor(2);
508
509 rv = callback_.WaitForResult();
510 if (GetParam() == HTTP) {
511 // HTTP Proxy CONNECT responses are not trustworthy
512 EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
513 EXPECT_FALSE(handle_.is_initialized());
514 EXPECT_FALSE(handle_.socket());
515 } else {
516 // HTTPS or SPDY Proxy CONNECT responses are trustworthy
517 EXPECT_EQ(ERR_HTTPS_PROXY_TUNNEL_RESPONSE, rv);
518 EXPECT_TRUE(handle_.is_initialized());
519 EXPECT_TRUE(handle_.socket());
520 }
521 }
522
523 // It would be nice to also test the timeouts in HttpProxyClientSocketPool.
524
525 } // namespace net
526