diff options
author | rch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-13 22:37:16 +0000 |
---|---|---|
committer | rch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-13 22:37:16 +0000 |
commit | d9da5fe263e748c75d85816ee6bae46b2b9de9c9 (patch) | |
tree | f6dbf611550dc1118fcff2cfe9429ca721a5c1bf /net | |
parent | a691b8874c225cf530396661264a77159db38780 (diff) | |
download | chromium_src-d9da5fe263e748c75d85816ee6bae46b2b9de9c9.zip chromium_src-d9da5fe263e748c75d85816ee6bae46b2b9de9c9.tar.gz chromium_src-d9da5fe263e748c75d85816ee6bae46b2b9de9c9.tar.bz2 |
Integrate the SpdyProxyClientSocket into the HttpStreamRequest
to support fetching HTTPS URLS over a SPDY Proxy.
BUG=29625
TEST=HttpNetworkTransactionTest.HttpsProxySpdyConnect
Review URL: http://codereview.chromium.org/3417010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@62468 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/base/net_error_list.h | 5 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 212 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket_pool.cc | 114 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket_pool.h | 21 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket_pool_unittest.cc | 416 | ||||
-rw-r--r-- | net/http/http_stream_request.cc | 18 | ||||
-rw-r--r-- | net/http/http_stream_request.h | 2 | ||||
-rw-r--r-- | net/socket/socket.h | 8 | ||||
-rw-r--r-- | net/socket/socket_test_util.cc | 97 | ||||
-rw-r--r-- | net/socket/socket_test_util.h | 61 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_pool_unittest.cc | 2 | ||||
-rw-r--r-- | net/spdy/spdy_framer.h | 2 | ||||
-rw-r--r-- | net/spdy/spdy_proxy_client_socket.cc | 79 | ||||
-rw-r--r-- | net/spdy/spdy_proxy_client_socket.h | 5 | ||||
-rw-r--r-- | net/spdy/spdy_proxy_client_socket_unittest.cc | 130 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.cc | 61 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.h | 18 |
17 files changed, 913 insertions, 338 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index c1c24b9..018cd77 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -174,7 +174,7 @@ NET_ERROR(SSL_UNSAFE_NEGOTIATION, -128) // The SSL server attempted to use a weak ephemeral Diffie-Hellman key. NET_ERROR(SSL_WEAK_SERVER_EPHEMERAL_DH_KEY, -129) -// Could not create a TCP connection to the proxy server. An error occurred +// Could not create a connection to the proxy server. An error occurred // either in resolving its name, or in connecting a socket to it. // Note that this does NOT include failures during the actual "CONNECT" method // of an HTTP proxy. @@ -202,6 +202,9 @@ NET_ERROR(SSL_CLIENT_AUTH_PRIVATE_KEY_ACCESS_DENIED, -134) // The SSL client certificate has no private key. NET_ERROR(SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY, -135) +// The certificate presented by the HTTPS Proxy was invalid. +NET_ERROR(PROXY_CERTIFICATE_INVALID, -136) + // Certificate error codes // // The values of certificate error codes must be consecutive. diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index 508cc28..e37eea6 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -1829,6 +1829,218 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyGet) { EXPECT_EQ(net::kUploadData, response_data); } +// Test a SPDY CONNECT through an HTTPS Proxy to an HTTPS (non-SPDY) Server. +TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyConnectHttps) { + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(CreateFixedProxyService("https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // CONNECT to www.google.com:443 via SPDY + scoped_ptr<spdy::SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1)); + // fetch https://www.google.com/ via HTTP + + const char get[] = "GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"; + scoped_ptr<spdy::SpdyFrame> wrapped_get( + ConstructSpdyBodyFrame(1, get, strlen(get), false)); + MockWrite spdy_writes[] = { + CreateMockWrite(*connect, 1), + CreateMockWrite(*wrapped_get, 3) + }; + + scoped_ptr<spdy::SpdyFrame> conn_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + const char resp[] = "HTTP/1.1 200 OK\r\n" + "Content-Length: 10\r\n\r\n"; + + scoped_ptr<spdy::SpdyFrame> wrapped_get_resp( + ConstructSpdyBodyFrame(1, resp, strlen(resp), false)); + scoped_ptr<spdy::SpdyFrame> wrapped_body( + ConstructSpdyBodyFrame(1, "1234567890", 10, false)); + MockRead spdy_reads[] = { + CreateMockRead(*conn_resp, 2, true), + CreateMockRead(*wrapped_get_resp, 4, true), + CreateMockRead(*wrapped_body, 5, true), + CreateMockRead(*wrapped_body, 6, true), + MockRead(true, 0, 7), + }; + + scoped_refptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data); + + SSLSocketDataProvider ssl(true, OK); + ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated; + ssl.next_proto = "spdy/2"; + ssl.was_npn_negotiated = true; + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + SSLSocketDataProvider ssl2(true, OK); + ssl2.was_npn_negotiated = false; + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, &callback1, log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("1234567890", response_data); +} + +// Test a SPDY CONNECT through an HTTPS Proxy to a SPDY server. +TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyConnectSpdy) { + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(CreateFixedProxyService("https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // CONNECT to www.google.com:443 via SPDY + scoped_ptr<spdy::SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1)); + // fetch https://www.google.com/ via SPDY + const char* const kMyUrl = "https://www.google.com/"; + scoped_ptr<spdy::SpdyFrame> get(ConstructSpdyGet(kMyUrl, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> wrapped_get(ConstructWrappedSpdyFrame(get, 1)); + MockWrite spdy_writes[] = { + CreateMockWrite(*connect, 1), + CreateMockWrite(*wrapped_get, 3) + }; + + scoped_ptr<spdy::SpdyFrame> conn_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> get_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> wrapped_get_resp( + ConstructWrappedSpdyFrame(get_resp, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> wrapped_body(ConstructWrappedSpdyFrame(body, 1)); + MockRead spdy_reads[] = { + CreateMockRead(*conn_resp, 2, true), + CreateMockRead(*wrapped_get_resp, 4, true), + CreateMockRead(*wrapped_body, 5, true), + MockRead(true, 0, 1), + }; + + scoped_refptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data); + + SSLSocketDataProvider ssl(true, OK); + ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated; + ssl.next_proto = "spdy/2"; + ssl.was_npn_negotiated = true; + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + SSLSocketDataProvider ssl2(true, OK); + ssl2.next_proto_status = SSLClientSocket::kNextProtoNegotiated; + ssl2.next_proto = "spdy/2"; + ssl2.was_npn_negotiated = true; + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, &callback1, log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ(net::kUploadData, response_data); +} + +// Test a SPDY CONNECT failure through an HTTPS Proxy. +TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyConnectFailure) { + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(CreateFixedProxyService("https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // CONNECT to www.google.com:443 via SPDY + scoped_ptr<spdy::SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> get(ConstructSpdyRstStream(1, spdy::CANCEL)); + + MockWrite spdy_writes[] = { + CreateMockWrite(*connect, 1), + CreateMockWrite(*get, 3), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdySynReplyError(1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp, 2, true), + MockRead(true, 0, 4), + }; + + scoped_refptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data); + + SSLSocketDataProvider ssl(true, OK); + ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated; + ssl.next_proto = "spdy/2"; + ssl.was_npn_negotiated = true; + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + SSLSocketDataProvider ssl2(true, OK); + ssl2.next_proto_status = SSLClientSocket::kNextProtoNegotiated; + ssl2.next_proto = "spdy/2"; + ssl2.was_npn_negotiated = true; + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, &callback1, log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response == NULL); +} + // Test the challenge-response-retry sequence through an HTTPS Proxy TEST_F(HttpNetworkTransactionTest, HttpsProxyAuthRetry) { // Configure against https proxy server "proxy:70". diff --git a/net/http/http_proxy_client_socket_pool.cc b/net/http/http_proxy_client_socket_pool.cc index 4a5495b..a5bbb53 100644 --- a/net/http/http_proxy_client_socket_pool.cc +++ b/net/http/http_proxy_client_socket_pool.cc @@ -19,6 +19,11 @@ #include "net/socket/ssl_client_socket.h" #include "net/socket/ssl_client_socket_pool.h" #include "net/socket/tcp_client_socket_pool.h" +#include "net/spdy/spdy_proxy_client_socket.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_settings_storage.h" +#include "net/spdy/spdy_stream.h" namespace net { @@ -30,9 +35,13 @@ HttpProxySocketParams::HttpProxySocketParams( HostPortPair endpoint, HttpAuthCache* http_auth_cache, HttpAuthHandlerFactory* http_auth_handler_factory, + SpdySessionPool* spdy_session_pool, + SpdySettingsStorage* spdy_settings, bool tunnel) : tcp_params_(tcp_params), ssl_params_(ssl_params), + spdy_session_pool_(spdy_session_pool), + spdy_settings_(spdy_settings), request_url_(request_url), user_agent_(user_agent), endpoint_(endpoint), @@ -87,6 +96,8 @@ LoadState HttpProxyConnectJob::GetLoadState() const { return transport_socket_handle_->GetLoadState(); case STATE_HTTP_PROXY_CONNECT: case STATE_HTTP_PROXY_CONNECT_COMPLETE: + case STATE_SPDY_PROXY_CREATE_STREAM: + case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE: return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL; default: NOTREACHED(); @@ -137,6 +148,13 @@ int HttpProxyConnectJob::DoLoop(int result) { case STATE_HTTP_PROXY_CONNECT_COMPLETE: rv = DoHttpProxyConnectComplete(rv); break; + case STATE_SPDY_PROXY_CREATE_STREAM: + DCHECK_EQ(OK, rv); + rv = DoSpdyProxyCreateStream(); + break; + case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE: + rv = DoSpdyProxyCreateStreamComplete(rv); + break; default: NOTREACHED() << "bad state"; rv = ERR_FAILED; @@ -165,11 +183,21 @@ int HttpProxyConnectJob::DoTCPConnectComplete(int result) { // longer to timeout than it should. ResetTimer(base::TimeDelta::FromSeconds( kHttpProxyConnectJobTimeoutInSeconds)); + next_state_ = STATE_HTTP_PROXY_CONNECT; return result; } int HttpProxyConnectJob::DoSSLConnect() { + if (params_->tunnel()) { + HostPortProxyPair pair(params_->destination().host_port_pair(), + ProxyServer::Direct()); + if (params_->spdy_session_pool()->HasSession(pair)) { + using_spdy_ = true; + next_state_ = STATE_SPDY_PROXY_CREATE_STREAM; + return OK; + } + } next_state_ = STATE_SSL_CONNECT_COMPLETE; transport_socket_handle_.reset(new ClientSocketHandle()); return transport_socket_handle_->Init( @@ -179,15 +207,21 @@ int HttpProxyConnectJob::DoSSLConnect() { } int HttpProxyConnectJob::DoSSLConnectComplete(int result) { - if (IsCertificateError(result) && - params_->ssl_params()->load_flags() & LOAD_IGNORE_ALL_CERT_ERRORS) - result = OK; + // TODO(rch): enable support for client auth to the proxy + if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) + return ERR_PROXY_AUTH_UNSUPPORTED; + if (IsCertificateError(result)) { + if (params_->ssl_params()->load_flags() & LOAD_IGNORE_ALL_CERT_ERRORS) + result = OK; + else + // TODO(rch): allow the user to deal with proxy cert errors in the + // same way as server cert errors. + return ERR_PROXY_CERTIFICATE_INVALID; + } if (result < 0) { - // TODO(eroman): return ERR_PROXY_CONNECTION_FAILED if failed with the - // TCP connection. if (transport_socket_handle_->socket()) transport_socket_handle_->socket()->Disconnect(); - return result; + return ERR_PROXY_CONNECTION_FAILED; } SSLClientSocket* ssl = @@ -199,10 +233,67 @@ int HttpProxyConnectJob::DoSSLConnectComplete(int result) { // longer to timeout than it should. ResetTimer(base::TimeDelta::FromSeconds( kHttpProxyConnectJobTimeoutInSeconds)); - next_state_ = STATE_HTTP_PROXY_CONNECT; + // TODO(rch): If we ever decide to implement a "trusted" SPDY proxy + // (one that we speak SPDY over SSL to, but to which we send HTTPS + // request directly instead of through CONNECT tunnels, then we + // need to add a predicate to this if statement so we fall through + // to the else case. (HttpProxyClientSocket currently acts as + // a "trusted" SPDY proxy). + if (using_spdy_ && params_->tunnel()) + next_state_ = STATE_SPDY_PROXY_CREATE_STREAM; + else + next_state_ = STATE_HTTP_PROXY_CONNECT; return result; } +int HttpProxyConnectJob::DoSpdyProxyCreateStream() { + DCHECK(using_spdy_); + DCHECK(params_->tunnel()); + + HostPortProxyPair pair(params_->destination().host_port_pair(), + ProxyServer::Direct()); + SpdySessionPool* spdy_pool = params_->spdy_session_pool(); + scoped_refptr<SpdySession> spdy_session; + // It's possible that a session to the proxy has recently been created + if (spdy_pool->HasSession(pair)) { + if (transport_socket_handle_->socket()) + transport_socket_handle_->socket()->Disconnect(); + transport_socket_handle_->Reset(); + spdy_session = spdy_pool->Get(pair, params_->spdy_settings(), net_log()); + } else { + // Create a session direct to the proxy itself + int rv = spdy_pool->GetSpdySessionFromSocket( + pair, params_->spdy_settings(), transport_socket_handle_.release(), + net_log(), OK, &spdy_session, /*using_ssl_*/ true); + if (rv < 0) { + if (transport_socket_handle_->socket()) + transport_socket_handle_->socket()->Disconnect(); + return rv; + } + } + + next_state_ = STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE; + return spdy_session->CreateStream(params_->request_url(), + params_->destination().priority(), + &spdy_stream_, net_log(), &callback_); +} + +int HttpProxyConnectJob::DoSpdyProxyCreateStreamComplete(int result) { + if (result < 0) + return result; + + next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE; + transport_socket_.reset( + new SpdyProxyClientSocket(spdy_stream_, + params_->user_agent(), + params_->endpoint(), + params_->request_url(), + params_->destination().host_port_pair(), + params_->http_auth_cache(), + params_->http_auth_handler_factory())); + return transport_socket_->Connect(&callback_); +} + int HttpProxyConnectJob::DoHttpProxyConnect() { next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE; const HostResolver::RequestInfo& tcp_destination = params_->destination(); @@ -219,14 +310,7 @@ int HttpProxyConnectJob::DoHttpProxyConnect() { params_->http_auth_handler_factory(), params_->tunnel(), using_spdy_)); - int result = transport_socket_->Connect(&callback_); - - // Clear the circular reference to HttpNetworkSession (|params_| reference - // HttpNetworkSession, which reference HttpProxyClientSocketPool, which - // references |this|) here because it is safe to do so now but not at other - // points. This may cancel this ConnectJob. - params_ = NULL; - return result; + return transport_socket_->Connect(&callback_); } int HttpProxyConnectJob::DoHttpProxyConnectComplete(int result) { diff --git a/net/http/http_proxy_client_socket_pool.h b/net/http/http_proxy_client_socket_pool.h index f95a054..a08a573 100644 --- a/net/http/http_proxy_client_socket_pool.h +++ b/net/http/http_proxy_client_socket_pool.h @@ -25,6 +25,9 @@ class HttpAuthCache; class HttpAuthHandlerFactory; class SSLClientSocketPool; class SSLSocketParams; +class SpdySessionPool; +class SpdySettingsStorage; +class SpdyStream; class TCPClientSocketPool; class TCPSocketParams; @@ -41,6 +44,8 @@ class HttpProxySocketParams : public base::RefCounted<HttpProxySocketParams> { HostPortPair endpoint, HttpAuthCache* http_auth_cache, HttpAuthHandlerFactory* http_auth_handler_factory, + SpdySessionPool* spdy_session_pool, + SpdySettingsStorage* spdy_settings, bool tunnel); const scoped_refptr<TCPSocketParams>& tcp_params() const { @@ -56,6 +61,12 @@ class HttpProxySocketParams : public base::RefCounted<HttpProxySocketParams> { HttpAuthHandlerFactory* http_auth_handler_factory() const { return http_auth_handler_factory_; } + SpdySessionPool* spdy_session_pool() { + return spdy_session_pool_; + } + SpdySettingsStorage* spdy_settings() { + return spdy_settings_; + } const HostResolver::RequestInfo& destination() const; bool tunnel() const { return tunnel_; } @@ -65,6 +76,8 @@ class HttpProxySocketParams : public base::RefCounted<HttpProxySocketParams> { const scoped_refptr<TCPSocketParams> tcp_params_; const scoped_refptr<SSLSocketParams> ssl_params_; + SpdySessionPool* spdy_session_pool_; + SpdySettingsStorage* spdy_settings_; const GURL request_url_; const std::string user_agent_; const HostPortPair endpoint_; @@ -100,6 +113,9 @@ class HttpProxyConnectJob : public ConnectJob { STATE_SSL_CONNECT_COMPLETE, STATE_HTTP_PROXY_CONNECT, STATE_HTTP_PROXY_CONNECT_COMPLETE, + STATE_SPDY_PROXY_CREATE_STREAM, + STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE, + STATE_SPDY_PROXY_CONNECT_COMPLETE, STATE_NONE, }; @@ -127,6 +143,9 @@ class HttpProxyConnectJob : public ConnectJob { int DoHttpProxyConnect(); int DoHttpProxyConnectComplete(int result); + int DoSpdyProxyCreateStream(); + int DoSpdyProxyCreateStreamComplete(int result); + scoped_refptr<HttpProxySocketParams> params_; TCPClientSocketPool* const tcp_pool_; SSLClientSocketPool* const ssl_pool_; @@ -138,6 +157,8 @@ class HttpProxyConnectJob : public ConnectJob { scoped_ptr<ClientSocket> transport_socket_; bool using_spdy_; + scoped_refptr<SpdyStream> spdy_stream_; + DISALLOW_COPY_AND_ASSIGN(HttpProxyConnectJob); }; diff --git a/net/http/http_proxy_client_socket_pool_unittest.cc b/net/http/http_proxy_client_socket_pool_unittest.cc index 4fe8873..71485e9 100644 --- a/net/http/http_proxy_client_socket_pool_unittest.cc +++ b/net/http/http_proxy_client_socket_pool_unittest.cc @@ -19,7 +19,9 @@ #include "net/socket/client_socket_handle.h" #include "net/socket/client_socket_pool_histograms.h" #include "net/socket/socket_test_util.h" +#include "net/spdy/spdy_protocol.h" #include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_test_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { @@ -28,34 +30,48 @@ namespace { const int kMaxSockets = 32; const int kMaxSocketsPerGroup = 6; +const char * const kAuthHeaders[] = { + "proxy-authorization", "Basic Zm9vOmJhcg==" +}; +const int kAuthHeadersSize = arraysize(kAuthHeaders) / 2; enum HttpProxyType { HTTP, - HTTPS + HTTPS, + SPDY }; typedef ::testing::TestWithParam<HttpProxyType> TestWithHttpParam; +} // namespace + class HttpProxyClientSocketPoolTest : public TestWithHttpParam { protected: HttpProxyClientSocketPoolTest() : ssl_config_(), ignored_tcp_socket_params_(new TCPSocketParams( - HostPortPair("proxy", 80), MEDIUM, GURL(), false)), + HostPortPair("proxy", 80), LOWEST, GURL(), false)), ignored_ssl_socket_params_(new SSLSocketParams( ignored_tcp_socket_params_, NULL, NULL, ProxyServer::SCHEME_DIRECT, - "host", ssl_config_, 0, false, false)), + "www.google.com", ssl_config_, 0, false, false)), tcp_histograms_("MockTCP"), tcp_socket_pool_( kMaxSockets, kMaxSocketsPerGroup, &tcp_histograms_, - &tcp_client_socket_factory_), + &socket_factory_), ssl_histograms_("MockSSL"), + ssl_config_service_(new SSLConfigServiceDefaults), + host_resolver_(new MockHostResolver), ssl_socket_pool_(kMaxSockets, kMaxSocketsPerGroup, &ssl_histograms_, - &tcp_client_socket_factory_, - &tcp_socket_pool_), - host_resolver_(new MockHostResolver), + host_resolver_.get(), + NULL /* dnsrr_resolver */, + &socket_factory_, + &tcp_socket_pool_, + NULL, + NULL, + ssl_config_service_.get(), + BoundNetLog().net_log()), http_auth_handler_factory_( HttpAuthHandlerFactory::CreateDefault(host_resolver_.get())), session_(new HttpNetworkSession(host_resolver_.get(), @@ -68,6 +84,8 @@ class HttpProxyClientSocketPoolTest : public TestWithHttpParam { NULL, NULL)), http_proxy_histograms_("HttpProxyUnitTest"), + ssl_data_(NULL), + data_(NULL), pool_(kMaxSockets, kMaxSocketsPerGroup, &http_proxy_histograms_, NULL, @@ -87,7 +105,7 @@ class HttpProxyClientSocketPoolTest : public TestWithHttpParam { } scoped_refptr<TCPSocketParams> GetTcpParams() { - if (GetParam() == HTTPS) + if (GetParam() != HTTP) return scoped_refptr<TCPSocketParams>(); return ignored_tcp_socket_params_; } @@ -105,11 +123,13 @@ class HttpProxyClientSocketPoolTest : public TestWithHttpParam { new HttpProxySocketParams( GetTcpParams(), GetSslParams(), - GURL("http://host/"), + GURL(tunnel ? "https://www.google.com/" : "http://www.google.com"), "", - HostPortPair("host", 80), + HostPortPair("www.google.com", tunnel ? 443 : 80), session_->auth_cache(), session_->http_auth_handler_factory(), + session_->spdy_session_pool(), + session_->mutable_spdy_settings(), tunnel)); } @@ -121,227 +141,363 @@ class HttpProxyClientSocketPoolTest : public TestWithHttpParam { return GetParams(false); } + DeterministicMockClientSocketFactory& socket_factory() { + return socket_factory_; + } + + void 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) { + if (GetParam() == SPDY) + data_ = new DeterministicSocketData(spdy_reads, spdy_reads_count, + spdy_writes, spdy_writes_count); + else + data_ = new DeterministicSocketData(reads, reads_count, writes, + writes_count); + + data_->set_connect_data(MockConnect(async, 0)); + data_->StopAfter(2); // Request / Response + + socket_factory_.AddSocketDataProvider(data_.get()); + + if (GetParam() != HTTP) { + ssl_data_.reset(new SSLSocketDataProvider(async, OK)); + if (GetParam() == SPDY) { + InitializeSpdySsl(); + } + socket_factory_.AddSSLSocketDataProvider(ssl_data_.get()); + } + } + + void InitializeSpdySsl() { + spdy::SpdyFramer::set_enable_compression_default(false); + ssl_data_->next_proto_status = SSLClientSocket::kNextProtoNegotiated; + ssl_data_->next_proto = "spdy/2"; + ssl_data_->was_npn_negotiated = true; + } + + private: SSLConfig ssl_config_; scoped_refptr<TCPSocketParams> ignored_tcp_socket_params_; scoped_refptr<SSLSocketParams> ignored_ssl_socket_params_; ClientSocketPoolHistograms tcp_histograms_; - MockClientSocketFactory tcp_client_socket_factory_; + DeterministicMockClientSocketFactory socket_factory_; MockTCPClientSocketPool tcp_socket_pool_; ClientSocketPoolHistograms ssl_histograms_; - MockSSLClientSocketPool ssl_socket_pool_; - - MockClientSocketFactory socket_factory_; + scoped_refptr<SSLConfigService> ssl_config_service_; scoped_ptr<HostResolver> host_resolver_; + SSLClientSocketPool ssl_socket_pool_; + scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory_; scoped_refptr<HttpNetworkSession> session_; ClientSocketPoolHistograms http_proxy_histograms_; + + protected: + scoped_ptr<SSLSocketDataProvider> ssl_data_; + scoped_refptr<DeterministicSocketData> data_; HttpProxyClientSocketPool pool_; + ClientSocketHandle handle_; + TestCompletionCallback callback_; }; //----------------------------------------------------------------------------- -// All tests are run with three different connection types: SPDY after NPN -// negotiation, SPDY without SSL, and SPDY with SSL. +// All tests are run with three different proxy types: HTTP, HTTPS (non-SPDY) +// and SPDY. INSTANTIATE_TEST_CASE_P(HttpProxyClientSocketPoolTests, HttpProxyClientSocketPoolTest, - ::testing::Values(HTTP, HTTPS)); + ::testing::Values(HTTP, HTTPS, SPDY)); TEST_P(HttpProxyClientSocketPoolTest, NoTunnel) { - StaticSocketDataProvider data; - data.set_connect_data(MockConnect(false, 0)); - tcp_client_socket_factory_.AddSocketDataProvider(&data); + Initialize(false, NULL, 0, NULL, 0, NULL, 0, NULL, 0); - ClientSocketHandle handle; - int rv = handle.Init("a", GetNoTunnelParams(), LOW, NULL, &pool_, + int rv = handle_.Init("a", GetNoTunnelParams(), LOW, NULL, &pool_, BoundNetLog()); EXPECT_EQ(OK, rv); - EXPECT_TRUE(handle.is_initialized()); - EXPECT_TRUE(handle.socket()); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); HttpProxyClientSocket* tunnel_socket = - static_cast<HttpProxyClientSocket*>(handle.socket()); + static_cast<HttpProxyClientSocket*>(handle_.socket()); EXPECT_TRUE(tunnel_socket->IsConnected()); } TEST_P(HttpProxyClientSocketPoolTest, NeedAuth) { MockWrite writes[] = { - MockWrite("CONNECT host:80 HTTP/1.1\r\n" - "Host: host\r\n" - "Proxy-Connection: keep-alive\r\n\r\n"), + MockWrite(true, 0, "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { - // No credentials. - MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), - MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), - MockRead("Content-Length: 10\r\n\r\n"), - MockRead("0123456789"), + // No credentials. + MockRead(true, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead(true, 2, "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead(true, 3, "Content-Length: 10\r\n\r\n"), + MockRead(true, 4, "0123456789"), + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, true), + CreateMockWrite(*rst, 2, true), + }; + scoped_ptr<spdy::SpdyFrame> resp( + ConstructSpdySynReplyError("407 Proxy Authentication Required", 1)); + MockRead spdy_reads[] = { + CreateMockWrite(*resp, 1, true), + MockRead(true, 0, 3) }; - StaticSocketDataProvider data(reads, arraysize(reads), writes, - arraysize(writes)); - tcp_client_socket_factory_.AddSocketDataProvider(&data); + Initialize(false, reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); - ClientSocketHandle handle; - TestCompletionCallback callback; - int rv = handle.Init("a", GetTunnelParams(), LOW, &callback, &pool_, + data_->StopAfter(4); + int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_FALSE(handle.is_initialized()); - EXPECT_FALSE(handle.socket()); - - EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, callback.WaitForResult()); - EXPECT_TRUE(handle.is_initialized()); - EXPECT_TRUE(handle.socket()); - HttpProxyClientSocket* tunnel_socket = - static_cast<HttpProxyClientSocket*>(handle.socket()); - EXPECT_FALSE(tunnel_socket->IsConnected()); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + data_->RunFor(4); + rv = callback_.WaitForResult(); + if (GetParam() != SPDY) { + EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, rv); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); + HttpProxyClientSocket* tunnel_socket = + static_cast<HttpProxyClientSocket*>(handle_.socket()); + EXPECT_FALSE(tunnel_socket->IsConnected()); + EXPECT_FALSE(tunnel_socket->using_spdy()); + } else { + // Proxy auth is not really implemented for SPDY yet + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + } } TEST_P(HttpProxyClientSocketPoolTest, HaveAuth) { + // It's pretty much impossible to make the SPDY case becave synchronously + // so we skip this test for SPDY + if (GetParam() == SPDY) + return; MockWrite writes[] = { - MockWrite(false, - "CONNECT host:80 HTTP/1.1\r\n" - "Host: host\r\n" - "Proxy-Connection: keep-alive\r\n" - "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + MockWrite(false, 0, + "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), }; MockRead reads[] = { - MockRead(false, "HTTP/1.1 200 Connection Established\r\n\r\n"), + MockRead(false, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"), }; - StaticSocketDataProvider data(reads, arraysize(reads), writes, - arraysize(writes)); - data.set_connect_data(MockConnect(false, 0)); - tcp_client_socket_factory_.AddSocketDataProvider(&data); + Initialize(false, reads, arraysize(reads), writes, arraysize(writes), NULL, 0, + NULL, 0); AddAuthToCache(); - ClientSocketHandle handle; - TestCompletionCallback callback; - int rv = handle.Init("a", GetTunnelParams(), LOW, &callback, &pool_, + int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_, BoundNetLog()); EXPECT_EQ(OK, rv); - EXPECT_TRUE(handle.is_initialized()); - EXPECT_TRUE(handle.socket()); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); HttpProxyClientSocket* tunnel_socket = - static_cast<HttpProxyClientSocket*>(handle.socket()); + static_cast<HttpProxyClientSocket*>(handle_.socket()); EXPECT_TRUE(tunnel_socket->IsConnected()); } TEST_P(HttpProxyClientSocketPoolTest, AsyncHaveAuth) { MockWrite writes[] = { - MockWrite("CONNECT host:80 HTTP/1.1\r\n" - "Host: host\r\n" - "Proxy-Connection: keep-alive\r\n" - "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), }; MockRead reads[] = { - MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + MockRead(false, "HTTP/1.1 200 Connection Established\r\n\r\n"), + }; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders, + kAuthHeadersSize, 1)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, true) + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead spdy_reads[] = { + CreateMockRead(*resp, 1, true), + MockRead(true, 0, 2) }; - StaticSocketDataProvider data(reads, arraysize(reads), writes, - arraysize(writes)); - tcp_client_socket_factory_.AddSocketDataProvider(&data); + Initialize(false, reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); AddAuthToCache(); - ClientSocketHandle handle; - TestCompletionCallback callback; - int rv = handle.Init("a", GetTunnelParams(), LOW, &callback, &pool_, + int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_FALSE(handle.is_initialized()); - EXPECT_FALSE(handle.socket()); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); - EXPECT_EQ(OK, callback.WaitForResult()); - EXPECT_TRUE(handle.is_initialized()); - EXPECT_TRUE(handle.socket()); + data_->RunFor(2); + EXPECT_EQ(OK, callback_.WaitForResult()); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); HttpProxyClientSocket* tunnel_socket = - static_cast<HttpProxyClientSocket*>(handle.socket()); + static_cast<HttpProxyClientSocket*>(handle_.socket()); EXPECT_TRUE(tunnel_socket->IsConnected()); } TEST_P(HttpProxyClientSocketPoolTest, TCPError) { - StaticSocketDataProvider data; - data.set_connect_data(MockConnect(true, ERR_CONNECTION_CLOSED)); + if (GetParam() == SPDY) return; + data_ = new DeterministicSocketData(NULL, 0, NULL, 0); + data_->set_connect_data(MockConnect(true, ERR_CONNECTION_CLOSED)); - tcp_client_socket_factory_.AddSocketDataProvider(&data); + socket_factory().AddSocketDataProvider(data_.get()); - ClientSocketHandle handle; - TestCompletionCallback callback; - int rv = handle.Init("a", GetTunnelParams(), LOW, &callback, &pool_, + int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_FALSE(handle.is_initialized()); - EXPECT_FALSE(handle.socket()); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); - if (GetParam() == HTTP) - EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, callback.WaitForResult()); - else - EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult()); + EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, callback_.WaitForResult()); - EXPECT_FALSE(handle.is_initialized()); - EXPECT_FALSE(handle.socket()); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); +} + +TEST_P(HttpProxyClientSocketPoolTest, SSLError) { + if (GetParam() == HTTP) return; + data_ = new DeterministicSocketData(NULL, 0, NULL, 0); + data_->set_connect_data(MockConnect(true, OK)); + socket_factory().AddSocketDataProvider(data_.get()); + + ssl_data_.reset(new SSLSocketDataProvider(true, + ERR_CERT_AUTHORITY_INVALID)); + if (GetParam() == SPDY) { + InitializeSpdySsl(); + } + socket_factory().AddSSLSocketDataProvider(ssl_data_.get()); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + EXPECT_EQ(ERR_PROXY_CERTIFICATE_INVALID, callback_.WaitForResult()); + + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); +} + +TEST_P(HttpProxyClientSocketPoolTest, SslClientAuth) { + if (GetParam() == HTTP) return; + data_ = new DeterministicSocketData(NULL, 0, NULL, 0); + data_->set_connect_data(MockConnect(true, OK)); + socket_factory().AddSocketDataProvider(data_.get()); + + ssl_data_.reset(new SSLSocketDataProvider(true, + ERR_SSL_CLIENT_AUTH_CERT_NEEDED)); + if (GetParam() == SPDY) { + InitializeSpdySsl(); + } + socket_factory().AddSSLSocketDataProvider(ssl_data_.get()); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + EXPECT_EQ(ERR_PROXY_AUTH_UNSUPPORTED, callback_.WaitForResult()); + + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); } TEST_P(HttpProxyClientSocketPoolTest, TunnelUnexpectedClose) { MockWrite writes[] = { - MockWrite("CONNECT host:80 HTTP/1.1\r\n" - "Host: host\r\n" - "Proxy-Connection: keep-alive\r\n" - "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + MockWrite(true, 0, + "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), }; MockRead reads[] = { - MockRead("HTTP/1.1 200 Conn"), - MockRead(true, ERR_CONNECTION_CLOSED), + MockRead(true, 1, "HTTP/1.1 200 Conn"), + MockRead(true, ERR_CONNECTION_CLOSED, 2), + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders, + kAuthHeadersSize, 1)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, true) + }; + MockRead spdy_reads[] = { + MockRead(true, ERR_CONNECTION_CLOSED, 1), }; - StaticSocketDataProvider data(reads, arraysize(reads), writes, - arraysize(writes)); - tcp_client_socket_factory_.AddSocketDataProvider(&data); + Initialize(false, reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); AddAuthToCache(); - ClientSocketHandle handle; - TestCompletionCallback callback; - int rv = handle.Init("a", GetTunnelParams(), LOW, &callback, &pool_, + int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_FALSE(handle.is_initialized()); - EXPECT_FALSE(handle.socket()); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); - EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult()); - EXPECT_FALSE(handle.is_initialized()); - EXPECT_FALSE(handle.socket()); + data_->RunFor(3); + EXPECT_EQ(ERR_CONNECTION_CLOSED, callback_.WaitForResult()); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); } TEST_P(HttpProxyClientSocketPoolTest, TunnelSetupError) { MockWrite writes[] = { - MockWrite("CONNECT host:80 HTTP/1.1\r\n" - "Host: host\r\n" - "Proxy-Connection: keep-alive\r\n" - "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + MockWrite(true, 0, + "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), }; MockRead reads[] = { - MockRead("HTTP/1.1 304 Not Modified\r\n\r\n"), + MockRead(true, 1, "HTTP/1.1 304 Not Modified\r\n\r\n"), + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders, + kAuthHeadersSize, 1)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, true), + CreateMockWrite(*rst, 2, true), + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdySynReplyError(1)); + MockRead spdy_reads[] = { + CreateMockRead(*resp, 1, true), + MockRead(true, 0, 3), }; - StaticSocketDataProvider data(reads, arraysize(reads), writes, - arraysize(writes)); - tcp_client_socket_factory_.AddSocketDataProvider(&data); + Initialize(false, reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); AddAuthToCache(); - ClientSocketHandle handle; - TestCompletionCallback callback; - int rv = handle.Init("a", GetTunnelParams(), LOW, &callback, &pool_, + int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_FALSE(handle.is_initialized()); - EXPECT_FALSE(handle.socket()); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + data_->RunFor(2); - EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, callback.WaitForResult()); - EXPECT_FALSE(handle.is_initialized()); - EXPECT_FALSE(handle.socket()); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, callback_.WaitForResult()); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); } // It would be nice to also test the timeouts in HttpProxyClientSocketPool. -} // namespace - } // namespace net diff --git a/net/http/http_stream_request.cc b/net/http/http_stream_request.cc index 35832c7..f60a86c 100644 --- a/net/http/http_stream_request.cc +++ b/net/http/http_stream_request.cc @@ -460,9 +460,9 @@ int HttpStreamRequest::DoInitConnection() { } // Check next if we have a spdy session for this proxy. If so, then go // straight to using that. - if (proxy_info()->is_https()) { + if (IsHttpsProxyAndHttpUrl()) { HostPortProxyPair proxy(proxy_info()->proxy_server().host_port_pair(), - proxy_info()->proxy_server()); + ProxyServer::Direct()); if (session_->spdy_session_pool()->HasSession(proxy)) { using_spdy_ = true; next_state_ = STATE_CREATE_STREAM; @@ -537,6 +537,8 @@ int HttpStreamRequest::DoInitConnection() { endpoint_, session_->auth_cache(), session_->http_auth_handler_factory(), + session_->spdy_session_pool(), + session_->mutable_spdy_settings(), using_ssl_); } else { DCHECK(proxy_info()->is_socks()); @@ -725,10 +727,11 @@ int HttpStreamRequest::DoCreateStream() { // connection, or it might be a SPDY session through an HTTP or HTTPS proxy. spdy_session = spdy_pool->Get(pair, session_->mutable_spdy_settings(), net_log_); - } else if (proxy_info()->is_https()) { + } else if (IsHttpsProxyAndHttpUrl()) { // If we don't have a direct SPDY session, and we're using an HTTPS // proxy, then we might have a SPDY session to the proxy - pair = HostPortProxyPair(proxy_server.host_port_pair(), proxy_server); + pair = HostPortProxyPair(proxy_server.host_port_pair(), + ProxyServer::Direct()); if (spdy_pool->HasSession(pair)) { spdy_session = spdy_pool->Get(pair, session_->mutable_spdy_settings(), net_log_); @@ -751,7 +754,8 @@ int HttpStreamRequest::DoCreateStream() { if (spdy_session->IsClosed()) return ERR_CONNECTION_CLOSED; - stream_.reset(new SpdyHttpStream(spdy_session, direct)); + bool useRelativeUrl = direct || request_info().url.SchemeIs("https"); + stream_.reset(new SpdyHttpStream(spdy_session, useRelativeUrl)); return OK; } @@ -798,6 +802,10 @@ void HttpStreamRequest::SetSocketMotivation() { // TODO(mbelshe): Add other motivations (like EARLY_LOAD_MOTIVATED). } +bool HttpStreamRequest::IsHttpsProxyAndHttpUrl() { + return proxy_info()->is_https() && request_info().url.SchemeIs("http"); +} + // Returns a newly create SSLSocketParams, and sets several // fields of ssl_config_. scoped_refptr<SSLSocketParams> HttpStreamRequest::GenerateSslParams( diff --git a/net/http/http_stream_request.h b/net/http/http_stream_request.h index 5a3ccb7..cad0e59 100644 --- a/net/http/http_stream_request.h +++ b/net/http/http_stream_request.h @@ -112,6 +112,8 @@ class HttpStreamRequest : public StreamFactory::StreamRequestJob { // Set the motivation for this request onto the underlying socket. void SetSocketMotivation(); + bool IsHttpsProxyAndHttpUrl(); + // Returns a newly create SSLSocketParams, and sets several // fields of ssl_config_. scoped_refptr<SSLSocketParams> GenerateSslParams( diff --git a/net/socket/socket.h b/net/socket/socket.h index c3465ea..da17ce4 100644 --- a/net/socket/socket.h +++ b/net/socket/socket.h @@ -26,8 +26,8 @@ class Socket { // case the result will be passed to the callback when available. If the // operation is not completed immediately, the socket acquires a reference to // the provided buffer until the callback is invoked or the socket is - // destroyed. If the socket is closed before the read completes, the callback - // will not be invoked. + // closed. If the socket is Disconnected before the read completes, the + // callback will not be invoked. virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback) = 0; @@ -40,9 +40,9 @@ class Socket { // case the result will be passed to the callback when available. If the // operation is not completed immediately, the socket acquires a reference to // the provided buffer until the callback is invoked or the socket is - // destroyed. Implementations of this method should not modify the contents + // closed. Implementations of this method should not modify the contents // of the actual buffer that is written to the socket. If the socket is - // closed before the write completes, the callback will not be invoked. + // Disconnected before the write completes, the callback will not be invoked. virtual int Write(IOBuffer* buf, int buf_len, CompletionCallback* callback) = 0; diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc index cba4b1c..414d395 100644 --- a/net/socket/socket_test_util.cc +++ b/net/socket/socket_test_util.cc @@ -127,7 +127,6 @@ void MockClientSocket::GetSSLInfo(net::SSLInfo* ssl_info) { void MockClientSocket::GetSSLCertRequestInfo( net::SSLCertRequestInfo* cert_request_info) { - NOTREACHED(); } SSLClientSocket::NextProtoStatus @@ -1235,100 +1234,4 @@ const char kSOCKS5OkResponse[] = { 0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x00, 0x50 }; const int kSOCKS5OkResponseLength = arraysize(kSOCKS5OkResponse); -MockSSLClientSocketPool::MockSSLClientSocketPool( - int max_sockets, - int max_sockets_per_group, - ClientSocketPoolHistograms* histograms, - ClientSocketFactory* socket_factory, - TCPClientSocketPool* tcp_pool) - : SSLClientSocketPool(max_sockets, max_sockets_per_group, histograms, - NULL, NULL, socket_factory, - tcp_pool, - NULL, NULL, NULL, NULL), - client_socket_factory_(socket_factory), - release_count_(0), - cancel_count_(0) { -} - -int MockSSLClientSocketPool::RequestSocket(const std::string& group_name, - const void* socket_params, - RequestPriority priority, - ClientSocketHandle* handle, - CompletionCallback* callback, - const BoundNetLog& net_log) { - ClientSocket* socket = client_socket_factory_->CreateTCPClientSocket( - AddressList(), net_log.net_log(), net::NetLog::Source()); - MockConnectJob* job = new MockConnectJob(socket, handle, callback); - job_list_.push_back(job); - handle->set_pool_id(1); - return job->Connect(); -} - -void MockSSLClientSocketPool::CancelRequest(const std::string& group_name, - ClientSocketHandle* handle) { - std::vector<MockConnectJob*>::iterator i; - for (i = job_list_.begin(); i != job_list_.end(); ++i) { - if ((*i)->CancelHandle(handle)) { - cancel_count_++; - break; - } - } -} - -void MockSSLClientSocketPool::ReleaseSocket(const std::string& group_name, - ClientSocket* socket, int id) { - EXPECT_EQ(1, id); - release_count_++; - delete socket; -} - -MockSSLClientSocketPool::~MockSSLClientSocketPool() {} - -MockSSLClientSocketPool::MockConnectJob::MockConnectJob( - ClientSocket* socket, - ClientSocketHandle* handle, - CompletionCallback* callback) - : socket_(socket), - handle_(handle), - user_callback_(callback), - ALLOW_THIS_IN_INITIALIZER_LIST( - connect_callback_(this, &MockConnectJob::OnConnect)) { -} - -int MockSSLClientSocketPool::MockConnectJob::Connect() { - int rv = socket_->Connect(&connect_callback_); - if (rv == OK) { - user_callback_ = NULL; - OnConnect(OK); - } - return rv; -} - -bool MockSSLClientSocketPool::MockConnectJob::CancelHandle( - const ClientSocketHandle* handle) { - if (handle != handle_) - return false; - socket_.reset(); - handle_ = NULL; - user_callback_ = NULL; - return true; -} - -void MockSSLClientSocketPool::MockConnectJob::OnConnect(int rv) { - if (!socket_.get()) - return; - if (rv == OK) { - handle_->set_socket(socket_.release()); - } else { - socket_.reset(); - } - - handle_ = NULL; - - if (user_callback_) { - CompletionCallback* callback = user_callback_; - user_callback_ = NULL; - callback->Run(rv); - } -} } // namespace net diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h index 7293c05..eb54b84 100644 --- a/net/socket/socket_test_util.h +++ b/net/socket/socket_test_util.h @@ -92,6 +92,11 @@ struct MockRead { result(0), data(data), data_len(data_len), sequence_number(0), time_stamp(base::Time::Now()) { } + // Read success (inferred data length) with sequence information. + MockRead(bool async, int seq, const char* data) : async(async), + result(0), data(data), data_len(strlen(data)), sequence_number(seq), + time_stamp(base::Time::Now()) { } + // Read success with sequence information. MockRead(bool async, const char* data, int data_len, int seq) : async(async), result(0), data(data), data_len(data_len), sequence_number(seq), @@ -923,62 +928,6 @@ extern const int kSOCKS5OkRequestLength; extern const char kSOCKS5OkResponse[]; extern const int kSOCKS5OkResponseLength; -class MockSSLClientSocketPool : public SSLClientSocketPool { - public: - class MockConnectJob { - public: - MockConnectJob(ClientSocket* socket, ClientSocketHandle* handle, - CompletionCallback* callback); - - int Connect(); - bool CancelHandle(const ClientSocketHandle* handle); - - private: - void OnConnect(int rv); - - scoped_ptr<ClientSocket> socket_; - ClientSocketHandle* handle_; - CompletionCallback* user_callback_; - CompletionCallbackImpl<MockConnectJob> connect_callback_; - - DISALLOW_COPY_AND_ASSIGN(MockConnectJob); - }; - - MockSSLClientSocketPool( - int max_sockets, - int max_sockets_per_group, - ClientSocketPoolHistograms* histograms, - ClientSocketFactory* socket_factory, - TCPClientSocketPool* tcp_pool); - - virtual ~MockSSLClientSocketPool(); - - int release_count() const { return release_count_; } - int cancel_count() const { return cancel_count_; } - - // SSLClientSocketPool methods. - virtual int RequestSocket(const std::string& group_name, - const void* socket_params, - RequestPriority priority, - ClientSocketHandle* handle, - CompletionCallback* callback, - const BoundNetLog& net_log); - - virtual void CancelRequest(const std::string& group_name, - ClientSocketHandle* handle); - virtual void ReleaseSocket(const std::string& group_name, - ClientSocket* socket, int id); - - private: - ClientSocketFactory* client_socket_factory_; - int release_count_; - int cancel_count_; - ScopedVector<MockConnectJob> job_list_; - - DISALLOW_COPY_AND_ASSIGN(MockSSLClientSocketPool); -}; - - } // namespace net #endif // NET_SOCKET_SOCKET_TEST_UTIL_H_ diff --git a/net/socket/ssl_client_socket_pool_unittest.cc b/net/socket/ssl_client_socket_pool_unittest.cc index 2cc564bf..755bd44 100644 --- a/net/socket/ssl_client_socket_pool_unittest.cc +++ b/net/socket/ssl_client_socket_pool_unittest.cc @@ -71,6 +71,8 @@ class SSLClientSocketPoolTest : public testing::Test { HostPortPair("host", 80), session_->auth_cache(), session_->http_auth_handler_factory(), + session_->spdy_session_pool(), + session_->mutable_spdy_settings(), true)), http_proxy_histograms_("MockHttpProxy"), http_proxy_socket_pool_( diff --git a/net/spdy/spdy_framer.h b/net/spdy/spdy_framer.h index 00ddac0..9b290cd 100644 --- a/net/spdy/spdy_framer.h +++ b/net/spdy/spdy_framer.h @@ -24,6 +24,7 @@ typedef struct z_stream_s z_stream; // Forward declaration for zlib. namespace net { +class HttpProxyClientSocketPoolTest; class HttpNetworkLayer; class HttpNetworkTransactionTest; class SpdyHttpStreamTest; @@ -251,6 +252,7 @@ class SpdyFramer { FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, UnclosedStreamDataCompressors); friend class net::HttpNetworkLayer; // This is temporary for the server. friend class net::HttpNetworkTransactionTest; + friend class net::HttpProxyClientSocketPoolTest; friend class net::SpdyHttpStreamTest; friend class net::SpdyNetworkTransactionTest; friend class net::SpdyProxyClientSocketTest; diff --git a/net/spdy/spdy_proxy_client_socket.cc b/net/spdy/spdy_proxy_client_socket.cc index 588af81..d36b6306 100644 --- a/net/spdy/spdy_proxy_client_socket.cc +++ b/net/spdy/spdy_proxy_client_socket.cc @@ -30,7 +30,7 @@ SpdyProxyClientSocket::SpdyProxyClientSocket( HttpAuthHandlerFactory* auth_handler_factory) : ALLOW_THIS_IN_INITIALIZER_LIST( io_callback_(this, &SpdyProxyClientSocket::OnIOComplete)), - next_state_(STATE_NONE), + next_state_(STATE_DISCONNECTED), spdy_stream_(spdy_stream), read_callback_(NULL), write_callback_(NULL), @@ -70,10 +70,10 @@ SpdyProxyClientSocket::~SpdyProxyClientSocket() { // the HTTPS Proxy tunnel failure from an HTTP Proxy tunnel failure. int SpdyProxyClientSocket::Connect(CompletionCallback* callback) { DCHECK(!read_callback_); - if (next_state_ == STATE_DONE) + if (next_state_ == STATE_OPEN) return OK; - DCHECK_EQ(STATE_NONE, next_state_); + DCHECK_EQ(STATE_DISCONNECTED, next_state_); next_state_ = STATE_GENERATE_AUTH_TOKEN; int rv = DoLoop(OK); @@ -83,7 +83,16 @@ int SpdyProxyClientSocket::Connect(CompletionCallback* callback) { } void SpdyProxyClientSocket::Disconnect() { - next_state_ = STATE_NONE; + read_buffer_.clear(); + user_buffer_ = NULL; + read_callback_ = NULL; + + write_buffer_len_ = 0; + write_bytes_outstanding_ = 0; + write_callback_ = NULL; + + next_state_ = STATE_DISCONNECTED; + if (spdy_stream_) // This will cause OnClose to be invoked, which takes care of // cleaning up all the internal state. @@ -91,8 +100,7 @@ void SpdyProxyClientSocket::Disconnect() { } bool SpdyProxyClientSocket::IsConnected() const { - return next_state_ == STATE_DONE && spdy_stream_ != NULL && - !spdy_stream_->closed(); + return next_state_ == STATE_OPEN || next_state_ == STATE_CLOSED; } bool SpdyProxyClientSocket::IsConnectedAndIdle() const { @@ -116,14 +124,17 @@ int SpdyProxyClientSocket::Read(IOBuffer* buf, int buf_len, DCHECK(!read_callback_); DCHECK(!user_buffer_); - if (!spdy_stream_) { + if (next_state_ == STATE_DISCONNECTED) + return ERR_SOCKET_NOT_CONNECTED; + + if (!spdy_stream_ && read_buffer_.empty()) { if (eof_has_been_read_) return ERR_CONNECTION_CLOSED; eof_has_been_read_ = true; return 0; } - DCHECK(next_state_ == STATE_DONE); + DCHECK(next_state_ == STATE_OPEN || next_state_ == STATE_CLOSED); DCHECK(buf); user_buffer_ = new DrainableIOBuffer(buf, buf_len); int result = PopulateUserReadBuffer(); @@ -146,7 +157,7 @@ int SpdyProxyClientSocket::PopulateUserReadBuffer() { data->BytesRemaining()); memcpy(user_buffer_->data(), data->data(), bytes_to_copy); user_buffer_->DidConsume(bytes_to_copy); - if (data->BytesRemaining() == 0) { + if (data->BytesRemaining() == bytes_to_copy) { // Consumed all data from this buffer read_buffer_.pop_front(); } else { @@ -160,6 +171,9 @@ int SpdyProxyClientSocket::PopulateUserReadBuffer() { int SpdyProxyClientSocket::Write(IOBuffer* buf, int buf_len, CompletionCallback* callback) { DCHECK(!write_callback_); + if (next_state_ == STATE_DISCONNECTED) + return ERR_SOCKET_NOT_CONNECTED; + if (!spdy_stream_) return ERR_CONNECTION_CLOSED; @@ -214,7 +228,7 @@ int SpdyProxyClientSocket::GetPeerAddress(AddressList* address) const { } void SpdyProxyClientSocket::OnIOComplete(int result) { - DCHECK_NE(STATE_NONE, next_state_); + DCHECK_NE(STATE_DISCONNECTED, next_state_); int rv = DoLoop(result); if (rv != ERR_IO_PENDING) { CompletionCallback* c = read_callback_; @@ -224,11 +238,11 @@ void SpdyProxyClientSocket::OnIOComplete(int result) { } int SpdyProxyClientSocket::DoLoop(int last_io_result) { - DCHECK_NE(next_state_, STATE_NONE); + DCHECK_NE(next_state_, STATE_DISCONNECTED); int rv = last_io_result; do { State state = next_state_; - next_state_ = STATE_NONE; + next_state_ = STATE_DISCONNECTED; switch (state) { case STATE_GENERATE_AUTH_TOKEN: DCHECK_EQ(OK, rv); @@ -258,8 +272,8 @@ int SpdyProxyClientSocket::DoLoop(int last_io_result) { rv = ERR_UNEXPECTED; break; } - } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE && - next_state_ != STATE_DONE); + } while (rv != ERR_IO_PENDING && next_state_ != STATE_DISCONNECTED && + next_state_ != STATE_OPEN); return rv; } @@ -322,11 +336,11 @@ int SpdyProxyClientSocket::DoReadReplyComplete(int result) { if (result < 0) return result; - next_state_ = STATE_DONE; // Require the "HTTP/1.x" status line for SSL CONNECT. if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0)) return ERR_TUNNEL_CONNECTION_FAILED; + next_state_ = STATE_OPEN; if (net_log_.IsLoggingAll()) { net_log_.AddEvent( NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, @@ -417,21 +431,34 @@ void SpdyProxyClientSocket::OnDataSent(int length) { void SpdyProxyClientSocket::OnClose(int status) { DCHECK(spdy_stream_); + was_ever_used_ = spdy_stream_->WasEverUsed(); + spdy_stream_ = NULL; + + bool connecting = next_state_ != STATE_DISCONNECTED && + next_state_ < STATE_OPEN; + if (next_state_ == STATE_OPEN) + next_state_ = STATE_CLOSED; + else + next_state_ = STATE_DISCONNECTED; + + CompletionCallback* write_callback = write_callback_; + write_callback_ = NULL; + write_buffer_len_ = 0; + write_bytes_outstanding_ = 0; + // If we're in the middle of connecting, we need to make sure // we invoke the connect callback. - CompletionCallback* connect_callback = NULL; - if (next_state_ != STATE_NONE && next_state_ != STATE_DONE) { + if (connecting) { DCHECK(read_callback_); - connect_callback = read_callback_; + CompletionCallback* read_callback = read_callback_; + read_callback_ = NULL; + read_callback->Run(status); + } else if (read_callback_) { + // If we have a read_callback, the we need to make sure we call it back + OnDataReceived(NULL, 0); } - was_ever_used_ = spdy_stream_->WasEverUsed(); - spdy_stream_ = NULL; - read_callback_ = NULL; - write_callback_ = NULL; - user_buffer_ = NULL; - read_buffer_.empty(); - if (connect_callback) - connect_callback->Run(status); + if (write_callback) + write_callback->Run(ERR_CONNECTION_CLOSED); } } // namespace net diff --git a/net/spdy/spdy_proxy_client_socket.h b/net/spdy/spdy_proxy_client_socket.h index 30062b0..b993b5e 100644 --- a/net/spdy/spdy_proxy_client_socket.h +++ b/net/spdy/spdy_proxy_client_socket.h @@ -114,13 +114,14 @@ class SpdyProxyClientSocket : public ClientSocket, public SpdyStream::Delegate { private: enum State { - STATE_NONE, + STATE_DISCONNECTED, STATE_GENERATE_AUTH_TOKEN, STATE_GENERATE_AUTH_TOKEN_COMPLETE, STATE_SEND_REQUEST, STATE_SEND_REQUEST_COMPLETE, STATE_READ_REPLY_COMPLETE, - STATE_DONE, + STATE_OPEN, + STATE_CLOSED }; void OnIOComplete(int result); diff --git a/net/spdy/spdy_proxy_client_socket_unittest.cc b/net/spdy/spdy_proxy_client_socket_unittest.cc index c3df858..9639036 100644 --- a/net/spdy/spdy_proxy_client_socket_unittest.cc +++ b/net/spdy/spdy_proxy_client_socket_unittest.cc @@ -942,10 +942,85 @@ TEST_F(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsZero) { AssertConnectSucceeds(); + Run(1); + + ASSERT_EQ(0, sock_->Read(NULL, 1, NULL)); + ASSERT_EQ(ERR_CONNECTION_CLOSED, sock_->Read(NULL, 1, NULL)); + ASSERT_EQ(ERR_CONNECTION_CLOSED, sock_->Read(NULL, 1, NULL)); +} + +// Read pending when socket is closed should return 0 +TEST_F(SpdyProxyClientSocketTest, PendingReadOnCloseReturnsZero) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, false), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, true), + MockRead(true, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertReadStarts(kMsg1, kLen1); + + Run(1); + + ASSERT_EQ(0, read_callback_.WaitForResult()); +} + +// Reading from a disconnected socket is an error +TEST_F(SpdyProxyClientSocketTest, ReadOnDisconnectSocketReturnsNotConnected) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, false), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, true), + MockRead(true, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + sock_->Disconnect(); + ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->Read(NULL, 1, NULL)); +} + +// Reading buffered data from an already closed socket should return +// buffered data, then 0. +TEST_F(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsBufferedData) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, false), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, true), + CreateMockRead(*msg1, 2, true), + MockRead(true, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(2); + + AssertSyncReadEquals(kMsg1, kLen1); ASSERT_EQ(0, sock_->Read(NULL, 1, NULL)); ASSERT_EQ(ERR_CONNECTION_CLOSED, sock_->Read(NULL, 1, NULL)); + // Verify that read *still* returns ERR_CONNECTION_CLOSED ASSERT_EQ(ERR_CONNECTION_CLOSED, sock_->Read(NULL, 1, NULL)); } @@ -972,9 +1047,60 @@ TEST_F(SpdyProxyClientSocketTest, WriteOnClosedStream) { EXPECT_EQ(ERR_CONNECTION_CLOSED, sock_->Write(buf, buf->size(), NULL)); } -// ----------- Pending read/write when closed +// Calling Write() on a disconnected socket is an error +TEST_F(SpdyProxyClientSocketTest, WriteOnDisconnectedSocket) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, false), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, true), + MockRead(true, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + sock_->Disconnect(); + + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->Write(buf, buf->size(), NULL)); +} // If the socket is closed with a pending Write(), the callback +// should be called with ERR_CONNECTION_CLOSED. +TEST_F(SpdyProxyClientSocketTest, WritePendingOnClose) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, false), + MockWrite(true, ERR_IO_PENDING, 2), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, true), + MockRead(true, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_IO_PENDING, sock_->Write(buf, buf->size(), &write_callback_)); + + Run(1); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, write_callback_.WaitForResult()); +} + +// If the socket is Disconnected with a pending Write(), the callback // should not be called. TEST_F(SpdyProxyClientSocketTest, DisconnectWithWritePending) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); @@ -1004,7 +1130,7 @@ TEST_F(SpdyProxyClientSocketTest, DisconnectWithWritePending) { EXPECT_FALSE(write_callback_.have_result()); } -// If the socket is closed with a pending Read(), the callback +// If the socket is Disconnected with a pending Read(), the callback // should not be called. TEST_F(SpdyProxyClientSocketTest, DisconnectWithReadPending) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc index 1727717..3dda1edd 100644 --- a/net/spdy/spdy_test_util.cc +++ b/net/spdy/spdy_test_util.cc @@ -407,6 +407,28 @@ spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[], arraysize(kStandardGetHeaders)); } +// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request. +spdy::SpdyFrame* ConstructSpdyConnect(const char* const extra_headers[], + int extra_header_count, + int stream_id) { + const char* const kConnectHeaders[] = { + "method", "CONNECT", + "url", "www.google.com:443", + "host", "www.google.com", + "version", "HTTP/1.1", + "proxy-connection", "keep-alive", + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + /*compressed*/ false, + stream_id, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + kConnectHeaders, + arraysize(kConnectHeaders)); +} + // Constructs a standard SPDY push SYN packet. // |extra_headers| are the extra header-value pairs, which typically // will vary the most between calls. @@ -524,6 +546,36 @@ spdy::SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id) { arraysize(kStandardGetHeaders)); } +// Constructs a standard SPDY SYN_REPLY packet with an Internal Server +// Error status code. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdySynReplyError(int stream_id) { + return ConstructSpdySynReplyError("500 Internal Server Error", 1); +} + +// Constructs a standard SPDY SYN_REPLY packet with the specified status code. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdySynReplyError(const char* const status, + int stream_id) { + static const char* const kStandardGetHeaders[] = { + "hello", + "bye", + "status", + status, + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(NULL, + 0, + false, + stream_id, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders)); +} + // Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET. // |extra_headers| are the extra header-value pairs, which typically // will vary the most between calls. @@ -629,6 +681,15 @@ spdy::SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data, stream_id, data, len, fin ? spdy::DATA_FLAG_FIN : spdy::DATA_FLAG_NONE); } +// Wraps |frame| in the payload of a data frame in stream |stream_id|. +spdy::SpdyFrame* ConstructWrappedSpdyFrame( + const scoped_ptr<spdy::SpdyFrame>& frame, + int stream_id) { + return ConstructSpdyBodyFrame(stream_id, frame->data(), + frame->length() + spdy::SpdyFrame::size(), + false); +} + // Construct an expected SPDY reply string. // |extra_headers| are the extra header-value pairs, which typically // will vary the most between calls. diff --git a/net/spdy/spdy_test_util.h b/net/spdy/spdy_test_util.h index 6304c74..8c3a1e5 100644 --- a/net/spdy/spdy_test_util.h +++ b/net/spdy/spdy_test_util.h @@ -212,6 +212,11 @@ spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[], RequestPriority request_priority, bool direct); +// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request. +spdy::SpdyFrame* ConstructSpdyConnect(const char* const extra_headers[], + int extra_header_count, + int stream_id); + // Constructs a standard SPDY push SYN packet. // |extra_headers| are the extra header-value pairs, which typically // will vary the most between calls. @@ -248,6 +253,15 @@ spdy::SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[], // Returns a SpdyFrame. spdy::SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id); +// Constructs a standard SPDY SYN_REPLY packet with an Internal Server +// Error status code. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdySynReplyError(int stream_id); + +// Constructs a standard SPDY SYN_REPLY packet with the specified status code. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdySynReplyError(const char* const status, + int stream_id); // Constructs a standard SPDY POST SYN packet. // |extra_headers| are the extra header-value pairs, which typically // will vary the most between calls. @@ -271,6 +285,10 @@ spdy::SpdyFrame* ConstructSpdyBodyFrame(int stream_id, spdy::SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data, uint32 len, bool fin); +// Wraps |frame| in the payload of a data frame in stream |stream_id|. +spdy::SpdyFrame* ConstructWrappedSpdyFrame( + const scoped_ptr<spdy::SpdyFrame>& frame, int stream_id); + // Create an async MockWrite from the given SpdyFrame. MockWrite CreateMockWrite(const spdy::SpdyFrame& req); |