]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: Added support for creating CONNECT tunnels through HTTP.
authorStephan Bosch <stephan@rename-it.nl>
Sat, 12 Oct 2013 08:05:08 +0000 (11:05 +0300)
committerStephan Bosch <stephan@rename-it.nl>
Sat, 12 Oct 2013 08:05:08 +0000 (11:05 +0300)
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

index fa0b782dd700fa5758f02d42937a9bceb98cb6e4..eff8e341f91727018937552053451cdf055eba09 100644 (file)
@@ -57,7 +57,7 @@ http_client_connection_count_pending(struct http_client_connection *conn)
 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 @@ void http_client_connection_check_idle(struct http_client_connection *conn)
 {
        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 @@ int http_client_connection_next_request(struct http_client_connection *conn)
                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 @@ static void http_client_payload_destroyed(struct http_client_request *req)
 {
        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 @@ http_client_connection_return_response(struct http_client_connection *conn,
        }
 
        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 @@ static void http_client_connection_input(struct connection *_conn)
        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 @@ static void http_client_connection_input(struct connection *_conn)
                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 @@ static void http_client_connection_input(struct connection *_conn)
                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 @@ int http_client_connection_output(struct http_client_connection *conn)
        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 @@ http_client_connection_ready(struct http_client_connection *conn)
                                       &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 @@ http_client_connection_create(struct http_client_peer *peer)
        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 @@ http_client_connection_create(struct http_client_peer *peer)
        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 @@ void http_client_connection_unref(struct http_client_connection **_conn)
 {
        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 @@ void http_client_connection_unref(struct http_client_connection **_conn)
 
        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 @@ void http_client_connection_unref(struct http_client_connection **_conn)
                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;
                }
        }
index 7716bb904a8417c97560405f0f58422a19aa81ec..57c37d676dc10d110f532dd157692245592fa55e 100644 (file)
@@ -46,13 +46,13 @@ http_client_host_port_connection_setup(struct http_client_host_port *hport);
 
 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 @@ http_client_host_port_find(struct http_client_host *host,
 
 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 @@ static void http_client_host_port_deinit(struct http_client_host_port *hport)
 {
        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 @@ static void
 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 @@ void http_client_host_connection_success(struct http_client_host *host,
        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 @@ void http_client_host_connection_failure(struct http_client_host *host,
        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 @@ void http_client_host_submit_request(struct http_client_host *host,
 {
        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 @@ void http_client_host_submit_request(struct http_client_host *host,
                }
        }
 
+       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 @@ http_client_host_claim_request(struct http_client_host *host,
        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 @@ unsigned int http_client_host_requests_pending(struct http_client_host *host,
 
        *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 @@ void http_client_host_drop_request(struct http_client_host *host,
        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;
+
+       http_client_request_get_peer_addr(req, &addr);
 
-       hport = http_client_host_port_find(host, port, https_name);
+       hport = http_client_host_port_find(host, &addr);
        if (hport == NULL)
                return;
 
index e4791122b60bad7b3118f46aad9dfb89cf5e23dc..1c35aba301ed8885c4fa3b44c007d5e6d1dea62d 100644 (file)
@@ -42,8 +42,17 @@ http_client_peer_debug(struct http_client_peer *peer,
 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 http_client_peer_addr_cmp
 {
        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 @@ http_client_peer_connect(struct http_client_peer *peer, unsigned int count)
        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 @@ http_client_peer_create(struct http_client *client,
        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 @@ http_client_peer_create(struct http_client *client,
        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 @@ void http_client_peer_free(struct http_client_peer **_peer)
        /* 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 @@ void http_client_peer_free(struct http_client_peer **_peer)
                (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;
 }
index 4cd161addc863a1cb03ea911ca40f0a819b9d130..023f1b1fcf9e073d7bb19270d92c043def038fee 100644 (file)
@@ -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_host, const char *,
 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 @@ struct http_client_request {
        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_host {
 
 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 @@ struct http_client_connection {
        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 @@ http_client_request_label(struct http_client_request *req)
        return req->label;
 }
 
-static inline in_port_t
-http_client_request_port(const struct http_client_request *req)
-{
-       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)
+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_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 @@ int http_client_init_ssl_ctx(struct http_client *client, const char **error_r);
 
 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 @@ bool http_client_connection_is_idle(struct http_client_connection *conn);
 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 @@ void http_client_host_submit_request(struct http_client_host *host,
 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);
index 55a98ed1071c29167132a53041c5bb6b3c4de692..2fe6964f3d70b3269fead68e3e1b41a537535d4f 100644 (file)
@@ -84,7 +84,6 @@ http_client_request(struct http_client *client,
        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 @@ http_client_request_url(struct http_client *client,
        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 @@ void http_client_request_unref(struct http_client_request **_req)
                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 @@ http_client_request_get_state(struct http_client_request *req)
        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 @@ static void http_client_request_do_submit(struct http_client_request *req)
                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 @@ static int http_client_request_send_real(struct http_client_request *req,
        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 @@ void http_client_request_set_destroy_callback(struct http_client_request *req,
        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);
+}
index ce72c05d880ada4b57d11d537df83c8bd67cbe71..0fe7959336764e2b9b1cda342596706f1f6ce651 100644 (file)
@@ -89,6 +89,12 @@ struct http_client_settings {
        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 @@ http_client_request_callback_t(const struct http_response *response,
 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 @@ http_client_request_url(struct http_client *client,
                        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 @@ enum http_request_state
 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 @@ int http_client_request_send_payload(struct http_client_request **req,
        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 */
index ea4fa5e1cb8e1d93d962d0ec6a6b06cd25f0e85b..7e3632bc87209d10f6657eba859fa5e1bb206512 100644 (file)
@@ -233,8 +233,8 @@ http_response_parse_status_line(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)
 {
        int ret;
 
@@ -287,10 +287,12 @@ int http_response_parse_next(struct http_response_parser *parser,
         */
        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;
index dea41f31e300526bfcb10f6e5573c2e46f609343..f98d4e6f2c3700c23395866dc0e6744d929e39f1 100644 (file)
@@ -12,7 +12,7 @@ http_response_parser_init(struct istream *input,
 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
index 061ed2e1031aa2be0f65a0ecfea6660fc097c50e..1fe5a02597a66a644fc64ba329acae15e7825a44 100644 (file)
@@ -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;