summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPenny MacNeil <pennymac@chromium.org>2015-03-05 15:20:04 -0800
committerPenny MacNeil <pennymac@chromium.org>2015-03-05 23:21:34 +0000
commit5940a1341af04048ca103ea3a2d60c4ee058911c (patch)
treee6fb70934aec1cdaf819156900c6bf790bfea7e2
parent15cd622668bb4efbe3f18907551f5c49fb1896fe (diff)
downloadchromium_src-5940a1341af04048ca103ea3a2d60c4ee058911c.zip
chromium_src-5940a1341af04048ca103ea3a2d60c4ee058911c.tar.gz
chromium_src-5940a1341af04048ca103ea3a2d60c4ee058911c.tar.bz2
Merge to M41 branch 2272: SanitizeProxyAuth: Whitelist all hop-by-hop headers
We were discarding "Proxy-Authenticate: keep-alive" headers in HTTP/1.0 responses, which broke multi-step connection-level authentication methods for proxies that sent HTTP/1.0 responses. BUG=463937 Review URL: https://codereview.chromium.org/982733002 Cr-Commit-Position: refs/heads/master@{#319219} (cherry picked from commit 34f63b55b51e8fc46ad86334783a665d5f487738) Review URL: https://codereview.chromium.org/988553002 Cr-Commit-Position: refs/branch-heads/2272@{#414} Cr-Branched-From: 827a380cfdb31aa54c8d56e63ce2c3fd8c3ba4d4-refs/heads/master@{#310958}
-rw-r--r--net/http/http_network_transaction_unittest.cc236
-rw-r--r--net/http/proxy_client_socket.cc14
2 files changed, 243 insertions, 7 deletions
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index e280bff..087d0df 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -2373,7 +2373,125 @@ TEST_P(HttpNetworkTransactionTest, BasicAuthKeepAliveImpatientServer) {
// Test the request-challenge-retry sequence for basic auth, over a connection
// that requires a restart when setting up an SSL tunnel.
-TEST_P(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) {
+TEST_P(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAliveHttp10) {
+ 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 proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"));
+ CapturingBoundNetLog log;
+ 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"),
+
+ // 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.0 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n\r\n"),
+
+ MockRead("HTTP/1.0 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(SYNCHRONOUS, "hello"),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ net::CapturingNetLog::CapturedEntryList 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);
+ EXPECT_FALSE(response->headers->IsKeepAlive());
+ ASSERT_FALSE(response->headers.get() == NULL);
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_TRUE(HttpVersion(1, 0) == response->headers->GetHttpVersion());
+ EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
+
+ LoadTimingInfo load_timing_info;
+ // CONNECT requests and responses are handled at the connect job level, so
+ // the transaction does not yet have a connection.
+ EXPECT_FALSE(trans->GetLoadTimingInfo(&load_timing_info));
+
+ 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);
+
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+
+ trans.reset();
+ 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_P(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAliveHttp11) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.google.com/");
@@ -2449,6 +2567,7 @@ TEST_P(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) {
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
+ EXPECT_FALSE(response->headers->IsKeepAlive());
ASSERT_FALSE(response->headers.get() == NULL);
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
@@ -2488,8 +2607,115 @@ TEST_P(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) {
}
// Test the request-challenge-retry sequence for basic auth, over a keep-alive
-// proxy connection, when setting up an SSL tunnel.
-TEST_P(HttpNetworkTransactionTest, BasicAuthProxyKeepAlive) {
+// proxy connection with HTTP/1.0 responses, when setting up an SSL tunnel.
+TEST_P(HttpNetworkTransactionTest, BasicAuthProxyKeepAliveHttp10) {
+ 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 proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed("myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ // 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. (Since it's HTTP/1.0, keep-alive has to be explicit.)
+ MockRead data_reads1[] = {
+ // No credentials.
+ MockRead("HTTP/1.0 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Proxy-Connection: keep-alive\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead("0123456789"),
+
+ // Wrong credentials (wrong password).
+ MockRead("HTTP/1.0 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Proxy-Connection: keep-alive\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ // No response body because the test stops reading here.
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ 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::CapturedEntryList 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);
+ ASSERT_TRUE(response->headers);
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_EQ(10, response->headers->GetContentLength());
+ EXPECT_TRUE(HttpVersion(1, 0) == 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);
+ ASSERT_TRUE(response->headers);
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_EQ(10, response->headers->GetContentLength());
+ EXPECT_TRUE(HttpVersion(1, 0) == 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, over a keep-alive
+// proxy connection with HTTP/1.1 responses, when setting up an SSL tunnel.
+TEST_P(HttpNetworkTransactionTest, BasicAuthProxyKeepAliveHttp11) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.google.com/");
@@ -2563,7 +2789,7 @@ TEST_P(HttpNetworkTransactionTest, BasicAuthProxyKeepAlive) {
ASSERT_TRUE(response->headers);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
- EXPECT_EQ(-1, response->headers->GetContentLength());
+ EXPECT_EQ(10, response->headers->GetContentLength());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
@@ -2582,7 +2808,7 @@ TEST_P(HttpNetworkTransactionTest, BasicAuthProxyKeepAlive) {
ASSERT_TRUE(response->headers);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
- EXPECT_EQ(-1, response->headers->GetContentLength());
+ EXPECT_EQ(10, response->headers->GetContentLength());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
diff --git a/net/http/proxy_client_socket.cc b/net/http/proxy_client_socket.cc
index 3c539c6..3c0d12d 100644
--- a/net/http/proxy_client_socket.cc
+++ b/net/http/proxy_client_socket.cc
@@ -95,9 +95,19 @@ bool ProxyClientSocket::SanitizeProxyAuth(HttpResponseInfo* response) {
scoped_refptr<HttpResponseHeaders> new_headers = new HttpResponseHeaders(
HttpUtil::AssembleRawHeaders(kHeaders, arraysize(kHeaders)));
+ // Copy status line and all hop-by-hop headers to preserve keep-alive
+ // behavior.
new_headers->ReplaceStatusLine(old_headers->GetStatusLine());
- CopyHeaderValues(old_headers, new_headers, "Connection");
- CopyHeaderValues(old_headers, new_headers, "Proxy-Authenticate");
+ CopyHeaderValues(old_headers, new_headers, "connection");
+ CopyHeaderValues(old_headers, new_headers, "proxy-connection");
+ CopyHeaderValues(old_headers, new_headers, "keep-alive");
+ CopyHeaderValues(old_headers, new_headers, "trailer");
+ CopyHeaderValues(old_headers, new_headers, "transfer-encoding");
+ CopyHeaderValues(old_headers, new_headers, "upgrade");
+
+ CopyHeaderValues(old_headers, new_headers, "content-length");
+
+ CopyHeaderValues(old_headers, new_headers, "proxy-authenticate");
response->headers = new_headers;
return true;