diff options
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 422 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket.cc | 45 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket.h | 17 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket_pool_unittest.cc | 30 | ||||
-rw-r--r-- | net/http/http_proxy_utils.cc | 20 | ||||
-rw-r--r-- | net/http/http_proxy_utils.h | 11 | ||||
-rw-r--r-- | net/http/http_stream_factory_impl_job.cc | 18 | ||||
-rw-r--r-- | net/http/proxy_client_socket.h | 10 | ||||
-rw-r--r-- | net/spdy/spdy_proxy_client_socket.cc | 23 | ||||
-rw-r--r-- | net/spdy/spdy_proxy_client_socket.h | 10 | ||||
-rw-r--r-- | net/spdy/spdy_proxy_client_socket_unittest.cc | 74 |
11 files changed, 583 insertions, 97 deletions
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index bcc83bb..86ea90c 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -1682,7 +1682,9 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) { MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" "Host: www.google.com\r\n" "Proxy-Connection: keep-alive\r\n\r\n"), + }; + MockWrite data_writes2[] = { // After calling trans->RestartWithAuth(), this is the request we should // be issuing -- the final header line contains the credentials. MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" @@ -1702,7 +1704,9 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) { MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), MockRead("Proxy-Connection: close\r\n\r\n"), + }; + MockRead data_reads2[] = { MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), MockRead("HTTP/1.1 200 OK\r\n"), @@ -1713,7 +1717,10 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) { StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); SSLSocketDataProvider ssl(true, OK); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); @@ -1926,6 +1933,368 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyCancelTunnel) { session->CloseAllConnections(); } +// Test the request-challenge-retry sequence for basic auth, over a connection +// that requires a restart when setting up an SSL tunnel. +TEST_F(HttpNetworkTransactionTest, BasicAuthHttpsProxyNoKeepAlive) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + // when the no authentication data flag is set. + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + // Configure against https proxy server "myproxy:70". + SessionDependencies session_deps( + ProxyService::CreateFixed("https://myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockWrite data_writes2[] = { + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + 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"), + + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + // The proxy responds to the connect with a 407, using a persistent + // connection. + MockRead data_reads1[] = { + // No credentials. + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Proxy-Connection: close\r\n\r\n"), + }; + + MockRead data_reads2[] = { + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead(false, "hello"), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + SSLSocketDataProvider proxy(true, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy); + SSLSocketDataProvider proxy2(true, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy2); + SSLSocketDataProvider server(true, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&server); + + TestOldCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, &callback1, log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestOldCompletionCallback callback2; + + rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar), &callback2); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(5, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + // The password prompt info should not be set. + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + trans.reset(); + session->CloseAllConnections(); +} + +// Test the request-challenge-retry sequence for basic auth, over a keep-alive +// proxy connection, when setting up an SSL tunnel. +TEST_F(HttpNetworkTransactionTest, BasicAuthHttpsProxyKeepAlive) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + // Ensure that proxy authentication is attempted even + // when the no authentication data flag is set. + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + // Configure against https proxy server "myproxy:70". + SessionDependencies session_deps( + ProxyService::CreateFixed("https://myproxy: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)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + 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 Zm9vOmJheg==\r\n\r\n"), + }; + + // The proxy responds to the connect with a 407, using a persistent + // connection. + MockRead data_reads1[] = { + // 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"), + + // Wrong credentials (wrong password). + 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"), + // No response body because the test stops reading here. + MockRead(false, ERR_UNEXPECTED), // Should not be reached. + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(true, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestOldCompletionCallback callback1; + + int rv = trans->Start(&request, &callback1, log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_EQ(10, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestOldCompletionCallback callback2; + + // Wrong password (should be "bar"). + rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBaz), &callback2); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_EQ(10, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + // Flush the idle socket before the NetLog and HttpNetworkTransaction go + // out of scope. + session->CloseAllConnections(); +} + +// Test the request-challenge-retry sequence for basic auth, through +// a SPDY proxy over a single SPDY session. +TEST_F(HttpNetworkTransactionTest, BasicAuthSpdyProxy) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + // when the no authentication data flag is set. + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + // Configure against https proxy server "myproxy:70". + SessionDependencies session_deps( + ProxyService::CreateFixed("https://myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should try to establish tunnel. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL)); + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + const char* const kAuthCredentials[] = { + "proxy-authorization", "Basic Zm9vOmJhcg==", + }; + scoped_ptr<spdy::SpdyFrame> connect2( + ConstructSpdyConnect(kAuthCredentials, arraysize(kAuthCredentials)/2, 3)); + // 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(3, get, strlen(get), false)); + + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, true), + CreateMockWrite(*rst, 2, true), + CreateMockWrite(*connect2, 3), + CreateMockWrite(*wrapped_get, 5) + }; + + // The proxy responds to the connect with a 407, using a persistent + // connection. + const char* const kAuthChallenge[] = { + "status", "407 Proxy Authentication Required", + "version", "HTTP/1.1", + "proxy-authenticate", "Basic realm=\"MyRealm1\"", + }; + + scoped_ptr<spdy::SpdyFrame> conn_auth_resp( + ConstructSpdyControlFrame(NULL, + 0, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kAuthChallenge, + arraysize(kAuthChallenge))); + + scoped_ptr<spdy::SpdyFrame> conn_resp(ConstructSpdyGetSynReply(NULL, 0, 3)); + const char resp[] = "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n\r\n"; + + scoped_ptr<spdy::SpdyFrame> wrapped_get_resp( + ConstructSpdyBodyFrame(3, resp, strlen(resp), false)); + scoped_ptr<spdy::SpdyFrame> wrapped_body( + ConstructSpdyBodyFrame(3, "hello", 5, false)); + MockRead spdy_reads[] = { + CreateMockRead(*conn_auth_resp, 1, true), + CreateMockRead(*conn_resp, 4, true), + CreateMockRead(*wrapped_get_resp, 5, true), + CreateMockRead(*wrapped_body, 6, true), + MockRead(false, ERR_IO_PENDING), + }; + + scoped_refptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data); + // Negotiate SPDY to the proxy + SSLSocketDataProvider proxy(true, OK); + proxy.next_proto_status = SSLClientSocket::kNextProtoNegotiated; + proxy.next_proto = "spdy/2"; + proxy.was_npn_negotiated = true; + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy); + // Vanilla SSL to the server + SSLSocketDataProvider server(true, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&server); + + TestOldCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, &callback1, log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(response->auth_challenge.get() != NULL); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestOldCompletionCallback callback2; + + rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar), &callback2); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(5, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + // The password prompt info should not be set. + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + trans.reset(); + session->CloseAllConnections(); +} + // Test when a server (non-proxy) returns a 407 (proxy-authenticate). // The request should fail with ERR_UNEXPECTED_PROXY_AUTH. TEST_F(HttpNetworkTransactionTest, UnexpectedProxyAuth) { @@ -7328,7 +7697,7 @@ TEST_F(HttpNetworkTransactionTest, // - HTTP or HTTPS backend (to include proxy tunneling). // - Non-authenticating and authenticating backend. // -// In all, there are 44 reasonable permuations (for example, if there are +// In all, there are 44 reasonable permutations (for example, if there are // problems generating an auth token for an authenticating proxy, we don't // need to test all permutations of the backend server). // @@ -8148,8 +8517,10 @@ TEST_F(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) { SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); HttpAuthHandlerMock::Factory* auth_factory = new HttpAuthHandlerMock::Factory(); - HttpAuthHandlerMock* auth_handler = new HttpAuthHandlerMock(); - auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_PROXY); + HttpAuthHandlerMock* auth_handler1 = new HttpAuthHandlerMock(); + auth_factory->AddMockHandler(auth_handler1, HttpAuth::AUTH_PROXY); + HttpAuthHandlerMock* auth_handler2 = new HttpAuthHandlerMock(); + auth_factory->AddMockHandler(auth_handler2, HttpAuth::AUTH_PROXY); auth_factory->set_do_init_from_challenge(true); session_deps.http_auth_handler_factory.reset(auth_factory); @@ -8181,11 +8552,6 @@ TEST_F(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) { // After the failure, a tunnel is established to www.google.com using // Proxy-Authorization headers. There is then a SPDY request round. // - // NOTE: Despite the "Proxy-Connection: Close", these are done on the - // same MockTCPClientSocket since the underlying HttpNetworkClientSocket - // does a Disconnect and Connect on the same socket, rather than trying - // to obtain a new one. - // // NOTE: Originally, the proxy response to the second CONNECT request // simply returned another 407 so the unit test could skip the SSL connection // establishment and SPDY framing issues. Alas, the @@ -8202,7 +8568,17 @@ TEST_F(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) { "Host: www.google.com\r\n" "Proxy-Connection: keep-alive\r\n" "\r\n"), + }; + + MockWrite data_writes_3[] = { + // Non-alternate protocol job that will run in parallel + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "\r\n"), + }; + MockWrite data_writes_4[] = { // Second connection attempt with Proxy-Authorization. MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" "Host: www.google.com\r\n" @@ -8216,6 +8592,7 @@ TEST_F(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) { const char kRejectConnectResponse[] = ("HTTP/1.1 407 Unauthorized\r\n" "Proxy-Authenticate: Mock\r\n" "Proxy-Connection: close\r\n" + "Content-Length: 0\r\n" "\r\n"); const char kAcceptConnectResponse[] = "HTTP/1.1 200 Connected\r\n\r\n"; MockRead data_reads_2[] = { @@ -8223,19 +8600,35 @@ TEST_F(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) { MockRead(false, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ, 1), MockRead(true, kRejectConnectResponse, arraysize(kRejectConnectResponse) - 1, 1), + }; + // Hang forever so we can ensure the alt job wins + MockRead data_reads_3[] = { + MockRead(false, ERR_IO_PENDING), + }; + + MockRead data_reads_4[] = { // Second connection attempt passes MockRead(true, kAcceptConnectResponse, - arraysize(kAcceptConnectResponse) -1, 4), + arraysize(kAcceptConnectResponse) -1, 1), // SPDY response - CreateMockRead(*resp.get(), 6), - CreateMockRead(*data.get(), 6), - MockRead(true, 0, 0, 6), + CreateMockRead(*resp.get(), 3), + CreateMockRead(*data.get(), 3), + MockRead(true, 0, 0, 4), }; scoped_refptr<OrderedSocketData> data_2( new OrderedSocketData(data_reads_2, arraysize(data_reads_2), data_writes_2, arraysize(data_writes_2))); + scoped_refptr<OrderedSocketData> data_3( + new OrderedSocketData(data_reads_3, arraysize(data_reads_3), + data_writes_3, arraysize(data_writes_3))); + // Hang forever so we can ensure the alt job wins + MockConnect conn_3(false, ERR_IO_PENDING); + data_3->set_connect_data(conn_3); + scoped_refptr<OrderedSocketData> data_4( + new OrderedSocketData(data_reads_4, arraysize(data_reads_4), + data_writes_4, arraysize(data_writes_4))); SSLSocketDataProvider ssl(true, OK); ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated; @@ -8250,6 +8643,9 @@ TEST_F(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) { session_deps.socket_factory.AddSocketDataProvider(&data_1); session_deps.socket_factory.AddSocketDataProvider(data_2.get()); + session_deps.socket_factory.AddSocketDataProvider(data_3.get()); + session_deps.socket_factory.AddSocketDataProvider(data_4.get()); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); session_deps.socket_factory.AddSocketDataProvider( &hanging_non_alternate_protocol_socket); @@ -8280,7 +8676,7 @@ TEST_F(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) { // After all that work, these two lines (or actually, just the scheme) are // what this test is all about. Make sure it happens correctly. - const GURL& request_url = auth_handler->request_url(); + const GURL& request_url = auth_handler2->request_url(); EXPECT_EQ("https", request_url.scheme()); EXPECT_EQ("www.google.com", request_url.host()); diff --git a/net/http/http_proxy_client_socket.cc b/net/http/http_proxy_client_socket.cc index 32ccb1b..1434c655 100644 --- a/net/http/http_proxy_client_socket.cc +++ b/net/http/http_proxy_client_socket.cc @@ -68,7 +68,7 @@ int HttpProxyClientSocket::RestartWithAuth(OldCompletionCallback* callback) { DCHECK(!user_callback_); int rv = PrepareForAuthRestart(); - if (rv != OK) + if (rv != OK || next_state_ == STATE_NONE) return rv; rv = DoLoop(OK); @@ -77,6 +77,11 @@ int HttpProxyClientSocket::RestartWithAuth(OldCompletionCallback* callback) { return rv; } +const +scoped_refptr<HttpAuthController>& HttpProxyClientSocket::auth_controller() { + return auth_; +} + const HttpResponseInfo* HttpProxyClientSocket::GetConnectResponseInfo() const { return response_.headers ? &response_ : NULL; } @@ -251,10 +256,7 @@ int HttpProxyClientSocket::DidDrainBodyForAuthRestart(bool keep_alive) { next_state_ = STATE_GENERATE_AUTH_TOKEN; transport_->set_is_reused(true); } else { - // This assumes that the underlying transport socket is a TCP socket, - // since only TCP sockets are restartable. - next_state_ = STATE_TCP_RESTART; - transport_->socket()->Disconnect(); + next_state_ = STATE_NONE; } // Reset the other member variables. @@ -267,17 +269,6 @@ int HttpProxyClientSocket::DidDrainBodyForAuthRestart(bool keep_alive) { return OK; } -int HttpProxyClientSocket::HandleAuthChallenge() { - DCHECK(response_.headers); - - int rv = auth_->HandleAuthChallenge(response_.headers, false, true, net_log_); - response_.auth_challenge = auth_->auth_info(); - if (rv == OK) - return ERR_PROXY_AUTH_REQUESTED; - - return rv; -} - void HttpProxyClientSocket::LogBlockedTunnelResponse(int response_code) const { LOG(WARNING) << "Blocked proxy response with status " << response_code << " to CONNECT request for " @@ -347,13 +338,6 @@ int HttpProxyClientSocket::DoLoop(int last_io_result) { case STATE_DRAIN_BODY_COMPLETE: rv = DoDrainBodyComplete(rv); break; - case STATE_TCP_RESTART: - DCHECK_EQ(OK, rv); - rv = DoTCPRestart(); - break; - case STATE_TCP_RESTART_COMPLETE: - rv = DoTCPRestartComplete(rv); - break; case STATE_DONE: break; default: @@ -452,7 +436,7 @@ int HttpProxyClientSocket::DoReadHeadersComplete(int result) { // authentication code is smart enough to avoid being tricked by an // active network attacker. // The next state is intentionally not set as it should be STATE_NONE; - return HandleAuthChallenge(); + return HandleAuthChallenge(auth_, &response_, net_log_); default: if (is_https_proxy_) @@ -488,17 +472,4 @@ int HttpProxyClientSocket::DoDrainBodyComplete(int result) { return OK; } -int HttpProxyClientSocket::DoTCPRestart() { - next_state_ = STATE_TCP_RESTART_COMPLETE; - return transport_->socket()->Connect(&io_callback_); -} - -int HttpProxyClientSocket::DoTCPRestartComplete(int result) { - if (result != OK) - return result; - - next_state_ = STATE_GENERATE_AUTH_TOKEN; - return result; -} - } // namespace net diff --git a/net/http/http_proxy_client_socket.h b/net/http/http_proxy_client_socket.h index def0221..70db3707 100644 --- a/net/http/http_proxy_client_socket.h +++ b/net/http/http_proxy_client_socket.h @@ -50,15 +50,6 @@ class HttpProxyClientSocket : public ProxyClientSocket { // On destruction Disconnect() is called. virtual ~HttpProxyClientSocket(); - // If Connect (or its callback) returns PROXY_AUTH_REQUESTED, then - // credentials should be added to the HttpAuthController before calling - // RestartWithAuth. - int RestartWithAuth(OldCompletionCallback* callback); - - const scoped_refptr<HttpAuthController>& auth_controller() { - return auth_; - } - bool using_spdy() { return using_spdy_; } @@ -66,6 +57,8 @@ class HttpProxyClientSocket : public ProxyClientSocket { // ProxyClientSocket methods: virtual const HttpResponseInfo* GetConnectResponseInfo() const OVERRIDE; virtual HttpStream* CreateConnectResponseStream() OVERRIDE; + virtual int RestartWithAuth(OldCompletionCallback* callback) OVERRIDE; + virtual const scoped_refptr<HttpAuthController>& auth_controller() OVERRIDE; // StreamSocket methods: virtual int Connect(OldCompletionCallback* callback) OVERRIDE; @@ -103,8 +96,6 @@ class HttpProxyClientSocket : public ProxyClientSocket { STATE_READ_HEADERS_COMPLETE, STATE_DRAIN_BODY, STATE_DRAIN_BODY_COMPLETE, - STATE_TCP_RESTART, - STATE_TCP_RESTART_COMPLETE, STATE_DONE, }; @@ -116,8 +107,6 @@ class HttpProxyClientSocket : public ProxyClientSocket { int PrepareForAuthRestart(); int DidDrainBodyForAuthRestart(bool keep_alive); - int HandleAuthChallenge(); - void LogBlockedTunnelResponse(int response_code) const; void DoCallback(int result); @@ -132,8 +121,6 @@ class HttpProxyClientSocket : public ProxyClientSocket { int DoReadHeadersComplete(int result); int DoDrainBody(); int DoDrainBodyComplete(int result); - int DoTCPRestart(); - int DoTCPRestartComplete(int result); OldCompletionCallbackImpl<HttpProxyClientSocket> io_callback_; State next_state_; diff --git a/net/http/http_proxy_client_socket_pool_unittest.cc b/net/http/http_proxy_client_socket_pool_unittest.cc index 175f167..d7b56867 100644 --- a/net/http/http_proxy_client_socket_pool_unittest.cc +++ b/net/http/http_proxy_client_socket_pool_unittest.cc @@ -257,9 +257,22 @@ TEST_P(HttpProxyClientSocketPoolTest, NeedAuth) { CreateMockWrite(*req, 0, true), CreateMockWrite(*rst, 2, true), }; + static const char* const kAuthChallenge[] = { + "status", "407 Proxy Authentication Required", + "version", "HTTP/1.1", + "proxy-authenticate", "Basic realm=\"MyRealm1\"", + }; + scoped_ptr<spdy::SpdyFrame> resp( - ConstructSpdySynReplyError( - "407 Proxy Authentication Required", NULL, 0, 1)); + ConstructSpdyControlFrame(NULL, + 0, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kAuthChallenge, + arraysize(kAuthChallenge))); MockRead spdy_reads[] = { CreateMockWrite(*resp, 1, true), MockRead(true, 0, 3) @@ -276,21 +289,16 @@ TEST_P(HttpProxyClientSocketPoolTest, NeedAuth) { EXPECT_FALSE(handle_.is_initialized()); EXPECT_FALSE(handle_.socket()); - data_->RunFor(4); + data_->RunFor(GetParam() == SPDY ? 2 : 4); rv = callback_.WaitForResult(); + EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, rv); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); 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()); } } diff --git a/net/http/http_proxy_utils.cc b/net/http/http_proxy_utils.cc index e93c8ee..6c2c955 100644 --- a/net/http/http_proxy_utils.cc +++ b/net/http/http_proxy_utils.cc @@ -1,14 +1,19 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/http/http_proxy_utils.h" +#include "base/logging.h" #include "base/stringprintf.h" #include "googleurl/src/gurl.h" #include "net/base/host_port_pair.h" +#include "net/base/net_errors.h" #include "net/base/net_util.h" +#include "net/http/http_auth_controller.h" #include "net/http/http_request_info.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" namespace net { @@ -36,4 +41,17 @@ void BuildTunnelRequest( request_headers->MergeFrom(auth_headers); } +int HandleAuthChallenge(HttpAuthController* auth, + HttpResponseInfo* response, + const BoundNetLog& net_log) { + DCHECK(response->headers); + + int rv = auth->HandleAuthChallenge(response->headers, false, true, net_log); + response->auth_challenge = auth->auth_info(); + if (rv == OK) + return ERR_PROXY_AUTH_REQUESTED; + + return rv; +} + } // namespace net diff --git a/net/http/http_proxy_utils.h b/net/http/http_proxy_utils.h index 9d18fc8..8475c34 100644 --- a/net/http/http_proxy_utils.h +++ b/net/http/http_proxy_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,6 +10,9 @@ namespace net { +class BoundNetLog; +class HttpAuthController; +class HttpResponseInfo; struct HttpRequestInfo; class HttpRequestHeaders; class HostPortPair; @@ -23,6 +26,12 @@ void BuildTunnelRequest(const HttpRequestInfo& request_info, std::string* request_line, HttpRequestHeaders* request_headers); +// When an auth challenge (407 response) is received during tunnel construction +// this method should be called. +int HandleAuthChallenge(HttpAuthController* auth, + HttpResponseInfo* response, + const BoundNetLog& net_log); + } // namespace net #endif // NET_HTTP_HTTP_PROXY_UTILS_H_ diff --git a/net/http/http_stream_factory_impl_job.cc b/net/http/http_stream_factory_impl_job.cc index 6fb88dc..a6ca05e 100644 --- a/net/http/http_stream_factory_impl_job.cc +++ b/net/http/http_stream_factory_impl_job.cc @@ -369,10 +369,10 @@ int HttpStreamFactoryImpl::Job::RunLoop(int result) { DCHECK(connection_->socket()); DCHECK(establishing_tunnel_); - HttpProxyClientSocket* http_proxy_socket = - static_cast<HttpProxyClientSocket*>(connection_->socket()); + ProxyClientSocket* proxy_socket = + static_cast<ProxyClientSocket*>(connection_->socket()); const HttpResponseInfo* tunnel_auth_response = - http_proxy_socket->GetConnectResponseInfo(); + proxy_socket->GetConnectResponseInfo(); next_state_ = STATE_WAITING_USER_ACTION; MessageLoop::current()->PostTask( @@ -381,7 +381,7 @@ int HttpStreamFactoryImpl::Job::RunLoop(int result) { &HttpStreamFactoryImpl::Job::OnNeedsProxyAuthCallback, ptr_factory_.GetWeakPtr(), *tunnel_auth_response, - http_proxy_socket->auth_controller())); + proxy_socket->auth_controller())); } return ERR_IO_PENDING; @@ -898,9 +898,9 @@ int HttpStreamFactoryImpl::Job::DoCreateStreamComplete(int result) { int HttpStreamFactoryImpl::Job::DoRestartTunnelAuth() { next_state_ = STATE_RESTART_TUNNEL_AUTH_COMPLETE; - HttpProxyClientSocket* http_proxy_socket = - static_cast<HttpProxyClientSocket*>(connection_->socket()); - return http_proxy_socket->RestartWithAuth(&io_callback_); + ProxyClientSocket* proxy_socket = + static_cast<ProxyClientSocket*>(connection_->socket()); + return proxy_socket->RestartWithAuth(&io_callback_); } int HttpStreamFactoryImpl::Job::DoRestartTunnelAuthComplete(int result) { @@ -908,14 +908,14 @@ int HttpStreamFactoryImpl::Job::DoRestartTunnelAuthComplete(int result) { return result; if (result == OK) { - // Now that we've got the HttpProxyClientSocket connected. We have + // Now that we've got the ProxyClientSocket prepared to restart. We have // to release it as an idle socket into the pool and start the connection // process from the beginning. Trying to pass it in with the // SSLSocketParams might cause a deadlock since params are dispatched // interchangeably. This request won't necessarily get this http proxy // socket, but there will be forward progress. establishing_tunnel_ = false; - ReturnToStateInitConnection(false /* do not close connection */); + ReturnToStateInitConnection(!connection_->socket()->IsConnectedAndIdle()); return OK; } diff --git a/net/http/proxy_client_socket.h b/net/http/proxy_client_socket.h index 451e098..1fd4f79 100644 --- a/net/http/proxy_client_socket.h +++ b/net/http/proxy_client_socket.h @@ -10,6 +10,7 @@ namespace net { +class HttpAuthController; class HttpStream; class HttpResponseInfo; @@ -26,6 +27,15 @@ class NET_EXPORT_PRIVATE ProxyClientSocket : public StreamSocket { // which can be used to read the response body. virtual HttpStream* CreateConnectResponseStream() = 0; + // Returns the HttpAuthController which can be used + // to interact with an HTTP Proxy Authorization Required (407) request. + virtual const scoped_refptr<HttpAuthController>& auth_controller() = 0; + + // If Connect (or its callback) returns PROXY_AUTH_REQUESTED, then + // credentials should be added to the HttpAuthController before calling + // RestartWithAuth. + virtual int RestartWithAuth(OldCompletionCallback* callback) = 0; + private: DISALLOW_COPY_AND_ASSIGN(ProxyClientSocket); }; diff --git a/net/spdy/spdy_proxy_client_socket.cc b/net/spdy/spdy_proxy_client_socket.cc index 1b2674d..6ef88e7 100644 --- a/net/spdy/spdy_proxy_client_socket.cc +++ b/net/spdy/spdy_proxy_client_socket.cc @@ -63,6 +63,19 @@ const HttpResponseInfo* SpdyProxyClientSocket::GetConnectResponseInfo() const { return response_.headers ? &response_ : NULL; } +int SpdyProxyClientSocket::RestartWithAuth(OldCompletionCallback* callback) { + // A SPDY Stream can only handle a single request, so the underlying + // stream may not be reused and a new SpdyProxyClientSocket must be + // created (possibly on top of the same SPDY Session). + next_state_ = STATE_DISCONNECTED; + return OK; +} + +const +scoped_refptr<HttpAuthController>& SpdyProxyClientSocket::auth_controller() { + return auth_; +} + HttpStream* SpdyProxyClientSocket::CreateConnectResponseStream() { DCHECK(response_stream_.get()); return response_stream_.release(); @@ -384,6 +397,16 @@ int SpdyProxyClientSocket::DoReadReplyComplete(int result) { if (response_.headers->response_code() == 200) { return OK; } else if (response_.headers->response_code() == 407) { + int rv = HandleAuthChallenge(auth_, &response_, net_log_); + if (rv != ERR_PROXY_AUTH_REQUESTED) { + return rv; + } + // SPDY only supports basic and digest auth + if (auth_->auth_info() && + (auth_->auth_info()->scheme == "basic" || + auth_->auth_info()->scheme == "digest")) { + return ERR_PROXY_AUTH_REQUESTED; + } return ERR_TUNNEL_CONNECTION_FAILED; } else { // Immediately hand off our SpdyStream to a newly created SpdyHttpStream diff --git a/net/spdy/spdy_proxy_client_socket.h b/net/spdy/spdy_proxy_client_socket.h index 8a9237b..9875a05 100644 --- a/net/spdy/spdy_proxy_client_socket.h +++ b/net/spdy/spdy_proxy_client_socket.h @@ -53,17 +53,11 @@ class NET_EXPORT_PRIVATE SpdyProxyClientSocket : public ProxyClientSocket, // On destruction Disconnect() is called. virtual ~SpdyProxyClientSocket(); - const scoped_refptr<HttpAuthController>& auth_controller() { - return auth_; - } - // ProxyClientSocket methods: virtual const HttpResponseInfo* GetConnectResponseInfo() const OVERRIDE; - - // In the event of a non-200 response to the CONNECT request, this - // method may be called to return an HttpStream in order to read - // the response body. virtual HttpStream* CreateConnectResponseStream() OVERRIDE; + virtual int RestartWithAuth(OldCompletionCallback* callback) OVERRIDE; + virtual const scoped_refptr<HttpAuthController>& auth_controller() OVERRIDE; // StreamSocket methods: virtual int Connect(OldCompletionCallback* callback) OVERRIDE; diff --git a/net/spdy/spdy_proxy_client_socket_unittest.cc b/net/spdy/spdy_proxy_client_socket_unittest.cc index 323b6db..89aba7e 100644 --- a/net/spdy/spdy_proxy_client_socket_unittest.cc +++ b/net/spdy/spdy_proxy_client_socket_unittest.cc @@ -66,6 +66,7 @@ class SpdyProxyClientSocketTest : public PlatformTest { spdy::SpdyFrame* ConstructConnectAuthRequestFrame(); spdy::SpdyFrame* ConstructConnectReplyFrame(); spdy::SpdyFrame* ConstructConnectAuthReplyFrame(); + spdy::SpdyFrame* ConstructNtlmAuthReplyFrame(); spdy::SpdyFrame* ConstructConnectErrorReplyFrame(); spdy::SpdyFrame* ConstructBodyFrame(const char* data, int length); scoped_refptr<IOBufferWithSize> CreateBuffer(const char* data, int size); @@ -387,6 +388,26 @@ spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectAuthReplyFrame() { arraysize(kStandardReplyHeaders)); } +// Constructs a SPDY SYN_REPLY frame to match the SPDY CONNECT which +// requires Proxy Authentication using NTLM. +spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructNtlmAuthReplyFrame() { + const char* const kStandardReplyHeaders[] = { + "status", "407 Proxy Authentication Required", + "version", "HTTP/1.1", + "proxy-authenticate", "NTLM", + }; + + return ConstructSpdyControlFrame(NULL, + 0, + false, + kStreamId, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardReplyHeaders, + arraysize(kStandardReplyHeaders)); +} + // Constructs a SPDY SYN_REPLY frame with an HTTP 500 error. spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectErrorReplyFrame() { const char* const kStandardReplyHeaders[] = { @@ -433,6 +454,23 @@ TEST_F(SpdyProxyClientSocketTest, ConnectSendsCorrectRequest) { AssertConnectionEstablished(); } +TEST_F(SpdyProxyClientSocketTest, ConnectWithUnsupportedAuth) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, false), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructNtlmAuthReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, true), + MockRead(true, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectFails(ERR_TUNNEL_CONNECTION_FAILED); +} + TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthRequested) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { @@ -447,7 +485,7 @@ TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthRequested) { Initialize(reads, arraysize(reads), writes, arraysize(writes)); - AssertConnectFails(ERR_TUNNEL_CONNECTION_FAILED); + AssertConnectFails(ERR_PROXY_AUTH_REQUESTED); const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); ASSERT_TRUE(response != NULL); @@ -476,6 +514,38 @@ TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthCredentials) { AssertConnectionEstablished(); } +TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthRestart) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<spdy::SpdyFrame> auth(ConstructConnectAuthRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, false), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectAuthReplyFrame()); + scoped_ptr<spdy::SpdyFrame> auth_resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, true), + MockRead(true, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectFails(ERR_PROXY_AUTH_REQUESTED); + + const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_EQ(407, response->headers->response_code()); + ASSERT_EQ("Proxy Authentication Required", + response->headers->GetStatusText()); + + AddAuthToCache(); + + ASSERT_EQ(OK, sock_->RestartWithAuth(&read_callback_)); + // A SpdyProxyClientSocket sits on a single SPDY stream which can + // only be used for a single request/response. + ASSERT_FALSE(sock_->IsConnectedAndIdle()); +} + TEST_F(SpdyProxyClientSocketTest, ConnectFails) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { @@ -821,7 +891,7 @@ TEST_F(SpdyProxyClientSocketTest, ReadAuthResponseBody) { Initialize(reads, arraysize(reads), writes, arraysize(writes)); - AssertConnectFails(ERR_TUNNEL_CONNECTION_FAILED); + AssertConnectFails(ERR_PROXY_AUTH_REQUESTED); Run(2); // SpdySession consumes the next two reads and sends then to // sock_ to be buffered. |