From c7af8b2475369ae8fd1f596c7265b08026c10cb4 Mon Sep 17 00:00:00 2001
From: "wtc@google.com" <wtc@google.com@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Mon, 25 Aug 2008 20:41:46 +0000
Subject: 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
---
 net/base/client_socket_handle.h      |   1 +
 net/base/net_error_list.h            |   3 +
 net/http/http_network_transaction.cc | 183 +++++++++++++++++++++++++++++++----
 net/http/http_network_transaction.h  |  24 ++++-
 4 files changed, 191 insertions(+), 20 deletions(-)

(limited to 'net')

diff --git a/net/base/client_socket_handle.h b/net/base/client_socket_handle.h
index 7d8e896..f033d2a 100644
--- a/net/base/client_socket_handle.h
+++ b/net/base/client_socket_handle.h
@@ -60,6 +60,7 @@ class ClientSocketHandle {
 
   // These may only be used if is_initialized() is true.
   ClientSocket* socket() { return socket_->get(); }
+  ClientSocket* release_socket() { return socket_->release(); }
   void set_socket(ClientSocket* s) { socket_->reset(s); }
 
  private:
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index b35a939..c3b7a60 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -71,6 +71,9 @@ NET_ERROR(ADDRESS_UNREACHABLE, -109)
 // The server requested a client certificate for SSL client authentication.
 NET_ERROR(SSL_CLIENT_AUTH_CERT_NEEDED, -110)
 
+// A tunnel connection through the proxy could not be established.
+NET_ERROR(TUNNEL_CONNECTION_FAILED, -111)
+
 // Certificate error codes
 //
 // The values of certificate error codes must be consecutive.
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 56604ee..6c90c66 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -153,8 +153,8 @@ void HttpNetworkTransaction::BuildRequestHeaders() {
     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 @@ void HttpNetworkTransaction::BuildRequestHeaders() {
   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 @@ int HttpNetworkTransaction::DoLoop(int result) {
       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::DoConnect() {
 }
 
 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 @@ int HttpNetworkTransaction::DoWriteHeaders() {
   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::DoWriteBodyComplete(int result) {
 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 @@ int HttpNetworkTransaction::DoReadBodyComplete(int result) {
   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_));
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index 1d0673d..5386196 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -44,6 +44,7 @@ class HttpNetworkTransaction : public HttpTransaction {
  private:
   ~HttpNetworkTransaction();
   void BuildRequestHeaders();
+  void BuildTunnelRequest();
   void DoCallback(int result);
   void OnIOComplete(int result);
 
@@ -62,6 +63,12 @@ class HttpNetworkTransaction : public HttpTransaction {
   int DoResolveHostComplete(int result);
   int DoConnect();
   int DoConnectComplete(int result);
+  int DoWriteTunnelRequest();
+  int DoWriteTunnelRequestComplete(int result);
+  int DoReadTunnelResponse();
+  int DoReadTunnelResponseComplete(int result);
+  int DoSSLConnectOverTunnel();
+  int DoSSLConnectOverTunnelComplete(int result);
   int DoWriteHeaders();
   int DoWriteHeadersComplete(int result);
   int DoWriteBody();
@@ -71,7 +78,16 @@ class HttpNetworkTransaction : public HttpTransaction {
   int DoReadBody();
   int DoReadBodyComplete(int result);
 
-  // Called when read_buf_ contains the complete response headers.
+  // Called to write the request headers in request_headers_.
+  int WriteRequestHeaders();
+
+  // Called to read the response headers into header_buf_.
+  int ReadResponseHeaders();
+
+  // Called when header_buf_ contains the complete CONNECT response.
+  int DidReadTunnelResponse();
+
+  // Called when header_buf_ contains the complete response headers.
   int DidReadResponseHeaders();
 
   // Called to possibly recover from the given error.  Sets next_state_ and
@@ -138,6 +154,12 @@ class HttpNetworkTransaction : public HttpTransaction {
     STATE_RESOLVE_HOST_COMPLETE,
     STATE_CONNECT,
     STATE_CONNECT_COMPLETE,
+    STATE_WRITE_TUNNEL_REQUEST,
+    STATE_WRITE_TUNNEL_REQUEST_COMPLETE,
+    STATE_READ_TUNNEL_RESPONSE,
+    STATE_READ_TUNNEL_RESPONSE_COMPLETE,
+    STATE_SSL_CONNECT_OVER_TUNNEL,
+    STATE_SSL_CONNECT_OVER_TUNNEL_COMPLETE,
     STATE_WRITE_HEADERS,
     STATE_WRITE_HEADERS_COMPLETE,
     STATE_WRITE_BODY,
-- 
cgit v1.1