diff options
Diffstat (limited to 'net/http')
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 575 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket.cc | 58 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket.h | 21 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket_pool.cc | 87 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket_pool.h | 29 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket_pool_unittest.cc | 43 | ||||
-rw-r--r-- | net/http/http_proxy_utils.cc | 18 | ||||
-rw-r--r-- | net/http/http_proxy_utils.h | 11 | ||||
-rw-r--r-- | net/http/http_stream_factory_impl_job.cc | 49 | ||||
-rw-r--r-- | net/http/http_stream_factory_impl_job.h | 12 | ||||
-rw-r--r-- | net/http/http_stream_factory_impl_request.cc | 4 | ||||
-rw-r--r-- | net/http/proxy_client_socket.h | 12 |
12 files changed, 806 insertions, 113 deletions
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); }; |