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_));