Implement basic HTTP tunnel support in bssl client.

For testing purposes.

Change-Id: Ied1b130e805bcf8cc5d1bd30a1ba5049d6f13a6d
Reviewed-on: https://boringssl-review.googlesource.com/17665
Commit-Queue: David Benjamin <davidben@google.com>
Commit-Queue: Steven Valdez <svaldez@google.com>
Reviewed-by: Steven Valdez <svaldez@google.com>
diff --git a/tool/client.cc b/tool/client.cc
index c4a071d..74a7124 100644
--- a/tool/client.cc
+++ b/tool/client.cc
@@ -127,6 +127,10 @@
         "-ed25519", kBooleanArgument, "Advertise Ed25519 support",
     },
     {
+        "-http-tunnel", kOptionalArgument,
+        "An HTTP proxy server to tunnel the TCP connection through",
+    },
+    {
         "", kOptionalArgument, "",
     },
 };
@@ -208,7 +212,12 @@
                          std::map<std::string, std::string> args_map,
                          bool (*cb)(SSL *ssl, int sock)) {
   int sock = -1;
-  if (!Connect(&sock, args_map["-connect"])) {
+  if (args_map.count("-http-tunnel") != 0) {
+    if (!Connect(&sock, args_map["-http-tunnel"]) ||
+        !DoHTTPTunnel(sock, args_map["-connect"])) {
+      return false;
+    }
+  } else if (!Connect(&sock, args_map["-connect"])) {
     return false;
   }
 
diff --git a/tool/transport_common.cc b/tool/transport_common.cc
index 912e680..0fc7c3c 100644
--- a/tool/transport_common.cc
+++ b/tool/transport_common.cc
@@ -70,10 +70,8 @@
   return true;
 }
 
-// Connect sets |*out_sock| to be a socket connected to the destination given
-// in |hostname_and_port|, which should be of the form "www.example.com:123".
-// It returns true on success and false otherwise.
-bool Connect(int *out_sock, const std::string &hostname_and_port) {
+static void SplitHostPort(std::string *out_hostname, std::string *out_port,
+                          const std::string &hostname_and_port) {
   size_t colon_offset = hostname_and_port.find_last_of(':');
   const size_t bracket_offset = hostname_and_port.find_last_of(']');
   std::string hostname, port;
@@ -85,12 +83,20 @@
   }
 
   if (colon_offset == std::string::npos) {
-    hostname = hostname_and_port;
-    port = "443";
+    *out_hostname = hostname_and_port;
+    *out_port = "443";
   } else {
-    hostname = hostname_and_port.substr(0, colon_offset);
-    port = hostname_and_port.substr(colon_offset + 1);
+    *out_hostname = hostname_and_port.substr(0, colon_offset);
+    *out_port = hostname_and_port.substr(colon_offset + 1);
   }
+}
+
+// Connect sets |*out_sock| to be a socket connected to the destination given
+// in |hostname_and_port|, which should be of the form "www.example.com:123".
+// It returns true on success and false otherwise.
+bool Connect(int *out_sock, const std::string &hostname_and_port) {
+  std::string hostname, port;
+  SplitHostPort(&hostname, &port, hostname_and_port);
 
   // Handle IPv6 literals.
   if (hostname.size() >= 2 && hostname[0] == '[' &&
@@ -631,3 +637,31 @@
 
   return true;
 }
+
+bool DoHTTPTunnel(int sock, const std::string &hostname_and_port) {
+  std::string hostname, port;
+  SplitHostPort(&hostname, &port, hostname_and_port);
+
+  fprintf(stderr, "Establishing HTTP tunnel to %s:%s.\n", hostname.c_str(),
+          port.c_str());
+  char buf[1024];
+  snprintf(buf, sizeof(buf), "CONNECT %s:%s HTTP/1.0\r\n\r\n", hostname.c_str(),
+           port.c_str());
+  if (!SendAll(sock, buf, strlen(buf))) {
+    return false;
+  }
+
+  SocketLineReader line_reader(sock);
+
+  // Read until an empty line, signaling the end of the HTTP response.
+  std::string line;
+  for (;;) {
+    if (!line_reader.Next(&line)) {
+      return false;
+    }
+    if (line.empty()) {
+      return true;
+    }
+    fprintf(stderr, "%s\n", line.c_str());
+  }
+}
diff --git a/tool/transport_common.h b/tool/transport_common.h
index 7c157ba..b149671 100644
--- a/tool/transport_common.h
+++ b/tool/transport_common.h
@@ -18,6 +18,8 @@
 #include <openssl/ssl.h>
 #include <string.h>
 
+#include <string>
+
 // InitSocketLibrary calls the Windows socket init functions, if needed.
 bool InitSocketLibrary();
 
@@ -59,4 +61,8 @@
 // returns true on success and false otherwise.
 bool DoSMTPStartTLS(int sock);
 
+// DoHTTPTunnel sends an HTTP CONNECT request over |sock|. It returns true on
+// success and false otherwise.
+bool DoHTTPTunnel(int sock, const std::string &hostname_and_port);
+
 #endif  /* !OPENSSL_HEADER_TOOL_TRANSPORT_COMMON_H */