Implement SSL tunneling through a proxy server.
Add several states to HttpNetworkTransaction for the HTTP
CONNECT method and the SSL Connect (handshake) after the
tunnel is connected.
Add the error code ERR_TUNNEL_CONNECTION_FAILED for failure
to connect a tunnel.
R=darin
BUG=1272555
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@1329 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 56604ee..6c90c669 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -153,8 +153,8 @@
path = request_->url.PathForRequest();
}
- request_headers_ = request_->method + " " + path + " HTTP/1.1\r\n" +
- "Host: " + request_->url.host();
+ request_headers_ = request_->method + " " + path +
+ " HTTP/1.1\r\nHost: " + request_->url.host();
if (request_->url.IntPort() != -1)
request_headers_ += ":" + request_->url.port();
request_headers_ += "\r\n";
@@ -200,6 +200,34 @@
request_headers_ += "\r\n";
}
+// The HTTP CONNECT method for establishing a tunnel connection is documented
+// in draft-luotonen-web-proxy-tunneling-01.txt and RFC 2717, Sections 5.2 and
+// 5.3.
+void HttpNetworkTransaction::BuildTunnelRequest() {
+ std::string port;
+ if (request_->url.has_port()) {
+ port = request_->url.port();
+ } else {
+ port = "443";
+ }
+
+ // RFC 2616 Section 9 says the Host request-header field MUST accompany all
+ // HTTP/1.1 requests.
+ request_headers_ = "CONNECT " + request_->url.host() + ":" + port +
+ " HTTP/1.1\r\nHost: " + request_->url.host();
+ if (request_->url.IntPort() != -1)
+ request_headers_ += ":" + request_->url.port();
+ request_headers_ += "\r\n";
+
+ if (!request_->user_agent.empty())
+ request_headers_ += "User-Agent: " + request_->user_agent + "\r\n";
+
+ // TODO(wtc): Add the Proxy-Authorization header, if necessary, to handle
+ // proxy authentication.
+
+ request_headers_ += "\r\n";
+}
+
void HttpNetworkTransaction::DoCallback(int rv) {
DCHECK(rv != ERR_IO_PENDING);
DCHECK(user_callback_);
@@ -252,6 +280,27 @@
case STATE_CONNECT_COMPLETE:
rv = DoConnectComplete(rv);
break;
+ case STATE_WRITE_TUNNEL_REQUEST:
+ DCHECK(rv == OK);
+ rv = DoWriteTunnelRequest();
+ break;
+ case STATE_WRITE_TUNNEL_REQUEST_COMPLETE:
+ rv = DoWriteTunnelRequestComplete(rv);
+ break;
+ case STATE_READ_TUNNEL_RESPONSE:
+ DCHECK(rv == OK);
+ rv = DoReadTunnelResponse();
+ break;
+ case STATE_READ_TUNNEL_RESPONSE_COMPLETE:
+ rv = DoReadTunnelResponseComplete(rv);
+ break;
+ case STATE_SSL_CONNECT_OVER_TUNNEL:
+ DCHECK(rv == OK);
+ rv = DoSSLConnectOverTunnel();
+ break;
+ case STATE_SSL_CONNECT_OVER_TUNNEL_COMPLETE:
+ rv = DoSSLConnectOverTunnelComplete(rv);
+ break;
case STATE_WRITE_HEADERS:
DCHECK(rv == OK);
rv = DoWriteHeaders();
@@ -403,6 +452,80 @@
}
int HttpNetworkTransaction::DoConnectComplete(int result) {
+ if (result == OK) {
+ if (using_tunnel_) {
+ next_state_ = STATE_WRITE_TUNNEL_REQUEST;
+ } else {
+ next_state_ = STATE_WRITE_HEADERS;
+ }
+ }
+ return result;
+}
+
+int HttpNetworkTransaction::DoWriteTunnelRequest() {
+ next_state_ = STATE_WRITE_TUNNEL_REQUEST_COMPLETE;
+
+ if (request_headers_.empty())
+ BuildTunnelRequest();
+
+ return WriteRequestHeaders();
+}
+
+int HttpNetworkTransaction::DoWriteTunnelRequestComplete(int result) {
+ if (result < 0)
+ return result;
+
+ request_headers_bytes_sent_ += result;
+ if (request_headers_bytes_sent_ < request_headers_.size()) {
+ next_state_ = STATE_WRITE_TUNNEL_REQUEST;
+ } else {
+ next_state_ = STATE_READ_TUNNEL_RESPONSE;
+ // Reset for writing the real request headers later.
+ request_headers_.clear();
+ request_headers_bytes_sent_ = 0;
+ }
+ return OK;
+}
+
+int HttpNetworkTransaction::DoReadTunnelResponse() {
+ next_state_ = STATE_READ_TUNNEL_RESPONSE_COMPLETE;
+
+ return ReadResponseHeaders();
+}
+
+int HttpNetworkTransaction::DoReadTunnelResponseComplete(int result) {
+ if (result < 0)
+ return result;
+ if (result == 0) // The socket was closed before the tunnel is established.
+ return ERR_TUNNEL_CONNECTION_FAILED;
+
+ header_buf_len_ += result;
+ DCHECK(header_buf_len_ <= header_buf_capacity_);
+
+ int eoh = HttpUtil::LocateEndOfHeaders(header_buf_.get(), header_buf_len_);
+ if (eoh == -1) {
+ next_state_ = STATE_READ_TUNNEL_RESPONSE; // Read more.
+ return OK;
+ }
+ if (eoh != header_buf_len_) {
+ // The proxy sent extraneous data after the headers.
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+
+ // And, we are done with the SSL tunnel CONNECT sequence.
+ return DidReadTunnelResponse();
+}
+
+int HttpNetworkTransaction::DoSSLConnectOverTunnel() {
+ next_state_ = STATE_SSL_CONNECT_OVER_TUNNEL_COMPLETE;
+
+ ClientSocket* s = connection_.release_socket();
+ s = socket_factory_->CreateSSLClientSocket(s, request_->url.host());
+ connection_.set_socket(s);
+ return connection_.socket()->Connect(&io_callback_);
+}
+
+int HttpNetworkTransaction::DoSSLConnectOverTunnelComplete(int result) {
if (result == OK)
next_state_ = STATE_WRITE_HEADERS;
return result;
@@ -421,12 +544,7 @@
if (request_headers_bytes_sent_ == 0)
response_.request_time = Time::Now();
- const char* buf = request_headers_.data() + request_headers_bytes_sent_;
- int buf_len = static_cast<int>(request_headers_.size() -
- request_headers_bytes_sent_);
- DCHECK(buf_len > 0);
-
- return connection_.socket()->Write(buf, buf_len, &io_callback_);
+ return WriteRequestHeaders();
}
int HttpNetworkTransaction::DoWriteHeadersComplete(int result) {
@@ -473,17 +591,7 @@
int HttpNetworkTransaction::DoReadHeaders() {
next_state_ = STATE_READ_HEADERS_COMPLETE;
- // Grow the read buffer if necessary.
- if (header_buf_len_ == header_buf_capacity_) {
- header_buf_capacity_ += kHeaderBufInitialSize;
- header_buf_.reset(static_cast<char*>(
- realloc(header_buf_.release(), header_buf_capacity_)));
- }
-
- char* buf = header_buf_.get() + header_buf_len_;
- int buf_len = header_buf_capacity_ - header_buf_len_;
-
- return connection_.socket()->Read(buf, buf_len, &io_callback_);
+ return ReadResponseHeaders();
}
int HttpNetworkTransaction::DoReadHeadersComplete(int result) {
@@ -591,6 +699,43 @@
return result;
}
+int HttpNetworkTransaction::WriteRequestHeaders() {
+ const char* buf = request_headers_.data() + request_headers_bytes_sent_;
+ int buf_len = static_cast<int>(request_headers_.size() -
+ request_headers_bytes_sent_);
+ DCHECK(buf_len > 0);
+
+ return connection_.socket()->Write(buf, buf_len, &io_callback_);
+}
+
+int HttpNetworkTransaction::ReadResponseHeaders() {
+ // Grow the read buffer if necessary.
+ if (header_buf_len_ == header_buf_capacity_) {
+ header_buf_capacity_ += kHeaderBufInitialSize;
+ header_buf_.reset(static_cast<char*>(
+ realloc(header_buf_.release(), header_buf_capacity_)));
+ }
+
+ char* buf = header_buf_.get() + header_buf_len_;
+ int buf_len = header_buf_capacity_ - header_buf_len_;
+
+ return connection_.socket()->Read(buf, buf_len, &io_callback_);
+}
+
+int HttpNetworkTransaction::DidReadTunnelResponse() {
+ // TODO(wtc): Require the "HTTP/1.x" status line. The HttpResponseHeaders
+ // constructor makes up an "HTTP/1.0 200 OK" status line if it is missing.
+ scoped_refptr<HttpResponseHeaders> headers = new HttpResponseHeaders(
+ HttpUtil::AssembleRawHeaders(header_buf_.get(), header_buf_len_));
+ // TODO(wtc): Handle 407 proxy authentication challenge.
+ if (headers->response_code() != 200)
+ return ERR_TUNNEL_CONNECTION_FAILED;
+
+ next_state_ = STATE_SSL_CONNECT_OVER_TUNNEL;
+ header_buf_len_ = 0; // Reset for reading the real response headers later.
+ return OK;
+}
+
int HttpNetworkTransaction::DidReadResponseHeaders() {
scoped_refptr<HttpResponseHeaders> headers = new HttpResponseHeaders(
HttpUtil::AssembleRawHeaders(header_buf_.get(), header_buf_body_offset_));