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: 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 92 virtual ~HttpProxyClientSocketPoolTest() { 93 } 94 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 107 scoped_refptr<TransportSocketParams> GetTcpParams() { 108 if (GetParam() != HTTP) 109 return scoped_refptr<TransportSocketParams>(); 110 return ignored_transport_socket_params_; 111 } 112 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. 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 135 scoped_refptr<HttpProxySocketParams> GetTunnelParams() { 136 return GetParams(true); 137 } 138 139 scoped_refptr<HttpProxySocketParams> GetNoTunnelParams() { 140 return GetParams(false); 141 } 142 143 DeterministicMockClientSocketFactory& socket_factory() { 144 return socket_factory_; 145 } 146 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 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 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 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 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 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 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 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 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 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 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 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