shill: Support CONNECT HTTP method in proxy
This will allow HTTPS requests to be proxied.
BUG=chromium-os:24220
TEST=New unit tests +
manual "curl -v -x localhost:xxx https://www.google.com" test
Change-Id: Ibdec3536edb97018f6a7e7545025dd792998e7b0
Reviewed-on: https://gerrit.chromium.org/gerrit/13955
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Reviewed-by: Thieu Le <thieule@chromium.org>
Commit-Ready: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/http_proxy.cc b/http_proxy.cc
index 1ba1d21..2075c65 100644
--- a/http_proxy.cc
+++ b/http_proxy.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -42,6 +42,8 @@
const size_t HTTPProxy::kMaxHeaderSize = 2048;
const int HTTPProxy::kTransactionTimeoutSeconds = 600;
+const char HTTPProxy::kHTTPMethodConnect[] = "connect";
+const char HTTPProxy::kHTTPMethodTerminator[] = " ";
const char HTTPProxy::kHTTPURLDelimiters[] = " /#?";
const char HTTPProxy::kHTTPURLPrefix[] = "http://";
const char HTTPProxy::kHTTPVersionPrefix[] = " HTTP/1";
@@ -207,6 +209,14 @@
}
server_socket_ = fd;
state_ = kStateTunnelData;
+
+ // If this was a "CONNECT" request, notify the client that the connection
+ // has been established by sending an "OK" response.
+ if (LowerCaseEqualsASCII(client_method_, kHTTPMethodConnect)) {
+ SetClientResponse(200, "OK", "", "");
+ StartReceive();
+ }
+
StartTransmit();
}
@@ -245,11 +255,13 @@
// Assemble the request as it will be sent to the server.
client_data_.Clear();
- for (vector<string>::iterator it = client_headers_.begin();
- it != client_headers_.end(); ++it) {
- client_data_.Append(ByteString(*it + "\r\n", false));
+ if (!LowerCaseEqualsASCII(client_method_, kHTTPMethodConnect)) {
+ for (vector<string>::iterator it = client_headers_.begin();
+ it != client_headers_.end(); ++it) {
+ client_data_.Append(ByteString(*it + "\r\n", false));
+ }
+ client_data_.Append(ByteString(string("\r\n"), false));
}
- client_data_.Append(ByteString(string("\r\n"), false));
TrimWhitespaceASCII(host, TRIM_ALL, &host);
if (host.empty()) {
@@ -314,7 +326,9 @@
// Is this is the first header line?
if (client_headers_.size() == 1) {
- if (!ReadClientHTTPVersion(header) || !ReadClientHostname(header)) {
+ if (!ReadClientHTTPMethod(header) ||
+ !ReadClientHTTPVersion(header) ||
+ !ReadClientHostname(header)) {
return false;
}
}
@@ -409,6 +423,17 @@
return true;
}
+bool HTTPProxy::ReadClientHTTPMethod(string *header) {
+ size_t method_end = header->find(kHTTPMethodTerminator);
+ if (method_end == string::npos || method_end == 0) {
+ LOG(ERROR) << "Could not parse HTTP method. Line was: " << *header;
+ SendClientError(501, "Server could not parse HTTP method");
+ return false;
+ }
+ client_method_ = header->substr(0, method_end);
+ return true;
+}
+
// Extract the HTTP version number from the first line of the client headers.
// Returns true if found.
bool HTTPProxy::ReadClientHTTPVersion(string *header) {
@@ -481,15 +506,27 @@
void HTTPProxy::SendClientError(int code, const string &error) {
VLOG(3) << "In " << __func__;
LOG(ERROR) << "Sending error " << error;
-
- string error_msg = StringPrintf("HTTP/1.1 %d ERROR\r\n"
- "Content-Type: text/plain\r\n\r\n"
- "%s", code, error.c_str());
- server_data_ = ByteString(error_msg, false);
+ SetClientResponse(code, "ERROR", "text/plain", error);
state_ = kStateFlushResponse;
StartTransmit();
}
+// Create an HTTP response message to be sent to the client.
+void HTTPProxy::SetClientResponse(int code, const string &type,
+ const string &content_type,
+ const string &message) {
+ string content_line;
+ if (!message.empty() && !content_type.empty()) {
+ content_line = StringPrintf("Content-Type: %s\r\n", content_type.c_str());
+ }
+ string response = StringPrintf("HTTP/1.1 %d %s\r\n"
+ "%s\r\n"
+ "%s", code, type.c_str(),
+ content_line.c_str(),
+ message.c_str());
+ server_data_ = ByteString(response, false);
+}
+
// Start a timeout for "the next event". This timeout augments the overall
// transaction timeout to make sure there is some activity occurring at
// reasonable intervals.
@@ -587,6 +624,7 @@
client_socket_ = -1;
}
client_headers_.clear();
+ client_method_.clear();
client_version_.clear();
server_port_ = kDefaultServerPort;
write_server_handler_.reset();