changeset 16855:c3884a6acde6

lib-http: Added support for creating CONNECT tunnels through HTTP.
author Stephan Bosch <stephan@rename-it.nl>
date Sat, 12 Oct 2013 11:05:08 +0300
parents 462ae2cb8094
children b9498573f0d0
files src/lib-http/http-client-connection.c src/lib-http/http-client-host.c src/lib-http/http-client-peer.c src/lib-http/http-client-private.h src/lib-http/http-client-request.c src/lib-http/http-client.h src/lib-http/http-response-parser.c src/lib-http/http-response-parser.h src/lib-http/http-response.h
diffstat 9 files changed, 319 insertions(+), 89 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-http/http-client-connection.c	Sat Oct 12 11:00:15 2013 +0300
+++ b/src/lib-http/http-client-connection.c	Sat Oct 12 11:05:08 2013 +0300
@@ -57,7 +57,7 @@
 bool http_client_connection_is_ready(struct http_client_connection *conn)
 {
 	return (conn->connected && !conn->output_locked &&
-		!conn->close_indicated &&
+		!conn->close_indicated && !conn->tunneling &&
 		http_client_connection_count_pending(conn) <
 			conn->client->set.max_pipelined_requests);
 }
@@ -173,7 +173,8 @@
 {
 	unsigned int timeout, count;
 
-	if (array_count(&conn->request_wait_list) == 0 &&
+	if (array_is_created(&conn->request_wait_list) &&
+		array_count(&conn->request_wait_list) == 0 &&
 		conn->incoming_payload == NULL &&
 		conn->client->set.max_idle_time_msecs > 0) {
 
@@ -302,6 +303,9 @@
 		return -1;
 	}
 
+	if (req->connect_tunnel)
+		conn->tunneling = TRUE;
+
 	/* https://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-21;
 			Section 6.1.2.1:
 
@@ -388,6 +392,7 @@
 {
 	struct http_client_connection *conn = req->conn;
 
+	i_assert(conn != NULL);
 	i_assert(conn->pending_request == req);
 	i_assert(conn->incoming_payload != NULL);
 	i_assert(conn->conn.io == NULL);
@@ -479,7 +484,8 @@
 	}
 
 	if (conn->incoming_payload == NULL) {
-		i_assert(conn->conn.io != NULL);
+		i_assert(conn->conn.io != NULL ||
+			conn->peer->addr.type == HTTP_CLIENT_PEER_ADDR_RAW);
 		return TRUE;
 	}
 
@@ -495,7 +501,7 @@
 	struct http_client_request *req = NULL;
 	int finished = 0, ret;
 	const char *error;
-	bool no_payload = FALSE;
+	enum http_response_payload_type payload_type;
 
 	i_assert(conn->incoming_payload == NULL);
 
@@ -516,18 +522,16 @@
 		req_idx = array_idx(&conn->request_wait_list, 0);
 		req = req_idx[0];
 
-		/* https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21
-		     Section 3.3.2:
-
-		   A server MAY send a Content-Length header field in a response to a
-		   HEAD request [...]
-		 */
-		no_payload = (strcmp(req->method, "HEAD") == 0);
+		/* determine whether to expect a response payload */
+		payload_type = http_client_request_get_payload_type(req);
+	} else {
+		req = NULL;
+		payload_type = HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED;
 	}
 
 	// FIXME: handle somehow if server replies before request->input is at EOF
 	while ((ret=http_response_parse_next
-		(conn->http_parser, no_payload, &response, &error)) > 0) {
+		(conn->http_parser, payload_type, &response, &error)) > 0) {
 		bool aborted;
 
 		if (req == NULL) {
@@ -621,13 +625,15 @@
 		if (array_count(&conn->request_wait_list) > 0) {
 			req_idx = array_idx(&conn->request_wait_list, 0);
 			req = req_idx[0];
-			no_payload = (strcmp(req->method, "HEAD") == 0);
+
+			/* determine whether to expect a response payload */
+			payload_type = http_client_request_get_payload_type(req);
 		} else {
 			/* no more requests waiting for the connection */
 			if (conn->to_requests != NULL)
 				timeout_remove(&conn->to_requests);
 			req = NULL;
-			no_payload = FALSE;
+			payload_type = HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED;
 		}
 	}
 
@@ -702,6 +708,33 @@
 	return 1;
 }
 
+void
+http_client_connection_start_tunnel(struct http_client_connection **_conn,
+	struct http_client_tunnel *tunnel)
+{
+	struct http_client_connection *conn = *_conn;
+
+	i_assert(conn->tunneling);
+
+	/* claim connection streams */
+	memset(tunnel, 0, sizeof(*tunnel));
+	tunnel->input = conn->conn.input;
+	tunnel->output = conn->conn.output;
+	tunnel->fd_in = conn->conn.fd_in;
+	tunnel->fd_out = conn->conn.fd_out;
+
+	/* detach from connection */
+	conn->conn.input = NULL;
+	conn->conn.output = NULL;
+	conn->conn.fd_in = -1;
+	conn->conn.fd_out = -1;
+	conn->closing = TRUE;
+	conn->connected = FALSE;
+	connection_disconnect(&conn->conn);
+
+	http_client_connection_unref(_conn);
+}
+
 static void 
 http_client_connection_ready(struct http_client_connection *conn)
 {
@@ -725,6 +758,34 @@
 				       &conn->conn.input, &conn->conn.output);
 	}
 
+	/* direct tunneling connections handle connect requests just by providing a
+	   raw connection */
+	if (conn->peer->addr.type == HTTP_CLIENT_PEER_ADDR_RAW) {
+		struct http_client_request *req;
+		
+		req = http_client_peer_claim_request(conn->peer, FALSE);
+		if (req != NULL) {
+			struct http_response response;
+
+			http_client_request_ref(req);
+			req->conn = conn;
+			conn->tunneling = TRUE;
+
+			memset(&response, 0, sizeof(response));
+			response.status = 200;
+			response.reason = "OK";
+
+			(void)http_client_connection_return_response(conn, req, &response);
+			http_client_request_unref(&req);
+			return;
+		} 
+		
+		http_client_connection_debug(conn,
+			"No raw connect requests pending; closing useless connection");
+		http_client_connection_unref(&conn);
+		return;
+	}
+
 	/* start protocol I/O */
 	conn->http_parser = http_response_parser_init
 		(conn->conn.input, &conn->client->set.response_hdr_limits);
@@ -881,13 +942,27 @@
 	struct http_client_connection *conn;
 	static unsigned int id = 0;
 	const struct http_client_peer_addr *addr = &peer->addr;
+	const char *conn_type = "UNKNOWN";
+
+	switch (peer->addr.type) {
+	case HTTP_CLIENT_PEER_ADDR_HTTP:
+		conn_type = "HTTP";
+		break;
+	case HTTP_CLIENT_PEER_ADDR_HTTPS:
+		conn_type = "HTTPS";
+		break;
+	case HTTP_CLIENT_PEER_ADDR_RAW:
+		conn_type = "Raw";
+		break;
+	}
 
 	conn = i_new(struct http_client_connection, 1);
 	conn->refcount = 1;
 	conn->client = peer->client;
 	conn->id = id++;
 	conn->peer = peer;
-	i_array_init(&conn->request_wait_list, 16);
+	if (peer->addr.type != HTTP_CLIENT_PEER_ADDR_RAW)
+		i_array_init(&conn->request_wait_list, 16);
 
 	connection_init_client_ip
 		(peer->client->conn_list, &conn->conn, &addr->ip, addr->port);
@@ -896,8 +971,9 @@
 	array_append(&peer->conns, &conn, 1);
 
 	http_client_connection_debug(conn,
-		"Connection created (%d parallel connections exist)%s",
-		array_count(&peer->conns), (conn->to_input == NULL ? "" : " [broken]"));
+		"%s connection created (%d parallel connections exist)%s",
+		conn_type, array_count(&peer->conns),
+		(conn->to_input == NULL ? "" : " [broken]"));
 	return conn;
 }
 
@@ -910,6 +986,7 @@
 {
 	struct http_client_connection *conn = *_conn;
 	struct http_client_connection *const *conn_idx;
+	ARRAY_TYPE(http_client_connection) *conn_arr;
 	struct http_client_peer *peer = conn->peer;
 	struct http_client_request **req;
 
@@ -931,17 +1008,19 @@
 
 	connection_disconnect(&conn->conn);
 
-	/* abort all pending requests */
-	array_foreach_modifiable(&conn->request_wait_list, req) {
-		i_assert((*req)->submitted);
-		http_client_request_error(*req, HTTP_CLIENT_REQUEST_ERROR_ABORTED,
-			"Aborting");
+	if (array_is_created(&conn->request_wait_list)) {
+		/* abort all pending requests */
+		array_foreach_modifiable(&conn->request_wait_list, req) {
+			i_assert((*req)->submitted);
+			http_client_request_error(*req, HTTP_CLIENT_REQUEST_ERROR_ABORTED,
+				"Aborting");
+		}
+		array_free(&conn->request_wait_list);
 	}
 	if (conn->pending_request != NULL) {
 		http_client_request_error(conn->pending_request,
 			HTTP_CLIENT_REQUEST_ERROR_ABORTED, "Aborting");
 	}
-	array_free(&conn->request_wait_list);
 
 	if (conn->http_parser != NULL)
 		http_response_parser_deinit(&conn->http_parser);
@@ -964,10 +1043,10 @@
 		timeout_remove(&conn->to_response);
 	
 	/* remove this connection from the list */
-	array_foreach(&conn->peer->conns, conn_idx) {
+	conn_arr = &conn->peer->conns;
+	array_foreach(conn_arr, conn_idx) {
 		if (*conn_idx == conn) {
-			array_delete(&conn->peer->conns,
-				array_foreach_idx(&conn->peer->conns, conn_idx), 1);
+			array_delete(conn_arr, array_foreach_idx(conn_arr, conn_idx), 1);
 			break;
 		}
 	}
--- a/src/lib-http/http-client-host.c	Sat Oct 12 11:00:15 2013 +0300
+++ b/src/lib-http/http-client-host.c	Sat Oct 12 11:05:08 2013 +0300
@@ -46,13 +46,13 @@
 
 static struct http_client_host_port *
 http_client_host_port_find(struct http_client_host *host,
-	in_port_t port, const char *https_name)
+	const struct http_client_peer_addr *addr)
 {
 	struct http_client_host_port *hport;
 
 	array_foreach_modifiable(&host->ports, hport) {
-		if (hport->addr.port == port &&
-		    null_strcmp(hport->addr.https_name, https_name) == 0)
+		if (hport->addr.type == addr->type && hport->addr.port == addr->port &&
+		    null_strcmp(hport->addr.https_name, addr->https_name) == 0)
 			return hport;
 	}
 
@@ -61,16 +61,17 @@
 
 static struct http_client_host_port *
 http_client_host_port_init(struct http_client_host *host,
-	in_port_t port, const char *https_name)
+	const struct http_client_peer_addr *addr)
 {
 	struct http_client_host_port *hport;
 
-	hport = http_client_host_port_find(host, port, https_name);
+	hport = http_client_host_port_find(host, addr);
 	if (hport == NULL) {
 		hport = array_append_space(&host->ports);
 		hport->host = host;
-		hport->addr.port = port;
-		hport->addr.https_name = i_strdup(https_name);
+		hport->addr = *addr;
+		hport->https_name = i_strdup(addr->https_name);
+		hport->addr.https_name = hport->https_name;
 		hport->ips_connect_idx = 0;
 		i_array_init(&hport->request_queue, 16);
 	}
@@ -94,7 +95,7 @@
 {
 	http_client_host_port_error
 		(hport, HTTP_CLIENT_REQUEST_ERROR_ABORTED, "Aborted");
-	i_free(hport->addr.https_name);
+	i_free(hport->https_name);
 	if (array_is_created(&hport->pending_peers))
 		array_free(&hport->pending_peers);
 	array_free(&hport->request_queue);
@@ -104,13 +105,12 @@
 http_client_host_port_drop_request(struct http_client_host_port *hport,
 	struct http_client_request *req)
 {
+	ARRAY_TYPE(http_client_request) *req_arr = &hport->request_queue;
 	struct http_client_request **req_idx;
-	unsigned int idx;
 
-	array_foreach_modifiable(&hport->request_queue, req_idx) {
+	array_foreach_modifiable(req_arr, req_idx) {
 		if (*req_idx == req) {
-			idx = array_foreach_idx(&hport->request_queue, req_idx);
-			array_delete(&hport->request_queue, idx, 1);
+			array_delete(req_arr, array_foreach_idx(req_arr, req_idx), 1);
 			break;
 		}
 	}
@@ -309,7 +309,7 @@
 	http_client_host_debug(host, "Successfully connected to %s",
 		http_client_peer_addr2str(addr));
 
-	hport = http_client_host_port_find(host, addr->port, addr->https_name);
+	hport = http_client_host_port_find(host, addr);
 	if (hport == NULL)
 		return;
 
@@ -324,7 +324,7 @@
 	http_client_host_debug(host, "Failed to connect to %s: %s",
 		http_client_peer_addr2str(addr), reason);
 
-	hport = http_client_host_port_find(host, addr->port, addr->https_name);
+	hport = http_client_host_port_find(host, addr);
 	if (hport == NULL)
 		return;
 
@@ -455,8 +455,7 @@
 {
 	struct http_client_host_port *hport;
 	const struct http_url *host_url = req->host_url;
-	const char *https_name = http_client_request_https_name(req);
-	in_port_t port = http_client_request_port(req);
+	struct http_client_peer_addr addr;
 	const char *error;
 
 	req->host = host;
@@ -469,8 +468,10 @@
 		}
 	}
 
+	http_client_request_get_peer_addr(req, &addr);
+
 	/* add request to host (grouped by tcp port) */
-	hport = http_client_host_port_init(host, port, https_name);
+	hport = http_client_host_port_init(host, &addr);
 	if (req->urgent)
 		array_insert(&hport->request_queue, 0, &req, 1);
 	else
@@ -496,7 +497,7 @@
 	struct http_client_request *req;
 	unsigned int i, count;
 
-	hport = http_client_host_port_find(host, addr->port, addr->https_name);
+	hport = http_client_host_port_find(host, addr);
 	if (hport == NULL)
 		return NULL;
 
@@ -530,13 +531,17 @@
 
 	*num_urgent_r = 0;
 
-	hport = http_client_host_port_find(host, addr->port, addr->https_name);
+	hport = http_client_host_port_find(host, addr);
 	if (hport == NULL)
 		return 0;
 
 	requests = array_get(&hport->request_queue, &count);
-	for (i = 0; i < count && requests[i]->urgent; i++)
-		(*num_urgent_r)++;
+	for (i = 0; i < count; i++) {
+		if (requests[i]->urgent)
+			(*num_urgent_r)++;
+		else
+			break;
+	}
 	return count;
 }
 
@@ -544,10 +549,11 @@
 	struct http_client_request *req)
 {
 	struct http_client_host_port *hport;
-	const char *https_name = http_client_request_https_name(req);
-	in_port_t port = http_client_request_port(req);
+	struct http_client_peer_addr addr;
 
-	hport = http_client_host_port_find(host, port, https_name);
+	http_client_request_get_peer_addr(req, &addr);
+
+	hport = http_client_host_port_find(host, &addr);
 	if (hport == NULL)
 		return;
 
--- a/src/lib-http/http-client-peer.c	Sat Oct 12 11:00:15 2013 +0300
+++ b/src/lib-http/http-client-peer.c	Sat Oct 12 11:05:08 2013 +0300
@@ -42,8 +42,17 @@
 unsigned int http_client_peer_addr_hash
 (const struct http_client_peer_addr *peer)
 {
-	return net_ip_hash(&peer->ip) + peer->port +
-		(peer->https_name == NULL ? 0 : str_hash(peer->https_name));
+	switch (peer->type) {
+	case HTTP_CLIENT_PEER_ADDR_RAW:
+		return net_ip_hash(&peer->ip) + peer->port + 1;
+	case HTTP_CLIENT_PEER_ADDR_HTTP:
+		return net_ip_hash(&peer->ip) + peer->port;
+	case HTTP_CLIENT_PEER_ADDR_HTTPS:
+		return net_ip_hash(&peer->ip) + peer->port +
+			(peer->https_name == NULL ? 0 : str_hash(peer->https_name));
+	}
+	i_unreached();
+	return 0;
 }
 
 int http_client_peer_addr_cmp
@@ -52,10 +61,14 @@
 {
 	int ret;
 
+	if (peer1->type != peer2->type)
+		return (peer1->type > peer2->type ? 1 : -1);
 	if ((ret=net_ip_cmp(&peer1->ip, &peer2->ip)) != 0)
 		return ret;
 	if (peer1->port != peer2->port)
 		return (peer1->port > peer2->port ? 1 : -1);
+	if (peer1->type != HTTP_CLIENT_PEER_ADDR_HTTPS)
+		return 0;
 	return null_strcmp(peer1->https_name, peer2->https_name);
 }
 
@@ -69,7 +82,8 @@
 	unsigned int i;
 
 	for (i = 0; i < count; i++) {
-		http_client_peer_debug(peer, "Making new connection %u of %u", i+1, count);
+		http_client_peer_debug(peer,
+			"Making new connection %u of %u", i+1, count);
 		(void)http_client_connection_create(peer);
 	}
 }
@@ -339,7 +353,8 @@
 	peer = i_new(struct http_client_peer, 1);
 	peer->client = client;
 	peer->addr = *addr;
-	peer->addr.https_name = i_strdup(addr->https_name);
+	peer->https_name = i_strdup(addr->https_name);
+	peer->addr.https_name = peer->https_name;
 	i_array_init(&peer->hosts, 16);
 	i_array_init(&peer->conns, 16);
 
@@ -348,7 +363,6 @@
 	DLLIST_PREPEND(&client->peers_list, peer);
 
 	http_client_peer_debug(peer, "Peer created");
-	http_client_peer_connect(peer, 1);
 	return peer;
 }
 
@@ -370,7 +384,6 @@
 	/* make a copy of the connection array; freed connections modify it */
 	t_array_init(&conns, array_count(&peer->conns));
 	array_copy(&conns.arr, 0, &peer->conns.arr, 0, array_count(&peer->conns));
-
 	array_foreach_modifiable(&conns, conn) {
 		http_client_connection_unref(conn);
 	}
@@ -383,7 +396,7 @@
 		(peer->client->peers, (const struct http_client_peer_addr *)&peer->addr);
 	DLLIST_REMOVE(&peer->client->peers_list, peer);
 
-	i_free(peer->addr.https_name);
+	i_free(peer->https_name);
 	i_free(peer);
 	*_peer = NULL;
 }
--- a/src/lib-http/http-client-private.h	Sat Oct 12 11:00:15 2013 +0300
+++ b/src/lib-http/http-client-private.h	Sat Oct 12 11:05:08 2013 +0300
@@ -14,6 +14,8 @@
 #define HTTP_CLIENT_DEFAULT_REQUEST_TIMEOUT_MSECS (1000*60*5)
 #define HTTP_CLIENT_CONTINUE_TIMEOUT_MSECS (1000*2)
 
+enum http_response_payload_type;
+
 struct http_client_host;
 struct http_client_host_port;
 struct http_client_peer;
@@ -30,8 +32,15 @@
 HASH_TABLE_DEFINE_TYPE(http_client_peer, const struct http_client_peer_addr *,
 	struct http_client_peer *);
 
+enum http_client_peer_addr_type {
+	HTTP_CLIENT_PEER_ADDR_HTTP = 0,
+	HTTP_CLIENT_PEER_ADDR_HTTPS,
+	HTTP_CLIENT_PEER_ADDR_RAW
+};
+
 struct http_client_peer_addr {
-	char *https_name; /* TLS SNI */
+	enum http_client_peer_addr_type type;
+	const char *https_name; /* TLS SNI */
 	struct ip_addr ip;
 	in_port_t port;
 };
@@ -86,12 +95,15 @@
 	unsigned int payload_wait:1;
 	unsigned int urgent:1;
 	unsigned int submitted:1;
+	unsigned int connect_tunnel:1;
+	unsigned int connect_direct:1;
 };
 
 struct http_client_host_port {
 	struct http_client_host *host;
 
 	struct http_client_peer_addr addr;
+	char *https_name;
 
 	/* current index in host->ips */
 	unsigned int ips_connect_idx;
@@ -132,6 +144,8 @@
 
 struct http_client_peer {
 	struct http_client_peer_addr addr;
+	char *https_name;
+
 	struct http_client *client;
 	struct http_client_peer *prev, *next;
 
@@ -178,6 +192,8 @@
 	ARRAY_TYPE(http_client_request) request_wait_list;
 
 	unsigned int connected:1;           /* connection is connected */
+	unsigned int tunneling:1;          /* last sent request turns this
+	                                      connection into tunnel */
 	unsigned int connect_succeeded:1;
 	unsigned int closing:1;
 	unsigned int close_indicated:1;
@@ -221,21 +237,24 @@
 	return req->label;
 }
 
-static inline in_port_t
-http_client_request_port(const struct http_client_request *req)
+static inline void
+http_client_request_get_peer_addr(const struct http_client_request *req,
+	struct http_client_peer_addr *addr)
 {
 	const struct http_url *host_url = req->host_url;
-
-	return (host_url->have_port ? host_url->port :
-		(host_url->have_ssl ? HTTPS_DEFAULT_PORT : HTTP_DEFAULT_PORT));
-}
-
-static inline const char *
-http_client_request_https_name(const struct http_client_request *req)
-{
-	const struct http_url *host_url = req->host_url;
-
-	return (host_url->have_ssl ? host_url->host_name : NULL);
+	
+	memset(addr, 0, sizeof(*addr));
+	if (req->connect_direct) {
+		addr->type = HTTP_CLIENT_PEER_ADDR_RAW;
+		addr->port = (host_url->have_port ? host_url->port : HTTPS_DEFAULT_PORT);
+	} else if (host_url->have_ssl) {
+		addr->type = HTTP_CLIENT_PEER_ADDR_HTTPS;
+		addr->https_name = host_url->host_name;
+ 		addr->port = (host_url->have_port ? host_url->port : HTTPS_DEFAULT_PORT);
+	} else {
+		addr->type = HTTP_CLIENT_PEER_ADDR_HTTP;
+		addr->port = (host_url->have_port ? host_url->port : HTTP_DEFAULT_PORT);
+	}
 }
 
 static inline const char *
@@ -255,12 +274,17 @@
 
 void http_client_request_ref(struct http_client_request *req);
 void http_client_request_unref(struct http_client_request **_req);
+enum http_response_payload_type
+http_client_request_get_payload_type(struct http_client_request *req);
 int http_client_request_send(struct http_client_request *req,
 			    const char **error_r);
 int http_client_request_send_more(struct http_client_request *req,
 				  const char **error_r);
 bool http_client_request_callback(struct http_client_request *req,
 	struct http_response *response);
+void http_client_request_connect_callback(struct http_client_request *req,
+			     const struct http_client_tunnel *tunnel,
+			     struct http_response *response);
 void http_client_request_resubmit(struct http_client_request *req);
 void http_client_request_retry(struct http_client_request *req,
 	unsigned int status, const char *error);
@@ -288,6 +312,8 @@
 int http_client_connection_next_request(struct http_client_connection *conn);
 void http_client_connection_check_idle(struct http_client_connection *conn);
 void http_client_connection_switch_ioloop(struct http_client_connection *conn);
+void http_client_connection_start_tunnel(struct http_client_connection **_conn,
+	struct http_client_tunnel *tunnel);
 
 unsigned int http_client_peer_addr_hash
 	(const struct http_client_peer_addr *peer) ATTR_PURE;
@@ -325,12 +351,18 @@
 struct http_client_request *
 http_client_host_claim_request(struct http_client_host *host,
 	const struct http_client_peer_addr *addr, bool no_urgent);
+struct http_client_request *
+http_client_host_claim_connect_request(struct http_client_host *host,
+	const struct http_client_peer_addr *addr);
 void http_client_host_connection_success(struct http_client_host *host,
 	const struct http_client_peer_addr *addr);
 void http_client_host_connection_failure(struct http_client_host *host,
 	const struct http_client_peer_addr *addr, const char *reason);
 unsigned int http_client_host_requests_pending(struct http_client_host *host,
 	const struct http_client_peer_addr *addr, unsigned int *num_urgent_r);
+unsigned int
+http_client_host_connect_requests_pending(struct http_client_host *host,
+	const struct http_client_peer_addr *addr);
 void http_client_host_drop_request(struct http_client_host *host,
 	struct http_client_request *req);
 void http_client_host_switch_ioloop(struct http_client_host *host);
--- a/src/lib-http/http-client-request.c	Sat Oct 12 11:00:15 2013 +0300
+++ b/src/lib-http/http-client-request.c	Sat Oct 12 11:05:08 2013 +0300
@@ -84,7 +84,6 @@
 	req = http_client_request_new(client, method, callback, context);
 	req->origin_url.host_name = p_strdup(req->pool, host);
 	req->target = (target == NULL ? "/" : p_strdup(req->pool, target));
-	req->headers = str_new(default_pool, 256);
 	return req;
 }
 
@@ -99,7 +98,24 @@
 	req = http_client_request_new(client, method, callback, context);
 	http_url_copy_authority(req->pool, &req->origin_url, target_url);
 	req->target = p_strdup(req->pool, http_url_create_target(target_url));
-	req->headers = str_new(default_pool, 256);
+	return req;
+}
+
+#undef http_client_request_connect
+struct http_client_request *
+http_client_request_connect(struct http_client *client,
+		    const char *host, in_port_t port,
+		    http_client_request_callback_t *callback,
+				void *context)
+{
+	struct http_client_request *req;
+
+	req = http_client_request_new(client, "CONNECT", callback, context);
+	req->origin_url.host_name = p_strdup(req->pool, host);
+	req->origin_url.port = port;
+	req->origin_url.have_port = TRUE;
+	req->connect_tunnel = TRUE;
+	req->target = "";
 	return req;
 }
 
@@ -139,7 +155,8 @@
 		i_stream_unref(&req->payload_input);
 	if (req->payload_output != NULL)
 		o_stream_unref(&req->payload_output);
-	str_free(&req->headers);
+	if (req->headers != NULL)
+		str_free(&req->headers);
 	pool_unref(&req->pool);
 	*_req = NULL;
 }
@@ -241,17 +258,45 @@
 	return req->state;
 }
 
+enum http_response_payload_type
+http_client_request_get_payload_type(struct http_client_request *req)
+{
+	/* https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23
+	     Section 3.3:
+
+	   The presence of a message body in a response depends on both the
+	   request method to which it is responding and the response status code.
+	   Responses to the HEAD request method never include a message body
+	   because the associated response header fields, if present, indicate only
+	   what their values would have been if the request method had been GET
+	   2xx (Successful) responses to CONNECT switch to tunnel mode instead of
+	   having a message body (Section 4.3.6 of [Part2]).
+	 */
+	if (strcmp(req->method, "HEAD") == 0)
+		return HTTP_RESPONSE_PAYLOAD_TYPE_NOT_PRESENT;
+	if (strcmp(req->method, "CONNECT") == 0)
+		return HTTP_RESPONSE_PAYLOAD_TYPE_ONLY_UNSUCCESSFUL;
+	return HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED;
+}
+
 static void http_client_request_do_submit(struct http_client_request *req)
 {
 	struct http_client *client = req->client;
 	struct http_client_host *host;
 	const struct http_url *proxy_url = client->set.proxy_url;
-	const char *target;
+	const char *authority, *target;
 
 	i_assert(req->state == HTTP_REQUEST_STATE_NEW);
 
-	target = t_strconcat
-		(http_url_create_host(&req->origin_url), req->target, NULL);
+	authority = http_url_create_authority(&req->origin_url);
+	if (req->connect_tunnel) {
+		/* connect requests require authority form for request target */
+		target = authority;
+	} else {
+		/* absolute target url */
+		target = t_strconcat
+			(http_url_create_host(&req->origin_url), req->target, NULL);
+	}
 
 	/* determine what host to contact to submit this request */
 	if (proxy_url != NULL) {
@@ -265,16 +310,23 @@
 		req->date = ioloop_time;
 	
 	/* prepare value for Host header */
-	req->authority =
-		p_strdup(req->pool, http_url_create_authority(req->host_url));
+	req->authority = p_strdup(req->pool, authority);
 
 	/* debug label */
 	req->label = p_strdup_printf(req->pool, "[%s %s]", req->method, target);
 
-	/* request target needs to be made absolute url for proxy requests */
-	if (proxy_url != NULL)
+	/* update request target */
+	if (req->connect_tunnel || proxy_url != NULL)
 		req->target = p_strdup(req->pool, target);
 
+	if (proxy_url == NULL) {
+		/* if we don't have a proxy, CONNECT requests are handled by creating
+		   the requested connection directly */
+		req->connect_direct = req->connect_tunnel;
+		if (req->connect_direct)
+			req->urgent = TRUE;
+	}
+
 	host = http_client_host_get(req->client, req->host_url->host_name);
 	req->state = HTTP_REQUEST_STATE_QUEUED;
 
@@ -542,8 +594,13 @@
 	iov[0].iov_base = str_data(rtext);
 	iov[0].iov_len = str_len(rtext);	
 	/* explicit headers */
-	iov[1].iov_base = str_data(req->headers);
-	iov[1].iov_len = str_len(req->headers);
+	if (req->headers != NULL) {
+		iov[1].iov_base = str_data(req->headers);
+		iov[1].iov_len = str_len(req->headers);
+	} else {
+		iov[1].iov_base = "";
+		iov[1].iov_len = 0;
+	}
 	/* end of header */
 	iov[2].iov_base = "\r\n";
 	iov[2].iov_len = 2;
@@ -876,3 +933,11 @@
 	req->destroy_callback = callback;
 	req->destroy_context = context;
 }
+
+void http_client_request_start_tunnel(struct http_client_request *req,
+	struct http_client_tunnel *tunnel)
+{
+	i_assert(req->state == HTTP_REQUEST_STATE_GOT_RESPONSE);
+
+	http_client_connection_start_tunnel(&req->conn, tunnel);
+}
--- a/src/lib-http/http-client.h	Sat Oct 12 11:00:15 2013 +0300
+++ b/src/lib-http/http-client.h	Sat Oct 12 11:05:08 2013 +0300
@@ -89,6 +89,12 @@
 	bool debug;
 };
 
+struct http_client_tunnel {
+	int fd_in, fd_out;
+	struct istream *input;
+	struct ostream *output;
+};
+
 typedef void
 http_client_request_callback_t(const struct http_response *response,
 			       void *context);
@@ -96,6 +102,7 @@
 struct http_client *http_client_init(const struct http_client_settings *set);
 void http_client_deinit(struct http_client **_client);
 
+/* create new HTTP request */
 struct http_client_request *
 http_client_request(struct http_client *client,
 		    const char *method, const char *host, const char *target,
@@ -116,6 +123,22 @@
 			const struct http_response *response, typeof(context))), \
 		(http_client_request_callback_t *)callback, context)
 
+/* create new HTTP CONNECT request. If this HTTP is configured to use a proxy,
+   a CONNECT request will be submitted at that proxy, otherwise the connection
+   is created directly. Call http_client_request_start_tunnel() to
+   to take over the connection.
+ */
+struct http_client_request *
+http_client_request_connect(struct http_client *client,
+		    const char *host, in_port_t port,
+		    http_client_request_callback_t *callback,
+		    void *context);
+#define http_client_request_connect(client, host, port, callback, context) \
+	http_client_request_connect(client, host, port + \
+		CALLBACK_TYPECHECK(callback, void (*)( \
+			const struct http_response *response, typeof(context))), \
+		(http_client_request_callback_t *)callback, context)
+
 void http_client_request_set_port(struct http_client_request *req,
 	in_port_t port);
 void http_client_request_set_ssl(struct http_client_request *req,
@@ -134,6 +157,7 @@
 http_client_request_get_state(struct http_client_request *req);
 void http_client_request_submit(struct http_client_request *req);
 bool http_client_request_try_retry(struct http_client_request *req);
+
 void http_client_request_abort(struct http_client_request **req);
 
 /* Call the specified callback when HTTP request is destroyed. */
@@ -148,6 +172,9 @@
 	const unsigned char *data, size_t size);
 int http_client_request_finish_payload(struct http_client_request **req);
 
+void http_client_request_start_tunnel(struct http_client_request *req,
+	struct http_client_tunnel *tunnel);
+
 void http_client_switch_ioloop(struct http_client *client);
 
 /* blocks until all currently submitted requests are handled */
--- a/src/lib-http/http-response-parser.c	Sat Oct 12 11:00:15 2013 +0300
+++ b/src/lib-http/http-response-parser.c	Sat Oct 12 11:05:08 2013 +0300
@@ -233,8 +233,8 @@
 }
 
 int http_response_parse_next(struct http_response_parser *parser,
-			     bool no_payload, struct http_response *response,
-			     const char **error_r)
+			     enum http_response_payload_type payload_type,
+			     struct http_response *response, const char **error_r)
 {
 	int ret;
 
@@ -287,10 +287,12 @@
 	 */
 	if (parser->response_status / 100 == 1 || parser->response_status == 204
 		|| parser->response_status == 304) { // HEAD is handled in caller
-		no_payload = TRUE;
+		payload_type = HTTP_RESPONSE_PAYLOAD_TYPE_NOT_PRESENT;
 	}
 
-	if (!no_payload) {
+	if ((payload_type == HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED) ||
+		(payload_type == HTTP_RESPONSE_PAYLOAD_TYPE_ONLY_UNSUCCESSFUL &&
+			parser->response_status / 100 != 2)) {
 		/* [ message-body ] */
 		if (http_message_parse_body(&parser->parser, FALSE) < 0) {
 			*error_r = parser->parser.error;
--- a/src/lib-http/http-response-parser.h	Sat Oct 12 11:00:15 2013 +0300
+++ b/src/lib-http/http-response-parser.h	Sat Oct 12 11:05:08 2013 +0300
@@ -12,7 +12,7 @@
 void http_response_parser_deinit(struct http_response_parser **_parser);
 
 int http_response_parse_next(struct http_response_parser *parser,
-			     bool no_payload, struct http_response *response,
-			     const char **error_r);
+			     enum http_response_payload_type payload_type,
+			     struct http_response *response, const char **error_r);
 
 #endif
--- a/src/lib-http/http-response.h	Sat Oct 12 11:00:15 2013 +0300
+++ b/src/lib-http/http-response.h	Sat Oct 12 11:05:08 2013 +0300
@@ -7,6 +7,12 @@
 
 #define http_response_header http_header_field /* FIXME: remove in v2.3 */
 
+enum http_response_payload_type {
+	HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED,
+	HTTP_RESPONSE_PAYLOAD_TYPE_NOT_PRESENT,
+	HTTP_RESPONSE_PAYLOAD_TYPE_ONLY_UNSUCCESSFUL
+};
+
 struct http_response {
 	unsigned char version_major;
 	unsigned char version_minor;