diff options
Diffstat (limited to 'net')
20 files changed, 940 insertions, 163 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index 37f5f31..bba09ea 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -285,6 +285,9 @@ NET_ERROR(CLIENT_AUTH_CERT_TYPE_UNSUPPORTED, -151) // first was still being generated. NET_ERROR(ORIGIN_BOUND_CERT_GENERATION_TYPE_MISMATCH, -152) +// The proxy does not support restarting a request on the existing connection. +NET_ERROR(NO_KEEP_ALIVE_ON_AUTH_RESTART, -153) + // 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 3a61a32..8dda238 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -421,6 +421,16 @@ bool CheckNTLMServerAuth(const AuthChallengeInfo* auth_challenge) { return true; } +bool CheckNTLMProxyAuth(const AuthChallengeInfo* auth_challenge) { + if (!auth_challenge) + return false; + EXPECT_TRUE(auth_challenge->is_proxy); + EXPECT_EQ("proxy:70", auth_challenge->challenger.ToString()); + EXPECT_EQ(std::string(), auth_challenge->realm); + EXPECT_EQ("ntlm", auth_challenge->scheme); + return true; +} + TEST_F(HttpNetworkTransactionTest, Basic) { SessionDependencies session_deps; scoped_ptr<HttpTransaction> trans( @@ -1718,6 +1728,9 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) { "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" @@ -1737,7 +1750,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"), @@ -1748,7 +1763,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); @@ -1963,6 +1981,372 @@ 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); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), 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())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar), + callback2.callback()); + 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); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), 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())); + + TestCompletionCallback callback2; + + // Wrong password (should be "bar"). + rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBaz), + callback2.callback()); + 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_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + // 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); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), 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())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar), + callback2.callback()); + 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) { @@ -3065,6 +3449,152 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth1) { EXPECT_EQ(13, response->headers->GetContentLength()); } + +// Enter the correct password and authenticate successfully. +TEST_F(HttpNetworkTransactionTest, NTLMProxyAuthWithConnectTunnel) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom1, + MockGetHostName); + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + + 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"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 407 Access Denied\r\n"), + // Negotiate and NTLM are often requested together. However, we only want + // to test NTLM. Since Negotiate is preferred over NTLM, we have to skip + // the header that requests Negotiate for this test. + MockRead("Proxy-Authenticate: NTLM\r\n"), + MockRead("Connection: close\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + // Missing content -- won't matter, as connection will be reset. + MockRead(false, ERR_UNEXPECTED), + }; + + MockWrite data_writes2[] = { + // After restarting with a null identity, this is the + // request we should be issuing -- the final header line contains a Type + // 1 message. + 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: NTLM " + "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"), + + // After calling trans->RestartWithAuth(), we should send a Type 3 message + // (the credentials for the origin server). The second request continues + // on the same connection. + 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: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA" + "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA" + "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwBVKW" + "Yma5xzVAAAAAAAAAAAAAAAAAAAAACH+gWcm+YsP9Tqb9zCR3WAeZZX" + "ahlhx5I=\r\n\r\n"), + + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads2[] = { + // The origin server responds with a Type 2 message. + MockRead("HTTP/1.1 407 Access Denied\r\n"), + MockRead("Proxy-Authenticate: NTLM " + "TlRMTVNTUAACAAAADAAMADgAAAAFgokCjGpMpPGlYKkAAAAAAAAAALo" + "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE" + "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA" + "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy" + "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB" + "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw" + "BtAAAAAAA=\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + MockRead("You are not authorized to view this page\r\n"), + + // Connect succeeds + MockRead("HTTP/1.0 200 Connected\r\n\r\n"), + + // Lastly we get the desired content. + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=utf-8\r\n"), + MockRead("Content-Length: 13\r\n\r\n"), + MockRead("Please Login\r\n"), + MockRead(false, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + SSLSocketDataProvider ssl(true, OK); + SSLSocketDataProvider ssl2(true, OK); + SSLSocketDataProvider ssl3(true, OK); + SSLSocketDataProvider ssl_server(true, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl3); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_server); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_FALSE(response == NULL); + EXPECT_TRUE(CheckNTLMProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth(AuthCredentials(kTestingNTLM, kTestingNTLM), + callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + TestCompletionCallback callback3; + + rv = trans->RestartWithAuth(AuthCredentials(), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(13, response->headers->GetContentLength()); +} + // Enter a wrong password, and then the correct one. TEST_F(HttpNetworkTransactionTest, NTLMAuth2) { HttpRequestInfo request; @@ -7452,7 +7982,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). // @@ -8310,11 +8840,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 @@ -8331,7 +8856,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 https://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" @@ -8345,6 +8880,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[] = { @@ -8352,19 +8888,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_ptr<OrderedSocketData> data_2( new OrderedSocketData(data_reads_2, arraysize(data_reads_2), data_writes_2, arraysize(data_writes_2))); + scoped_ptr<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_ptr<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; @@ -8380,6 +8932,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); diff --git a/net/http/http_proxy_client_socket.cc b/net/http/http_proxy_client_socket.cc index 8e6631a..97f1402 100644 --- a/net/http/http_proxy_client_socket.cc +++ b/net/http/http_proxy_client_socket.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -31,8 +31,7 @@ HttpProxyClientSocket::HttpProxyClientSocket( const std::string& user_agent, const HostPortPair& endpoint, const HostPortPair& proxy_server, - HttpAuthCache* http_auth_cache, - HttpAuthHandlerFactory* http_auth_handler_factory, + HttpAuthController* http_auth_controller, bool tunnel, bool using_spdy, SSLClientSocket::NextProto protocol_negotiated, @@ -43,13 +42,7 @@ HttpProxyClientSocket::HttpProxyClientSocket( next_state_(STATE_NONE), transport_(transport_socket), endpoint_(endpoint), - auth_(tunnel ? - new HttpAuthController(HttpAuth::AUTH_PROXY, - GURL((is_https_proxy ? "https://" : "http://") - + proxy_server.ToString()), - http_auth_cache, - http_auth_handler_factory) - : NULL), + auth_(http_auth_controller), tunnel_(tunnel), using_spdy_(using_spdy), protocol_negotiated_(protocol_negotiated), @@ -67,6 +60,11 @@ HttpProxyClientSocket::~HttpProxyClientSocket() { Disconnect(); } +const +scoped_refptr<HttpAuthController>& HttpProxyClientSocket::GetAuthController() { + return auth_; +} + int HttpProxyClientSocket::RestartWithAuth(const CompletionCallback& callback) { DCHECK_EQ(STATE_NONE, next_state_); DCHECK(user_callback_.is_null()); @@ -254,14 +252,14 @@ int HttpProxyClientSocket::PrepareForAuthRestart() { } int HttpProxyClientSocket::DidDrainBodyForAuthRestart(bool keep_alive) { + int rv = OK; if (keep_alive && transport_->socket()->IsConnectedAndIdle()) { 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; + next_state_ = STATE_NONE; transport_->socket()->Disconnect(); + rv = ERR_NO_KEEP_ALIVE_ON_AUTH_RESTART; } // Reset the other member variables. @@ -271,17 +269,6 @@ int HttpProxyClientSocket::DidDrainBodyForAuthRestart(bool keep_alive) { request_line_.clear(); request_headers_.Clear(); response_ = HttpResponseInfo(); - 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; } @@ -354,13 +341,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: @@ -459,7 +439,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_) @@ -495,18 +475,4 @@ int HttpProxyClientSocket::DoDrainBodyComplete(int result) { return OK; } -int HttpProxyClientSocket::DoTCPRestart() { - next_state_ = STATE_TCP_RESTART_COMPLETE; - return transport_->socket()->Connect( - base::Bind(&HttpProxyClientSocket::OnIOComplete, base::Unretained(this))); -} - -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 e4b1844..7a05127 100644 --- a/net/http/http_proxy_client_socket.h +++ b/net/http/http_proxy_client_socket.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -27,7 +27,6 @@ namespace net { class AddressList; class ClientSocketHandle; class GrowableIOBuffer; -class HttpAuthCache; class HttpStream; class HttpStreamParser; class IOBuffer; @@ -42,8 +41,7 @@ class HttpProxyClientSocket : public ProxyClientSocket { const std::string& user_agent, const HostPortPair& endpoint, const HostPortPair& proxy_server, - HttpAuthCache* http_auth_cache, - HttpAuthHandlerFactory* http_auth_handler_factory, + HttpAuthController* http_auth_controller, bool tunnel, bool using_spdy, SSLClientSocket::NextProto protocol_negotiated, @@ -52,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(const CompletionCallback& callback); - - const scoped_refptr<HttpAuthController>& auth_controller() { - return auth_; - } - bool using_spdy() { return using_spdy_; } @@ -72,6 +61,8 @@ class HttpProxyClientSocket : public ProxyClientSocket { // ProxyClientSocket implementation. virtual const HttpResponseInfo* GetConnectResponseInfo() const OVERRIDE; virtual HttpStream* CreateConnectResponseStream() OVERRIDE; + virtual int RestartWithAuth(const CompletionCallback& callback) OVERRIDE; + virtual const scoped_refptr<HttpAuthController>& GetAuthController() OVERRIDE; // StreamSocket implementation. virtual int Connect(const CompletionCallback& callback) OVERRIDE; @@ -109,8 +100,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, }; @@ -122,8 +111,6 @@ class HttpProxyClientSocket : public ProxyClientSocket { int PrepareForAuthRestart(); int DidDrainBodyForAuthRestart(bool keep_alive); - int HandleAuthChallenge(); - void LogBlockedTunnelResponse(int response_code) const; void DoCallback(int result); diff --git a/net/http/http_proxy_client_socket_pool.cc b/net/http/http_proxy_client_socket_pool.cc index 8194376..b4c1eae 100644 --- a/net/http/http_proxy_client_socket_pool.cc +++ b/net/http/http_proxy_client_socket_pool.cc @@ -28,6 +28,15 @@ namespace net { +namespace { + +std::string GetProxyUrl(const scoped_refptr<HttpProxySocketParams>& params) { + return (params->ssl_params() != NULL ? "https://" : "http://") + + params->destination().host_port_pair().ToString(); +} + +} // namespace + HttpProxySocketParams::HttpProxySocketParams( const scoped_refptr<TransportSocketParams>& transport_params, const scoped_refptr<SSLSocketParams>& ssl_params, @@ -37,7 +46,8 @@ HttpProxySocketParams::HttpProxySocketParams( HttpAuthCache* http_auth_cache, HttpAuthHandlerFactory* http_auth_handler_factory, SpdySessionPool* spdy_session_pool, - bool tunnel) + bool tunnel, + TunnelAuthCallback auth_needed_callback) : transport_params_(transport_params), ssl_params_(ssl_params), spdy_session_pool_(spdy_session_pool), @@ -46,7 +56,8 @@ HttpProxySocketParams::HttpProxySocketParams( endpoint_(endpoint), http_auth_cache_(tunnel ? http_auth_cache : NULL), http_auth_handler_factory_(tunnel ? http_auth_handler_factory : NULL), - tunnel_(tunnel) { + tunnel_(tunnel), + auth_needed_callback_(auth_needed_callback) { DCHECK((transport_params == NULL && ssl_params != NULL) || (transport_params != NULL && ssl_params == NULL)); if (transport_params_) @@ -87,7 +98,14 @@ HttpProxyConnectJob::HttpProxyConnectJob( callback_(base::Bind(&HttpProxyConnectJob::OnIOComplete, base::Unretained(this)))), using_spdy_(false), - protocol_negotiated_(SSLClientSocket::kProtoUnknown) { + protocol_negotiated_(SSLClientSocket::kProtoUnknown), + auth_(params->tunnel() ? + new HttpAuthController(HttpAuth::AUTH_PROXY, + GURL(GetProxyUrl(params_)), + params->http_auth_cache(), + params->http_auth_handler_factory()) + : NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(ptr_factory_(this)) { } HttpProxyConnectJob::~HttpProxyConnectJob() {} @@ -103,6 +121,8 @@ LoadState HttpProxyConnectJob::GetLoadState() const { case STATE_HTTP_PROXY_CONNECT_COMPLETE: case STATE_SPDY_PROXY_CREATE_STREAM: case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE: + case STATE_RESTART_WITH_AUTH: + case STATE_RESTART_WITH_AUTH_COMPLETE: return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL; default: NOTREACHED(); @@ -159,6 +179,13 @@ int HttpProxyConnectJob::DoLoop(int result) { case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE: rv = DoSpdyProxyCreateStreamComplete(rv); break; + case STATE_RESTART_WITH_AUTH: + DCHECK_EQ(OK, rv); + rv = DoRestartWithAuth(); + break; + case STATE_RESTART_WITH_AUTH_COMPLETE: + rv = DoRestartWithAuthComplete(rv); + break; default: NOTREACHED() << "bad state"; rv = ERR_FAILED; @@ -266,8 +293,7 @@ int HttpProxyConnectJob::DoHttpProxyConnect() { params_->user_agent(), params_->endpoint(), proxy_server, - params_->http_auth_cache(), - params_->http_auth_handler_factory(), + auth_, params_->tunnel(), using_spdy_, protocol_negotiated_, @@ -275,8 +301,54 @@ int HttpProxyConnectJob::DoHttpProxyConnect() { return transport_socket_->Connect(callback_); } +void HttpProxyConnectJob::HandleProxyAuthChallenge() { + next_state_ = STATE_RESTART_WITH_AUTH; + params_->auth_needed_callback().Run( + *transport_socket_->GetConnectResponseInfo(), + transport_socket_->GetAuthController(), + callback_); +} + +int HttpProxyConnectJob::DoRestartWithAuth() { + // If no auth was added to the controller, then we should abort. + next_state_ = STATE_RESTART_WITH_AUTH_COMPLETE; + if (!transport_socket_->GetAuthController()->HaveAuth()) { + return ERR_PROXY_AUTH_REQUESTED; + } + + return transport_socket_->RestartWithAuth(callback_); +} + +int HttpProxyConnectJob::DoRestartWithAuthComplete(int result) { + if (result != OK) { + if (result == ERR_NO_KEEP_ALIVE_ON_AUTH_RESTART) { + next_state_ = params_->transport_params() ? + STATE_TCP_CONNECT : STATE_SSL_CONNECT; + return OK; + } + if (result == ERR_PROXY_AUTH_REQUESTED || + result == ERR_HTTPS_PROXY_TUNNEL_RESPONSE) { + set_socket(transport_socket_.release()); + } + return result; + } + + next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE; + return OK; +} + int HttpProxyConnectJob::DoHttpProxyConnectComplete(int result) { - if (result == OK || result == ERR_PROXY_AUTH_REQUESTED || + // Handle a proxy auth challenge by asynchronously invoking the callback. + // We do this asynchronously so that the caller is notified of job + // completion only via NotifyDelegateOfCompletion. + if (result == ERR_PROXY_AUTH_REQUESTED) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&HttpProxyConnectJob::HandleProxyAuthChallenge, + ptr_factory_.GetWeakPtr())); + return ERR_IO_PENDING; + } + if (result == OK || result == ERR_HTTPS_PROXY_TUNNEL_RESPONSE) { set_socket(transport_socket_.release()); } @@ -326,8 +398,7 @@ int HttpProxyConnectJob::DoSpdyProxyCreateStreamComplete(int result) { params_->endpoint(), params_->request_url(), params_->destination().host_port_pair(), - params_->http_auth_cache(), - params_->http_auth_handler_factory())); + auth_)); return transport_socket_->Connect(callback_); } diff --git a/net/http/http_proxy_client_socket_pool.h b/net/http/http_proxy_client_socket_pool.h index 8991224..9e90187 100644 --- a/net/http/http_proxy_client_socket_pool.h +++ b/net/http/http_proxy_client_socket_pool.h @@ -26,6 +26,7 @@ namespace net { class HostResolver; class HttpAuthCache; +class HttpAuthController; class HttpAuthHandlerFactory; class SSLClientSocketPool; class SSLSocketParams; @@ -34,6 +35,17 @@ class SpdyStream; class TransportClientSocketPool; class TransportSocketParams; +// Called when a 407 Proxy Authentication Required response is received +// from an HTTP or HTTPS proxy when attempting to establish a CONNECT tunnel +// to an HTTPS server. Information about the challenge can be found in +// the HttpResponse info. Credentials should be added to the +// HttpAuthController, and the CompletionCallback should be invoked +// with the status. +typedef base::Callback<void (const HttpResponseInfo&, + HttpAuthController*, + CompletionCallback)> + TunnelAuthCallback; + // HttpProxySocketParams only needs the socket params for one of the proxy // types. The other param must be NULL. When using an HTTP Proxy, // |transport_params| must be set. When using an HTTPS Proxy, |ssl_params| @@ -50,7 +62,8 @@ class NET_EXPORT_PRIVATE HttpProxySocketParams HttpAuthCache* http_auth_cache, HttpAuthHandlerFactory* http_auth_handler_factory, SpdySessionPool* spdy_session_pool, - bool tunnel); + bool tunnel, + TunnelAuthCallback auth_needed_callback); const scoped_refptr<TransportSocketParams>& transport_params() const { return transport_params_; @@ -71,6 +84,7 @@ class NET_EXPORT_PRIVATE HttpProxySocketParams const HostResolver::RequestInfo& destination() const; bool tunnel() const { return tunnel_; } bool ignore_limits() const { return ignore_limits_; } + TunnelAuthCallback auth_needed_callback() { return auth_needed_callback_; } private: friend class base::RefCounted<HttpProxySocketParams>; @@ -86,6 +100,7 @@ class NET_EXPORT_PRIVATE HttpProxySocketParams HttpAuthHandlerFactory* const http_auth_handler_factory_; const bool tunnel_; bool ignore_limits_; + TunnelAuthCallback auth_needed_callback_; DISALLOW_COPY_AND_ASSIGN(HttpProxySocketParams); }; @@ -120,6 +135,8 @@ class HttpProxyConnectJob : public ConnectJob { STATE_SPDY_PROXY_CREATE_STREAM, STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE, STATE_SPDY_PROXY_CONNECT_COMPLETE, + STATE_RESTART_WITH_AUTH, + STATE_RESTART_WITH_AUTH_COMPLETE, STATE_NONE, }; @@ -141,6 +158,11 @@ class HttpProxyConnectJob : public ConnectJob { int DoSpdyProxyCreateStream(); int DoSpdyProxyCreateStreamComplete(int result); + int DoRestartWithAuth(); + int DoRestartWithAuthComplete(int result); + + void HandleProxyAuthChallenge(); + // Begins the tcp connection and the optional Http proxy tunnel. If the // request is not immediately servicable (likely), the request will return // ERR_IO_PENDING. An OK return from this function or the callback means @@ -167,6 +189,11 @@ class HttpProxyConnectJob : public ConnectJob { scoped_refptr<SpdyStream> spdy_stream_; + // AuthController to be used for *all* requests when setting up this tunnel. + scoped_refptr<HttpAuthController> auth_; + + base::WeakPtrFactory<HttpProxyConnectJob> ptr_factory_; + 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 8a83840..ba7b6b5 100644 --- a/net/http/http_proxy_client_socket_pool_unittest.cc +++ b/net/http/http_proxy_client_socket_pool_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -131,7 +131,9 @@ class HttpProxyClientSocketPoolTest : public TestWithHttpParam { session_->http_auth_cache(), session_->http_auth_handler_factory(), session_->spdy_session_pool(), - tunnel)); + tunnel, + base::Bind(&HttpProxyClientSocketPoolTest::OnNeedsProxyAuthCallback, + base::Unretained(this)))); } scoped_refptr<HttpProxySocketParams> GetTunnelParams() { @@ -191,6 +193,14 @@ class HttpProxyClientSocketPoolTest : public TestWithHttpParam { return new HttpNetworkSession(params); } + void OnNeedsProxyAuthCallback(const HttpResponseInfo& response_info, + HttpAuthController* auth_controller, + CompletionCallback cb) { + // Don't add any auth, just run the callback + cb.Run(OK); + } + + private: SSLConfig ssl_config_; @@ -258,9 +268,21 @@ 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) @@ -277,21 +299,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 7086bda..c523eb1 100644 --- a/net/http/http_proxy_utils.cc +++ b/net/http/http_proxy_utils.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -7,8 +7,12 @@ #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 +40,16 @@ 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 b93d034..c8aabb8 100644 --- a/net/http/http_proxy_utils.h +++ b/net/http/http_proxy_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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 9c5820c..92581e1 100644 --- a/net/http/http_stream_factory_impl_job.cc +++ b/net/http/http_stream_factory_impl_job.cc @@ -181,12 +181,20 @@ int HttpStreamFactoryImpl::Job::Preconnect(int num_streams) { return StartInternal(); } -int HttpStreamFactoryImpl::Job::RestartTunnelWithProxyAuth( - const AuthCredentials& credentials) { - DCHECK(establishing_tunnel_); - next_state_ = STATE_RESTART_TUNNEL_AUTH; - stream_.reset(); - return RunLoop(OK); +int HttpStreamFactoryImpl::Job::RestartTunnelWithProxyAuth() { + // We run this asynchronously to ensure that we don't invoke + // the callback (which might cause the caller to be deleted) + // while the caller is waiting for this method to return. + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&HttpStreamFactoryImpl::Job::DoRestartTunnelWithProxyAuth, + ptr_factory_.GetWeakPtr())); + return ERR_IO_PENDING; +} + +void HttpStreamFactoryImpl::Job::DoRestartTunnelWithProxyAuth() { + tunnel_auth_handled_callback_.Run(OK); + tunnel_auth_handled_callback_.Reset(); } LoadState HttpStreamFactoryImpl::Job::GetLoadState() const { @@ -342,6 +350,18 @@ void HttpStreamFactoryImpl::Job::OnNeedsProxyAuthCallback( // |this| may be deleted after this call. } +void HttpStreamFactoryImpl::Job::OnNeedsProxyTunnelAuthCallback( + const HttpResponseInfo& response_info, + HttpAuthController* auth_controller, + CompletionCallback callback) { + DCHECK(!callback.is_null()); + DCHECK(tunnel_auth_handled_callback_.is_null()); + tunnel_auth_handled_callback_ = callback; + request_->OnNeedsProxyAuth( + this, response_info, server_ssl_config_, proxy_info_, auth_controller); + // |this| may be deleted after this call. +} + void HttpStreamFactoryImpl::Job::OnNeedsClientAuthCallback( SSLCertRequestInfo* cert_info) { DCHECK(!IsPreconnecting()); @@ -420,10 +440,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( @@ -432,7 +452,7 @@ int HttpStreamFactoryImpl::Job::RunLoop(int result) { &HttpStreamFactoryImpl::Job::OnNeedsProxyAuthCallback, ptr_factory_.GetWeakPtr(), *tunnel_auth_response, - http_proxy_socket->auth_controller())); + proxy_socket->GetAuthController())); } return ERR_IO_PENDING; @@ -733,13 +753,18 @@ int HttpStreamFactoryImpl::Job::DoInitConnection() { server_ssl_config_, proxy_ssl_config_, net_log_, - num_streams_); + num_streams_, + base::Bind(&HttpStreamFactoryImpl::Job::OnNeedsProxyTunnelAuthCallback, + ptr_factory_.GetWeakPtr())); } else { return InitSocketHandleForHttpRequest( origin_url_, request_info_.extra_headers, request_info_.load_flags, request_info_.priority, session_, proxy_info_, ShouldForceSpdySSL(), want_spdy_over_npn, server_ssl_config_, proxy_ssl_config_, net_log_, - connection_.get(), io_callback_); + connection_.get(), + base::Bind(&HttpStreamFactoryImpl::Job::OnNeedsProxyTunnelAuthCallback, + ptr_factory_.GetWeakPtr()), + io_callback_); } } diff --git a/net/http/http_stream_factory_impl_job.h b/net/http/http_stream_factory_impl_job.h index 492a317..500c671 100644 --- a/net/http/http_stream_factory_impl_job.h +++ b/net/http/http_stream_factory_impl_job.h @@ -13,6 +13,7 @@ #include "net/base/ssl_config_service.h" #include "net/http/http_auth.h" #include "net/http/http_auth_controller.h" +#include "net/http/http_proxy_client_socket_pool.h" #include "net/http/http_request_info.h" #include "net/http/http_stream_factory_impl.h" #include "net/proxy/proxy_service.h" @@ -46,7 +47,8 @@ class HttpStreamFactoryImpl::Job { // appropriate ClientSocketPool. int Preconnect(int num_streams); - int RestartTunnelWithProxyAuth(const AuthCredentials& credentials); + int RestartTunnelWithProxyAuth(); + LoadState GetLoadState() const; // Marks this Job as the "alternate" job, from Alternate-Protocol. Tracks the @@ -123,6 +125,9 @@ class HttpStreamFactoryImpl::Job { void OnCertificateErrorCallback(int result, const SSLInfo& ssl_info); void OnNeedsProxyAuthCallback(const HttpResponseInfo& response_info, HttpAuthController* auth_controller); + void OnNeedsProxyTunnelAuthCallback(const HttpResponseInfo& response_info, + HttpAuthController* auth_controller, + CompletionCallback callback); void OnNeedsClientAuthCallback(SSLCertRequestInfo* cert_info); void OnHttpsProxyTunnelResponseCallback(const HttpResponseInfo& response_info, HttpStream* stream); @@ -153,6 +158,8 @@ class HttpStreamFactoryImpl::Job { // Returns to STATE_INIT_CONNECTION and resets some state. void ReturnToStateInitConnection(bool close_connection); + void DoRestartTunnelWithProxyAuth(); + // Set the motivation for this request onto the underlying socket. void SetSocketMotivation(); @@ -255,6 +262,9 @@ class HttpStreamFactoryImpl::Job { scoped_refptr<HttpAuthController> auth_controllers_[HttpAuth::AUTH_NUM_TARGETS]; + // Invoked after a request for tunnel auth has been handled. + CompletionCallback tunnel_auth_handled_callback_; + // True when the tunnel is in the process of being established - we can't // read from the socket until the tunnel is done. bool establishing_tunnel_; diff --git a/net/http/http_stream_factory_impl_request.cc b/net/http/http_stream_factory_impl_request.cc index cb8fcbf..1a14a86 100644 --- a/net/http/http_stream_factory_impl_request.cc +++ b/net/http/http_stream_factory_impl_request.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -205,7 +205,7 @@ void HttpStreamFactoryImpl::Request::OnHttpsProxyTunnelResponse( int HttpStreamFactoryImpl::Request::RestartTunnelWithProxyAuth( const AuthCredentials& credentials) { DCHECK(bound_job_.get()); - return bound_job_->RestartTunnelWithProxyAuth(credentials); + return bound_job_->RestartTunnelWithProxyAuth(); } LoadState HttpStreamFactoryImpl::Request::GetLoadState() const { diff --git a/net/http/proxy_client_socket.h b/net/http/proxy_client_socket.h index 451e098..21c7086 100644 --- a/net/http/proxy_client_socket.h +++ b/net/http/proxy_client_socket.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -12,6 +12,7 @@ namespace net { class HttpStream; class HttpResponseInfo; +class HttpAuthController; class NET_EXPORT_PRIVATE ProxyClientSocket : public StreamSocket { public: @@ -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>& GetAuthController() = 0; + + // If Connect (or its callback) returns PROXY_AUTH_REQUESTED, then + // credentials should be added to the HttpAuthController before calling + // RestartWithAuth. + virtual int RestartWithAuth(const CompletionCallback& callback) = 0; + private: DISALLOW_COPY_AND_ASSIGN(ProxyClientSocket); }; diff --git a/net/socket/client_socket_pool_manager.cc b/net/socket/client_socket_pool_manager.cc index 7098864..b65cb83 100644 --- a/net/socket/client_socket_pool_manager.cc +++ b/net/socket/client_socket_pool_manager.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -52,6 +52,7 @@ int InitSocketPoolHelper(const GURL& request_url, const BoundNetLog& net_log, int num_preconnect_streams, ClientSocketHandle* socket_handle, + TunnelAuthCallback auth_needed_callback, const CompletionCallback& callback) { scoped_refptr<TransportSocketParams> tcp_params; scoped_refptr<HttpProxySocketParams> http_proxy_params; @@ -136,7 +137,8 @@ int InitSocketPoolHelper(const GURL& request_url, session->http_auth_cache(), session->http_auth_handler_factory(), session->spdy_session_pool(), - force_tunnel || using_ssl); + force_tunnel || using_ssl, + auth_needed_callback); } else { DCHECK(proxy_info.is_socks()); char socks_version; @@ -289,13 +291,15 @@ int InitSocketHandleForHttpRequest( const SSLConfig& ssl_config_for_proxy, const BoundNetLog& net_log, ClientSocketHandle* socket_handle, + TunnelAuthCallback auth_needed_callback, const CompletionCallback& callback) { + DCHECK(socket_handle); return InitSocketPoolHelper( request_url, request_extra_headers, request_load_flags, request_priority, session, proxy_info, force_spdy_over_ssl, want_spdy_over_npn, ssl_config_for_origin, ssl_config_for_proxy, false, net_log, 0, - socket_handle, callback); + socket_handle, auth_needed_callback, callback); } int InitSocketHandleForRawConnect( @@ -306,6 +310,7 @@ int InitSocketHandleForRawConnect( const SSLConfig& ssl_config_for_proxy, const BoundNetLog& net_log, ClientSocketHandle* socket_handle, + TunnelAuthCallback auth_needed_callback, const CompletionCallback& callback) { DCHECK(socket_handle); // Synthesize an HttpRequestInfo. @@ -313,11 +318,11 @@ int InitSocketHandleForRawConnect( HttpRequestHeaders request_extra_headers; int request_load_flags = 0; RequestPriority request_priority = MEDIUM; - return InitSocketPoolHelper( request_url, request_extra_headers, request_load_flags, request_priority, session, proxy_info, false, false, ssl_config_for_origin, - ssl_config_for_proxy, true, net_log, 0, socket_handle, callback); + ssl_config_for_proxy, true, net_log, 0, socket_handle, + auth_needed_callback, callback); } int PreconnectSocketsForHttpRequest( @@ -332,12 +337,13 @@ int PreconnectSocketsForHttpRequest( const SSLConfig& ssl_config_for_origin, const SSLConfig& ssl_config_for_proxy, const BoundNetLog& net_log, - int num_preconnect_streams) { + int num_preconnect_streams, + TunnelAuthCallback auth_needed_callback) { return InitSocketPoolHelper( request_url, request_extra_headers, request_load_flags, request_priority, session, proxy_info, force_spdy_over_ssl, want_spdy_over_npn, ssl_config_for_origin, ssl_config_for_proxy, false, net_log, - num_preconnect_streams, NULL, CompletionCallback()); + num_preconnect_streams, NULL, auth_needed_callback, CompletionCallback()); } } // namespace net diff --git a/net/socket/client_socket_pool_manager.h b/net/socket/client_socket_pool_manager.h index 7c1f865..f025e56 100644 --- a/net/socket/client_socket_pool_manager.h +++ b/net/socket/client_socket_pool_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. // @@ -13,6 +13,7 @@ #include "net/base/completion_callback.h" #include "net/base/net_export.h" #include "net/base/request_priority.h" +#include "net/http/http_proxy_client_socket_pool.h" class GURL; @@ -26,7 +27,6 @@ class BoundNetLog; class ClientSocketHandle; class HostPortPair; class HttpNetworkSession; -class HttpProxyClientSocketPool; class HttpRequestHeaders; class ProxyInfo; class TransportClientSocketPool; @@ -88,6 +88,7 @@ int InitSocketHandleForHttpRequest( const SSLConfig& ssl_config_for_proxy, const BoundNetLog& net_log, ClientSocketHandle* socket_handle, + TunnelAuthCallback auth_needed_callback, const CompletionCallback& callback); // A helper method that uses the passed in proxy information to initialize a @@ -102,6 +103,7 @@ NET_EXPORT int InitSocketHandleForRawConnect( const SSLConfig& ssl_config_for_proxy, const BoundNetLog& net_log, ClientSocketHandle* socket_handle, + TunnelAuthCallback auth_needed_callback, const CompletionCallback& callback); // Similar to InitSocketHandleForHttpRequest except that it initiates the @@ -118,7 +120,8 @@ int PreconnectSocketsForHttpRequest( const SSLConfig& ssl_config_for_origin, const SSLConfig& ssl_config_for_proxy, const BoundNetLog& net_log, - int num_preconnect_streams); + int num_preconnect_streams, + TunnelAuthCallback auth_needed_callback); } // namespace net diff --git a/net/socket/ssl_client_socket_pool.cc b/net/socket/ssl_client_socket_pool.cc index 53f7f97..33a0d70 100644 --- a/net/socket/ssl_client_socket_pool.cc +++ b/net/socket/ssl_client_socket_pool.cc @@ -258,8 +258,8 @@ int SSLConnectJob::DoTunnelConnectComplete(int result) { } else if (result == ERR_PROXY_AUTH_REQUESTED || result == ERR_HTTPS_PROXY_TUNNEL_RESPONSE) { StreamSocket* socket = transport_socket_handle_->socket(); - HttpProxyClientSocket* tunnel_socket = - static_cast<HttpProxyClientSocket*>(socket); + ProxyClientSocket* tunnel_socket = + static_cast<ProxyClientSocket*>(socket); error_response_info_ = *tunnel_socket->GetConnectResponseInfo(); } if (result < 0) diff --git a/net/socket/ssl_client_socket_pool_unittest.cc b/net/socket/ssl_client_socket_pool_unittest.cc index b2b6cb3..125cba9 100644 --- a/net/socket/ssl_client_socket_pool_unittest.cc +++ b/net/socket/ssl_client_socket_pool_unittest.cc @@ -1,8 +1,8 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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_client_socket_pool.h" +#include "net/socket/ssl_client_socket_pool.h" #include "base/callback.h" #include "base/compiler_specific.h" @@ -19,6 +19,7 @@ #include "net/base/test_completion_callback.h" #include "net/http/http_auth_handler_factory.h" #include "net/http/http_network_session.h" +#include "net/http/http_proxy_client_socket_pool.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" #include "net/http/http_server_properties_impl.h" @@ -71,7 +72,9 @@ class SSLClientSocketPoolTest : public testing::Test { session_->http_auth_cache(), session_->http_auth_handler_factory(), session_->spdy_session_pool(), - true)), + true, + base::Bind(&SSLClientSocketPoolTest::OnNeedsProxyAuthCallback, + base::Unretained(this)))), http_proxy_histograms_("MockHttpProxy"), http_proxy_socket_pool_( kMaxSockets, @@ -144,6 +147,13 @@ class SSLClientSocketPoolTest : public testing::Test { return new HttpNetworkSession(params); } + void OnNeedsProxyAuthCallback(const HttpResponseInfo& response_info, + HttpAuthController* auth_controller, + CompletionCallback cb) { + // Don't add any auth, just execute the callback. + cb.Run(OK); + } + MockClientSocketFactory socket_factory_; MockCachingHostResolver host_resolver_; CertVerifier cert_verifier_; diff --git a/net/spdy/spdy_proxy_client_socket.cc b/net/spdy/spdy_proxy_client_socket.cc index 7267fef..adbba8e 100644 --- a/net/spdy/spdy_proxy_client_socket.cc +++ b/net/spdy/spdy_proxy_client_socket.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -14,8 +14,6 @@ #include "net/base/auth.h" #include "net/base/io_buffer.h" #include "net/base/net_util.h" -#include "net/http/http_auth_cache.h" -#include "net/http/http_auth_handler_factory.h" #include "net/http/http_net_log_params.h" #include "net/http/http_proxy_utils.h" #include "net/http/http_response_headers.h" @@ -29,16 +27,11 @@ SpdyProxyClientSocket::SpdyProxyClientSocket( const HostPortPair& endpoint, const GURL& url, const HostPortPair& proxy_server, - HttpAuthCache* auth_cache, - HttpAuthHandlerFactory* auth_handler_factory) + HttpAuthController* http_auth_controller) : next_state_(STATE_DISCONNECTED), spdy_stream_(spdy_stream), endpoint_(endpoint), - auth_( - new HttpAuthController(HttpAuth::AUTH_PROXY, - GURL("https://" + proxy_server.ToString()), - auth_cache, - auth_handler_factory)), + auth_(http_auth_controller), user_buffer_(NULL), write_buffer_len_(0), write_bytes_outstanding_(0), @@ -61,6 +54,19 @@ const HttpResponseInfo* SpdyProxyClientSocket::GetConnectResponseInfo() const { return response_.headers ? &response_ : NULL; } +const +scoped_refptr<HttpAuthController>& SpdyProxyClientSocket::GetAuthController() { + return auth_; +} + +int SpdyProxyClientSocket::RestartWithAuth(const CompletionCallback& 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 ERR_NO_KEEP_ALIVE_ON_AUTH_RESTART; +} + HttpStream* SpdyProxyClientSocket::CreateConnectResponseStream() { DCHECK(response_stream_.get()); return response_stream_.release(); @@ -72,8 +78,6 @@ HttpStream* SpdyProxyClientSocket::CreateConnectResponseStream() { // ERR_TUNNEL_CONNECTION_FAILED will be returned for any other status. // In any of these cases, Read() may be called to retrieve the HTTP // response body. Any other return values should be considered fatal. -// TODO(rch): handle 407 proxy auth requested correctly, perhaps -// by creating a new stream for the subsequent request. // TODO(rch): create a more appropriate error code to disambiguate // the HTTPS Proxy tunnel failure from an HTTP Proxy tunnel failure. int SpdyProxyClientSocket::Connect(const CompletionCallback& callback) { @@ -379,7 +383,17 @@ int SpdyProxyClientSocket::DoReadReplyComplete(int result) { if (response_.headers->response_code() == 200) { return OK; } else if (response_.headers->response_code() == 407) { - return ERR_TUNNEL_CONNECTION_FAILED; + 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_UNSUPPORTED; + } + return ERR_PROXY_AUTH_REQUESTED; } else { // Immediately hand off our SpdyStream to a newly created SpdyHttpStream // so that any subsequent SpdyFrames are processed in the context of diff --git a/net/spdy/spdy_proxy_client_socket.h b/net/spdy/spdy_proxy_client_socket.h index b969499..36d05dd 100644 --- a/net/spdy/spdy_proxy_client_socket.h +++ b/net/spdy/spdy_proxy_client_socket.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -47,19 +47,15 @@ class NET_EXPORT_PRIVATE SpdyProxyClientSocket : public ProxyClientSocket, const HostPortPair& endpoint, const GURL& url, const HostPortPair& proxy_server, - HttpAuthCache* auth_cache, - HttpAuthHandlerFactory* auth_handler_factory); - + HttpAuthController* auth_controller); // On destruction Disconnect() is called. virtual ~SpdyProxyClientSocket(); - const scoped_refptr<HttpAuthController>& auth_controller() { - return auth_; - } - // ProxyClientSocket methods: virtual const HttpResponseInfo* GetConnectResponseInfo() const OVERRIDE; + virtual int RestartWithAuth(const CompletionCallback& callback) OVERRIDE; + virtual const scoped_refptr<HttpAuthController>& GetAuthController() 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 diff --git a/net/spdy/spdy_proxy_client_socket_unittest.cc b/net/spdy/spdy_proxy_client_socket_unittest.cc index 1357b7d..2bc939f 100644 --- a/net/spdy/spdy_proxy_client_socket_unittest.cc +++ b/net/spdy/spdy_proxy_client_socket_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -68,6 +68,7 @@ class SpdyProxyClientSocketTest : public PlatformTest { spdy::SpdyFrame* ConstructConnectAuthRequestFrame(); spdy::SpdyFrame* ConstructConnectReplyFrame(); spdy::SpdyFrame* ConstructConnectAuthReplyFrame(); + spdy::SpdyFrame* ConstructConnectNtlmAuthReplyFrame(); spdy::SpdyFrame* ConstructConnectErrorReplyFrame(); spdy::SpdyFrame* ConstructBodyFrame(const char* data, int length); scoped_refptr<IOBufferWithSize> CreateBuffer(const char* data, int size); @@ -199,8 +200,12 @@ void SpdyProxyClientSocketTest::Initialize(MockRead* reads, sock_.reset( new SpdyProxyClientSocket(spdy_stream_, user_agent_, endpoint_host_port_pair_, url_, - proxy_host_port_, session_->http_auth_cache(), - session_->http_auth_handler_factory())); + proxy_host_port_, + new HttpAuthController( + HttpAuth::AUTH_PROXY, + GURL(kProxyUrl), + session_->http_auth_cache(), + session_->http_auth_handler_factory()))); } scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketTest::CreateBuffer( @@ -390,6 +395,26 @@ spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectAuthReplyFrame() { arraysize(kStandardReplyHeaders)); } +// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT. +spdy::SpdyFrame* +SpdyProxyClientSocketTest::ConstructConnectNtlmAuthReplyFrame() { + 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[] = { @@ -436,13 +461,13 @@ TEST_F(SpdyProxyClientSocketTest, ConnectSendsCorrectRequest) { AssertConnectionEstablished(); } -TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthRequested) { +TEST_F(SpdyProxyClientSocketTest, ConnectWithUnsupportedAuthScheme) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, false), }; - scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectAuthReplyFrame()); + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectNtlmAuthReplyFrame()); MockRead reads[] = { CreateMockRead(*resp, 1, true), MockRead(true, 0, 3), // EOF @@ -450,13 +475,7 @@ TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthRequested) { Initialize(reads, arraysize(reads), writes, arraysize(writes)); - AssertConnectFails(ERR_TUNNEL_CONNECTION_FAILED); - - const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); - ASSERT_TRUE(response != NULL); - ASSERT_EQ(407, response->headers->response_code()); - ASSERT_EQ("Proxy Authentication Required", - response->headers->GetStatusText()); + AssertConnectFails(ERR_PROXY_AUTH_UNSUPPORTED); } TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthCredentials) { @@ -479,6 +498,39 @@ 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(ERR_NO_KEEP_ALIVE_ON_AUTH_RESTART, + sock_->RestartWithAuth(read_callback_.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[] = { @@ -831,7 +883,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. |