summaryrefslogtreecommitdiffstats
path: root/net/http
diff options
context:
space:
mode:
authorttuttle@chromium.org <ttuttle@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-09 05:40:17 +0000
committerttuttle@chromium.org <ttuttle@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-09 05:40:17 +0000
commit4eddbc735618575805c304bcee9f50d4fe4b68e7 (patch)
tree49ddcf08fad02125d5606589fe5b777a802fdc4e /net/http
parent16e2ba27dbbb74a0dc2a85db91746b6e9d8abd63 (diff)
downloadchromium_src-4eddbc735618575805c304bcee9f50d4fe4b68e7.zip
chromium_src-4eddbc735618575805c304bcee9f50d4fe4b68e7.tar.gz
chromium_src-4eddbc735618575805c304bcee9f50d4fe4b68e7.tar.bz2
Fix proxy CONNECT response handling
Don't trust most non-success responses to a CONNECT request -- as the BUG= explains, the rest of the stack will treat the response as if it came from the target server, not the proxy. This trivially lets a proxy run code as any HTTPS site the user tries to connect to, which is Very Badâ„¢. Do, however, accept 302 responses, but sanitize them so they contain only the Location header and no response body. Many proxies use this for login pages, so we can't break it. Update the HttpProxyClientSocketPool unittests to expect failure in all but the 302 case, and add a 302-specific test case. BUG=137891 TEST=Added cases to Http- and SpdyProxyClientSocket unittests. net_unittests pass. Review URL: https://chromiumcodereview.appspot.com/10825030 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@150749 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/http')
-rw-r--r--net/http/http_network_transaction_spdy2_unittest.cc46
-rw-r--r--net/http/http_network_transaction_spdy3_unittest.cc46
-rw-r--r--net/http/http_proxy_client_socket.cc32
-rw-r--r--net/http/http_proxy_client_socket.h2
-rw-r--r--net/http/http_proxy_client_socket_pool_spdy2_unittest.cc83
-rw-r--r--net/http/http_proxy_client_socket_pool_spdy3_unittest.cc83
-rw-r--r--net/http/http_response_headers.cc35
-rw-r--r--net/http/http_util.cc30
-rw-r--r--net/http/http_util.h9
-rw-r--r--net/http/proxy_client_socket.cc43
-rw-r--r--net/http/proxy_client_socket.h15
11 files changed, 310 insertions, 114 deletions
diff --git a/net/http/http_network_transaction_spdy2_unittest.cc b/net/http/http_network_transaction_spdy2_unittest.cc
index e6cc4f6..fe0204f 100644
--- a/net/http/http_network_transaction_spdy2_unittest.cc
+++ b/net/http/http_network_transaction_spdy2_unittest.cc
@@ -2523,11 +2523,9 @@ TEST_F(HttpNetworkTransactionSpdy2Test, HttpsProxySpdyConnectFailure) {
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
- EXPECT_EQ(OK, rv);
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
- const HttpResponseInfo* response = trans->GetResponseInfo();
- ASSERT_TRUE(response != NULL);
- EXPECT_EQ(500, response->headers->response_code());
+ // TODO(ttuttle): Anything else to check here?
}
// Test the challenge-response-retry sequence through an HTTPS Proxy
@@ -4783,9 +4781,9 @@ TEST_F(HttpNetworkTransactionSpdy2Test, RedirectOfHttpsConnectViaSpdyProxy) {
EXPECT_EQ("http://login.example.com/", url);
}
-// Test an HTTPS Proxy's ability to provide a response to a CONNECT request
+// Test that an HTTPS proxy's response to a CONNECT request is filtered.
TEST_F(HttpNetworkTransactionSpdy2Test,
- ErrorResponseTofHttpsConnectViaHttpsProxy) {
+ ErrorResponseToHttpsConnectViaHttpsProxy) {
SessionDependencies session_deps(
ProxyService::CreateFixed("https://proxy:70"));
@@ -4823,25 +4821,14 @@ TEST_F(HttpNetworkTransactionSpdy2Test,
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
- EXPECT_EQ(OK, rv);
- const HttpResponseInfo* response = trans->GetResponseInfo();
-
- ASSERT_TRUE(response != NULL);
-
- EXPECT_EQ(404, response->headers->response_code());
- EXPECT_EQ(23, response->headers->GetContentLength());
- EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
- EXPECT_FALSE(response->ssl_info.is_valid());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
- std::string response_data;
- ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
- EXPECT_EQ("The host does not exist", response_data);
+ // TODO(ttuttle): Anything else to check here?
}
-// Test an HTTPS (SPDY) Proxy's ability to provide a response to a CONNECT
-// request
+// Test that a SPDY proxy's response to a CONNECT request is filtered.
TEST_F(HttpNetworkTransactionSpdy2Test,
- ErrorResponseTofHttpsConnectViaSpdyProxy) {
+ ErrorResponseToHttpsConnectViaSpdyProxy) {
SessionDependencies session_deps(
ProxyService::CreateFixed("https://proxy:70"));
@@ -4851,9 +4838,10 @@ TEST_F(HttpNetworkTransactionSpdy2Test,
request.load_flags = 0;
scoped_ptr<SpdyFrame> conn(ConstructSpdyConnect(NULL, 0, 1));
- scoped_ptr<SpdyFrame> goaway(ConstructSpdyRstStream(1, CANCEL));
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
MockWrite data_writes[] = {
CreateMockWrite(*conn.get(), 0, SYNCHRONOUS),
+ CreateMockWrite(*rst.get(), 3, SYNCHRONOUS),
};
static const char* const kExtraHeaders[] = {
@@ -4868,7 +4856,7 @@ TEST_F(HttpNetworkTransactionSpdy2Test,
MockRead data_reads[] = {
CreateMockRead(*resp.get(), 1, SYNCHRONOUS),
CreateMockRead(*body.get(), 2, SYNCHRONOUS),
- MockRead(ASYNC, 0, 3), // EOF
+ MockRead(ASYNC, 0, 4), // EOF
};
DelayedSocketData data(
@@ -4890,17 +4878,9 @@ TEST_F(HttpNetworkTransactionSpdy2Test,
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
- EXPECT_EQ(OK, rv);
- const HttpResponseInfo* response = trans->GetResponseInfo();
-
- ASSERT_TRUE(response != NULL);
-
- EXPECT_EQ(404, response->headers->response_code());
- EXPECT_FALSE(response->ssl_info.is_valid());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
- std::string response_data;
- ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
- EXPECT_EQ("The host does not exist", response_data);
+ // TODO(ttuttle): Anything else to check here?
}
// Test the request-challenge-retry sequence for basic auth, through
diff --git a/net/http/http_network_transaction_spdy3_unittest.cc b/net/http/http_network_transaction_spdy3_unittest.cc
index d63434d..3cbc5dd 100644
--- a/net/http/http_network_transaction_spdy3_unittest.cc
+++ b/net/http/http_network_transaction_spdy3_unittest.cc
@@ -2523,11 +2523,9 @@ TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyConnectFailure) {
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
- EXPECT_EQ(OK, rv);
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
- const HttpResponseInfo* response = trans->GetResponseInfo();
- ASSERT_TRUE(response != NULL);
- EXPECT_EQ(500, response->headers->response_code());
+ // TODO(ttuttle): Anything else to check here?
}
// Test the challenge-response-retry sequence through an HTTPS Proxy
@@ -4783,9 +4781,9 @@ TEST_F(HttpNetworkTransactionSpdy3Test, RedirectOfHttpsConnectViaSpdyProxy) {
EXPECT_EQ("http://login.example.com/", url);
}
-// Test an HTTPS Proxy's ability to provide a response to a CONNECT request
+// Test that an HTTPS proxy's response to a CONNECT request is filtered.
TEST_F(HttpNetworkTransactionSpdy3Test,
- ErrorResponseTofHttpsConnectViaHttpsProxy) {
+ ErrorResponseToHttpsConnectViaHttpsProxy) {
SessionDependencies session_deps(
ProxyService::CreateFixed("https://proxy:70"));
@@ -4823,25 +4821,14 @@ TEST_F(HttpNetworkTransactionSpdy3Test,
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
- EXPECT_EQ(OK, rv);
- const HttpResponseInfo* response = trans->GetResponseInfo();
-
- ASSERT_TRUE(response != NULL);
-
- EXPECT_EQ(404, response->headers->response_code());
- EXPECT_EQ(23, response->headers->GetContentLength());
- EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
- EXPECT_FALSE(response->ssl_info.is_valid());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
- std::string response_data;
- ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
- EXPECT_EQ("The host does not exist", response_data);
+ // TODO(ttuttle): Anything else to check here?
}
-// Test an HTTPS (SPDY) Proxy's ability to provide a response to a CONNECT
-// request
+// Test that a SPDY proxy's response to a CONNECT request is filtered.
TEST_F(HttpNetworkTransactionSpdy3Test,
- ErrorResponseTofHttpsConnectViaSpdyProxy) {
+ ErrorResponseToHttpsConnectViaSpdyProxy) {
SessionDependencies session_deps(
ProxyService::CreateFixed("https://proxy:70"));
@@ -4851,9 +4838,10 @@ TEST_F(HttpNetworkTransactionSpdy3Test,
request.load_flags = 0;
scoped_ptr<SpdyFrame> conn(ConstructSpdyConnect(NULL, 0, 1));
- scoped_ptr<SpdyFrame> goaway(ConstructSpdyRstStream(1, CANCEL));
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
MockWrite data_writes[] = {
CreateMockWrite(*conn.get(), 0, SYNCHRONOUS),
+ CreateMockWrite(*rst.get(), 3, SYNCHRONOUS),
};
static const char* const kExtraHeaders[] = {
@@ -4868,7 +4856,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test,
MockRead data_reads[] = {
CreateMockRead(*resp.get(), 1, SYNCHRONOUS),
CreateMockRead(*body.get(), 2, SYNCHRONOUS),
- MockRead(ASYNC, 0, 3), // EOF
+ MockRead(ASYNC, 0, 4), // EOF
};
DelayedSocketData data(
@@ -4890,17 +4878,9 @@ TEST_F(HttpNetworkTransactionSpdy3Test,
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
- EXPECT_EQ(OK, rv);
- const HttpResponseInfo* response = trans->GetResponseInfo();
-
- ASSERT_TRUE(response != NULL);
-
- EXPECT_EQ(404, response->headers->response_code());
- EXPECT_FALSE(response->ssl_info.is_valid());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
- std::string response_data;
- ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
- EXPECT_EQ("The host does not exist", response_data);
+ // TODO(ttuttle): Anything else to check here?
}
// Test the request-challenge-retry sequence for basic auth, through
diff --git a/net/http/http_proxy_client_socket.cc b/net/http/http_proxy_client_socket.cc
index e3dfa06..ec88a0e 100644
--- a/net/http/http_proxy_client_socket.cc
+++ b/net/http/http_proxy_client_socket.cc
@@ -236,7 +236,7 @@ int HttpProxyClientSocket::Read(IOBuffer* buf, int buf_len,
// We reach this case when the user cancels a 407 proxy auth prompt.
// See http://crbug.com/8473.
DCHECK_EQ(407, response_.headers->response_code());
- LogBlockedTunnelResponse(response_.headers->response_code());
+ LogBlockedTunnelResponse();
return ERR_TUNNEL_CONNECTION_FAILED;
}
@@ -309,10 +309,11 @@ int HttpProxyClientSocket::DidDrainBodyForAuthRestart(bool keep_alive) {
return OK;
}
-void HttpProxyClientSocket::LogBlockedTunnelResponse(int response_code) const {
- LOG(WARNING) << "Blocked proxy response with status " << response_code
- << " to CONNECT request for "
- << GetHostAndPort(request_.url) << ".";
+void HttpProxyClientSocket::LogBlockedTunnelResponse() const {
+ ProxyClientSocket::LogBlockedTunnelResponse(
+ response_.headers->response_code(),
+ request_.url,
+ is_https_proxy_);
}
void HttpProxyClientSocket::DoCallback(int result) {
@@ -477,6 +478,19 @@ int HttpProxyClientSocket::DoReadHeadersComplete(int result) {
// The only safe thing to do here is to fail the connection because our
// client is expecting an SSL protected response.
// See http://crbug.com/7338.
+
+ case 302: // Found / Moved Temporarily
+ // Attempt to follow redirects from HTTPS proxies, but only if we can
+ // sanitize the response. This still allows a rogue HTTPS proxy to
+ // redirect an HTTPS site load to a similar-looking site, but no longer
+ // allows it to impersonate the site the user requested.
+ if (is_https_proxy_ && SanitizeProxyRedirect(&response_, request_.url))
+ return ERR_HTTPS_PROXY_TUNNEL_RESPONSE;
+
+ // We're not using an HTTPS proxy, or we couldn't sanitize the redirect.
+ LogBlockedTunnelResponse();
+ return ERR_TUNNEL_CONNECTION_FAILED;
+
case 407: // Proxy Authentication Required
// We need this status code to allow proxy authentication. Our
// authentication code is smart enough to avoid being tricked by an
@@ -485,15 +499,13 @@ int HttpProxyClientSocket::DoReadHeadersComplete(int result) {
return HandleProxyAuthChallenge(auth_, &response_, net_log_);
default:
- if (is_https_proxy_)
- return ERR_HTTPS_PROXY_TUNNEL_RESPONSE;
- // For all other status codes, we conservatively fail the CONNECT
- // request.
+ // Ignore response to avoid letting the proxy impersonate the target
+ // server. (See http://crbug.com/137891.)
// We lose something by doing this. We have seen proxy 403, 404, and
// 501 response bodies that contain a useful error message. For
// example, Squid uses a 404 response to report the DNS error: "The
// domain name does not exist."
- LogBlockedTunnelResponse(response_.headers->response_code());
+ LogBlockedTunnelResponse();
return ERR_TUNNEL_CONNECTION_FAILED;
}
}
diff --git a/net/http/http_proxy_client_socket.h b/net/http/http_proxy_client_socket.h
index 3740aed..24db3f8 100644
--- a/net/http/http_proxy_client_socket.h
+++ b/net/http/http_proxy_client_socket.h
@@ -112,7 +112,7 @@ class HttpProxyClientSocket : public ProxyClientSocket {
int PrepareForAuthRestart();
int DidDrainBodyForAuthRestart(bool keep_alive);
- void LogBlockedTunnelResponse(int response_code) const;
+ void LogBlockedTunnelResponse() const;
void DoCallback(int result);
void OnIOComplete(int result);
diff --git a/net/http/http_proxy_client_socket_pool_spdy2_unittest.cc b/net/http/http_proxy_client_socket_pool_spdy2_unittest.cc
index fee1626..9b28398 100644
--- a/net/http/http_proxy_client_socket_pool_spdy2_unittest.cc
+++ b/net/http/http_proxy_client_socket_pool_spdy2_unittest.cc
@@ -16,6 +16,7 @@
#include "net/http/http_auth_handler_factory.h"
#include "net/http/http_network_session.h"
#include "net/http/http_proxy_client_socket.h"
+#include "net/http/http_response_headers.h"
#include "net/http/http_server_properties_impl.h"
#include "net/proxy/proxy_service.h"
#include "net/socket/client_socket_handle.h"
@@ -529,16 +530,92 @@ TEST_P(HttpProxyClientSocketPoolSpdy2Test, TunnelSetupError) {
data_->RunFor(2);
rv = callback_.WaitForResult();
+ // All Proxy CONNECT responses are not trustworthy
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+}
+
+TEST_P(HttpProxyClientSocketPoolSpdy2Test, TunnelSetupRedirect) {
+ const std::string redirectTarget = "https://foo.google.com/";
+
+ const std::string responseText = "HTTP/1.1 302 Found\r\n"
+ "Location: " + redirectTarget + "\r\n"
+ "Set-Cookie: foo=bar\r\n"
+ "\r\n";
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0,
+ "CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, responseText.c_str()),
+ };
+ scoped_ptr<SpdyFrame> req(
+ ConstructSpdyConnect(kAuthHeaders, kAuthHeadersSize, 1));
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*req, 0, ASYNC),
+ };
+
+ const char* const responseHeaders[] = {
+ "location", redirectTarget.c_str(),
+ "set-cookie", "foo=bar",
+ };
+ const int responseHeadersSize = arraysize(responseHeaders) / 2;
+ scoped_ptr<SpdyFrame> resp(
+ ConstructSpdySynReplyError("302 Found",
+ responseHeaders, responseHeadersSize,
+ 1));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2),
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes),
+ spdy_reads, arraysize(spdy_reads), spdy_writes,
+ arraysize(spdy_writes));
+ AddAuthToCache();
+
+ int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+
+ data_->RunFor(2);
+
+ rv = callback_.WaitForResult();
+
if (GetParam() == HTTP) {
- // HTTP Proxy CONNECT responses are not trustworthy
+ // We don't trust 302 responses to CONNECT from HTTP proxies.
EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
} else {
- // HTTPS or SPDY Proxy CONNECT responses are trustworthy
+ // Expect ProxyClientSocket to return the proxy's response, sanitized.
EXPECT_EQ(ERR_HTTPS_PROXY_TUNNEL_RESPONSE, rv);
EXPECT_TRUE(handle_.is_initialized());
- EXPECT_TRUE(handle_.socket());
+ ASSERT_TRUE(handle_.socket());
+
+ const ProxyClientSocket* tunnel_socket =
+ static_cast<ProxyClientSocket*>(handle_.socket());
+ const HttpResponseInfo* response = tunnel_socket->GetConnectResponseInfo();
+ const HttpResponseHeaders* headers = response->headers;
+
+ // Make sure Set-Cookie header was stripped.
+ EXPECT_FALSE(headers->HasHeader("set-cookie"));
+
+ // Make sure Content-Length: 0 header was added.
+ EXPECT_TRUE(headers->HasHeaderValue("content-length", "0"));
+
+ // Make sure Location header was included and correct.
+ std::string location;
+ EXPECT_TRUE(headers->IsRedirect(&location));
+ EXPECT_EQ(location, redirectTarget);
}
}
diff --git a/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc b/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc
index b9c5f74..47156c2 100644
--- a/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc
+++ b/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc
@@ -16,6 +16,7 @@
#include "net/http/http_auth_handler_factory.h"
#include "net/http/http_network_session.h"
#include "net/http/http_proxy_client_socket.h"
+#include "net/http/http_response_headers.h"
#include "net/http/http_server_properties_impl.h"
#include "net/proxy/proxy_service.h"
#include "net/socket/client_socket_handle.h"
@@ -530,16 +531,92 @@ TEST_P(HttpProxyClientSocketPoolSpdy3Test, TunnelSetupError) {
data_->RunFor(2);
rv = callback_.WaitForResult();
+ // All Proxy CONNECT responses are not trustworthy
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+}
+
+TEST_P(HttpProxyClientSocketPoolSpdy3Test, TunnelSetupRedirect) {
+ const std::string redirectTarget = "https://foo.google.com/";
+
+ const std::string responseText = "HTTP/1.1 302 Found\r\n"
+ "Location: " + redirectTarget + "\r\n"
+ "Set-Cookie: foo=bar\r\n"
+ "\r\n";
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0,
+ "CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, responseText.c_str()),
+ };
+ scoped_ptr<SpdyFrame> req(
+ ConstructSpdyConnect(kAuthHeaders, kAuthHeadersSize, 1));
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*req, 0, ASYNC),
+ };
+
+ const char* const responseHeaders[] = {
+ "location", redirectTarget.c_str(),
+ "set-cookie", "foo=bar",
+ };
+ const int responseHeadersSize = arraysize(responseHeaders) / 2;
+ scoped_ptr<SpdyFrame> resp(
+ ConstructSpdySynReplyError("302 Found",
+ responseHeaders, responseHeadersSize,
+ 1));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2),
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes),
+ spdy_reads, arraysize(spdy_reads), spdy_writes,
+ arraysize(spdy_writes));
+ AddAuthToCache();
+
+ int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+
+ data_->RunFor(2);
+
+ rv = callback_.WaitForResult();
+
if (GetParam() == HTTP) {
- // HTTP Proxy CONNECT responses are not trustworthy
+ // We don't trust 302 responses to CONNECT from HTTP proxies.
EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
} else {
- // HTTPS or SPDY Proxy CONNECT responses are trustworthy
+ // Expect ProxyClientSocket to return the proxy's response, sanitized.
EXPECT_EQ(ERR_HTTPS_PROXY_TUNNEL_RESPONSE, rv);
EXPECT_TRUE(handle_.is_initialized());
- EXPECT_TRUE(handle_.socket());
+ ASSERT_TRUE(handle_.socket());
+
+ const ProxyClientSocket* tunnel_socket =
+ static_cast<ProxyClientSocket*>(handle_.socket());
+ const HttpResponseInfo* response = tunnel_socket->GetConnectResponseInfo();
+ const HttpResponseHeaders* headers = response->headers;
+
+ // Make sure Set-Cookie header was stripped.
+ EXPECT_FALSE(headers->HasHeader("set-cookie"));
+
+ // Make sure Content-Length: 0 header was added.
+ EXPECT_TRUE(headers->HasHeaderValue("content-length", "0"));
+
+ // Make sure Location header was included and correct.
+ std::string location;
+ EXPECT_TRUE(headers->IsRedirect(&location));
+ EXPECT_EQ(location, redirectTarget);
}
}
diff --git a/net/http/http_response_headers.cc b/net/http/http_response_headers.cc
index d46726d..0d2279b 100644
--- a/net/http/http_response_headers.cc
+++ b/net/http/http_response_headers.cc
@@ -99,34 +99,6 @@ bool ShouldUpdateHeader(const std::string::const_iterator& name_begin,
return true;
}
-// Functions for histogram initialization. The code 0 is put in the
-// response map to track response codes that are invalid.
-// TODO(gavinp): Greatly prune the collected codes once we learn which
-// ones are not sent in practice, to reduce upload size & memory use.
-
-enum {
- HISTOGRAM_MIN_HTTP_RESPONSE_CODE = 100,
- HISTOGRAM_MAX_HTTP_RESPONSE_CODE = 599,
-};
-
-std::vector<int> GetAllHttpResponseCodes() {
- std::vector<int> codes;
- codes.reserve(
- HISTOGRAM_MAX_HTTP_RESPONSE_CODE - HISTOGRAM_MIN_HTTP_RESPONSE_CODE + 2);
- codes.push_back(0);
- for (int i = HISTOGRAM_MIN_HTTP_RESPONSE_CODE;
- i <= HISTOGRAM_MAX_HTTP_RESPONSE_CODE; ++i)
- codes.push_back(i);
- return codes;
-}
-
-int MapHttpResponseCode(int code) {
- if (HISTOGRAM_MIN_HTTP_RESPONSE_CODE <= code &&
- code <= HISTOGRAM_MAX_HTTP_RESPONSE_CODE)
- return code;
- return 0;
-}
-
void CheckDoesNotHaveEmbededNulls(const std::string& str) {
// Care needs to be taken when adding values to the raw headers string to
// make sure it does not contain embeded NULLs. Any embeded '\0' may be
@@ -154,7 +126,7 @@ HttpResponseHeaders::HttpResponseHeaders(const std::string& raw_input)
Parse(raw_input);
// The most important thing to do with this histogram is find out
- // the existence of unusual HTTP response codes. As it happens
+ // the existence of unusual HTTP status codes. As it happens
// right now, there aren't double-constructions of response headers
// using this constructor, so our counts should also be accurate,
// without instantiating the histogram in two places. It is also
@@ -164,11 +136,12 @@ HttpResponseHeaders::HttpResponseHeaders(const std::string& raw_input)
// HttpResponseHeader that was serialized, and initialization of the
// new object from that pickle.
UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.HttpResponseCode",
- MapHttpResponseCode(response_code_),
+ HttpUtil::MapStatusCodeForHistogram(
+ response_code_),
// Note the third argument is only
// evaluated once, see macro
// definition for details.
- GetAllHttpResponseCodes());
+ HttpUtil::GetStatusCodesForHistogram());
}
HttpResponseHeaders::HttpResponseHeaders(const Pickle& pickle,
diff --git a/net/http/http_util.cc b/net/http/http_util.cc
index a4c6266..43be4f0 100644
--- a/net/http/http_util.cc
+++ b/net/http/http_util.cc
@@ -730,6 +730,36 @@ bool HttpUtil::HasStrongValidators(HttpVersion version,
return ((date - last_modified).InSeconds() >= 60);
}
+// Functions for histogram initialization. The code 0 is put in the map to
+// track status codes that are invalid.
+// TODO(gavinp): Greatly prune the collected codes once we learn which
+// ones are not sent in practice, to reduce upload size & memory use.
+
+enum {
+ HISTOGRAM_MIN_HTTP_STATUS_CODE = 100,
+ HISTOGRAM_MAX_HTTP_STATUS_CODE = 599,
+};
+
+// static
+std::vector<int> HttpUtil::GetStatusCodesForHistogram() {
+ std::vector<int> codes;
+ codes.reserve(
+ HISTOGRAM_MAX_HTTP_STATUS_CODE - HISTOGRAM_MIN_HTTP_STATUS_CODE + 2);
+ codes.push_back(0);
+ for (int i = HISTOGRAM_MIN_HTTP_STATUS_CODE;
+ i <= HISTOGRAM_MAX_HTTP_STATUS_CODE; ++i)
+ codes.push_back(i);
+ return codes;
+}
+
+// static
+int HttpUtil::MapStatusCodeForHistogram(int code) {
+ if (HISTOGRAM_MIN_HTTP_STATUS_CODE <= code &&
+ code <= HISTOGRAM_MAX_HTTP_STATUS_CODE)
+ return code;
+ return 0;
+}
+
// BNF from section 4.2 of RFC 2616:
//
// message-header = field-name ":" [ field-value ]
diff --git a/net/http/http_util.h b/net/http/http_util.h
index 0bc5687..7560b92 100644
--- a/net/http/http_util.h
+++ b/net/http/http_util.h
@@ -194,6 +194,15 @@ class NET_EXPORT HttpUtil {
const std::string& last_modified_header,
const std::string& date_header);
+ // Gets a vector of common HTTP status codes for histograms of status
+ // codes. Currently returns everything in the range [100, 600), plus 0
+ // (for invalid responses/status codes).
+ static std::vector<int> GetStatusCodesForHistogram();
+
+ // Maps an HTTP status code to one of the status codes in the vector
+ // returned by GetStatusCodesForHistogram.
+ static int MapStatusCodeForHistogram(int code);
+
// Used to iterate over the name/value pairs of HTTP headers. To iterate
// over the values in a multi-value header, use ValuesIterator.
// See AssembleRawHeaders for joining line continuations (this iterator
diff --git a/net/http/proxy_client_socket.cc b/net/http/proxy_client_socket.cc
index fb0a5a2..8882f42 100644
--- a/net/http/proxy_client_socket.cc
+++ b/net/http/proxy_client_socket.cc
@@ -4,6 +4,7 @@
#include "net/http/proxy_client_socket.h"
+#include "base/metrics/histogram.h"
#include "base/stringprintf.h"
#include "googleurl/src/gurl.h"
#include "net/base/host_port_pair.h"
@@ -53,4 +54,46 @@ int ProxyClientSocket::HandleProxyAuthChallenge(HttpAuthController* auth,
return rv;
}
+// static
+void ProxyClientSocket::LogBlockedTunnelResponse(int http_status_code,
+ const GURL& url,
+ bool is_https_proxy) {
+ if (is_https_proxy) {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Net.BlockedTunnelResponse.HttpsProxy",
+ HttpUtil::MapStatusCodeForHistogram(http_status_code),
+ HttpUtil::GetStatusCodesForHistogram());
+ } else {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Net.BlockedTunnelResponse.HttpProxy",
+ HttpUtil::MapStatusCodeForHistogram(http_status_code),
+ HttpUtil::GetStatusCodesForHistogram());
+ }
+}
+
+// static
+bool ProxyClientSocket::SanitizeProxyRedirect(HttpResponseInfo* response,
+ const GURL& url) {
+ DCHECK(response && response->headers);
+
+ std::string location;
+ if (!response->headers->IsRedirect(&location))
+ return false;
+
+ // Return minimal headers; set "Content-length: 0" to ignore response body.
+ std::string fake_response_headers =
+ base::StringPrintf("HTTP/1.0 302 Found\n"
+ "Location: %s\n"
+ "Content-length: 0\n"
+ "Connection: close\n"
+ "\n",
+ location.c_str());
+ std::string raw_headers =
+ HttpUtil::AssembleRawHeaders(fake_response_headers.data(),
+ fake_response_headers.length());
+ response->headers = new HttpResponseHeaders(raw_headers);
+
+ return true;
+}
+
} // namespace net
diff --git a/net/http/proxy_client_socket.h b/net/http/proxy_client_socket.h
index c9b03f38..da255f3 100644
--- a/net/http/proxy_client_socket.h
+++ b/net/http/proxy_client_socket.h
@@ -10,6 +10,8 @@
#include "net/socket/ssl_client_socket.h"
#include "net/socket/stream_socket.h"
+class GURL;
+
namespace net {
class HostPortPair;
@@ -67,6 +69,19 @@ class NET_EXPORT_PRIVATE ProxyClientSocket : public StreamSocket {
HttpResponseInfo* response,
const BoundNetLog& net_log);
+ // Logs (to the log and in a histogram) a blocked CONNECT response.
+ static void LogBlockedTunnelResponse(int http_response_code,
+ const GURL& url,
+ bool is_https_proxy);
+
+ // When a redirect (e.g. 302 response) is received during tunnel
+ // construction, this method should be called to strip everything
+ // but the Location header from the redirect response. If it returns
+ // false, the response should be discarded and tunnel construction should
+ // fail. |url| is for logging purposes.
+ static bool SanitizeProxyRedirect(HttpResponseInfo* response,
+ const GURL& url);
+
private:
DISALLOW_COPY_AND_ASSIGN(ProxyClientSocket);
};