]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: Various fixes
authorTimo Sirainen <tss@iki.fi>
Mon, 7 Jan 2013 10:24:29 +0000 (12:24 +0200)
committerTimo Sirainen <tss@iki.fi>
Mon, 7 Jan 2013 10:24:29 +0000 (12:24 +0200)
Patch by Stephan Bosch.

src/lib-http/http-client-connection.c
src/lib-http/http-client-private.h
src/lib-http/http-client-request.c
src/lib-http/http-transfer-chunked.c
src/lib-http/test-http-client.c

index 2d28fc2a5fc31f13cab648ff89ee0a291df58341..b19dd662dde71e51ff20de88c7079f9bc81b548a 100644 (file)
@@ -232,6 +232,7 @@ bool http_client_connection_next_request(struct http_client_connection *conn)
        conn->payload_continue = FALSE;
        if (conn->peer->no_payload_sync)
                req->payload_sync = FALSE;
+
        array_append(&conn->request_wait_list, &req, 1);
        http_client_request_ref(req);
 
@@ -254,7 +255,7 @@ bool http_client_connection_next_request(struct http_client_connection *conn)
           period before sending the payload body.
         */
        if (req->payload_sync) {
-               i_assert(req->input_size > 0);
+               i_assert(req->payload_size > 0);
                i_assert(conn->to_response == NULL);
                conn->to_response =     timeout_add(HTTP_CLIENT_CONTINUE_TIMEOUT_MSECS,
                        http_client_connection_continue_timeout, conn);
@@ -428,6 +429,10 @@ static void http_client_connection_input(struct connection *_conn)
                        return;
                }
 
+               /* Got some response; cancel response timeout */
+               if (conn->to_response != NULL)
+                       timeout_remove(&conn->to_response);
+
                /* Got some response; cancel response timeout */
                if (conn->to_response != NULL)
                        timeout_remove(&conn->to_response);
index 82f8134027b015a329c1e39e75f22fd35222ed19..ce7cf429c41e56eec6178a7dbec3ae40490da089 100644 (file)
@@ -56,8 +56,9 @@ struct http_client_request {
        struct http_client_connection *conn;
 
        string_t *headers;
-       struct istream *input;
-       uoff_t input_size, input_offset;
+       struct istream *payload_input;
+       uoff_t payload_size, payload_offset;
+       struct ostream *payload_output;
 
        unsigned int attempts;
        unsigned int redirects;
@@ -68,6 +69,7 @@ struct http_client_request {
        enum http_request_state state;
 
        unsigned int payload_sync:1;
+       unsigned int payload_chunked:1;
        unsigned int ssl:1;
        unsigned int urgent:1;
 };
index b08364ad6c1aabca47e73466ce768df2de148568..ddeb4e2693aed480ef64d6519dee1eedd5a09f37 100644 (file)
@@ -9,6 +9,7 @@
 #include "ostream.h"
 #include "http-url.h"
 #include "http-response-parser.h"
+#include "http-transfer.h"
 
 #include "http-client-private.h"
 
@@ -86,8 +87,10 @@ void http_client_request_unref(struct http_client_request **_req)
        http_client_request_debug(req, "Destroy (requests left=%d)",
                client->pending_requests);
 
-       if (req->input != NULL)
-               i_stream_unref(&req->input);
+       if (req->payload_input != NULL)
+               i_stream_unref(&req->payload_input);
+       if (req->payload_output != NULL)
+               o_stream_unref(&req->payload_output);
        str_free(&req->headers);
        pool_unref(&req->pool);
        *_req = NULL;
@@ -131,18 +134,19 @@ void http_client_request_set_payload(struct http_client_request *req,
                                     struct istream *input, bool sync)
 {
        i_assert(req->state == HTTP_REQUEST_STATE_NEW);
-       i_assert(req->input == NULL);
+       i_assert(req->payload_input == NULL);
 
        i_stream_ref(input);
-       req->input = input;
-       if (i_stream_get_size(input, TRUE, &req->input_size) <= 0)
-               i_unreached(); //FIXME  
-       req->input_offset = input->v_offset;
+       req->payload_input = input;
+       if (i_stream_get_size(input, TRUE, &req->payload_size) <= 0) {
+               req->payload_size = 0;
+               req->payload_chunked = TRUE;
+       }
+       req->payload_offset = input->v_offset;
 
        /* prepare request payload sync using 100 Continue response from server */
-       if (req->input_size > 0 && sync) {
+       if ((req->payload_chunked || req->payload_size > 0) && sync)
                req->payload_sync = TRUE;
-       }
 }
 
 void http_client_request_submit(struct http_client_request *req)
@@ -161,21 +165,24 @@ void http_client_request_submit(struct http_client_request *req)
 int http_client_request_send_more(struct http_client_request *req)
 {
        struct http_client_connection *conn = req->conn;
-       struct ostream *output = conn->conn.output;
+       struct ostream *output = req->payload_output;
        int ret = 0;
 
-       i_assert(req->input != NULL);
+       i_assert(req->payload_input != NULL);
 
        o_stream_set_max_buffer_size(output, 0);
-       if (o_stream_send_istream(output, req->input) < 0)
+       if (o_stream_send_istream(output, req->payload_input) < 0)
                ret = -1;
        o_stream_set_max_buffer_size(output, (size_t)-1);
 
-       if (!i_stream_have_bytes_left(req->input)) {
-               if (req->input->v_offset != req->input_size) {
+       if (!i_stream_have_bytes_left(req->payload_input)) {
+               if (!req->payload_chunked &&
+                       req->payload_input->v_offset - req->payload_offset != req->payload_size) {
                        i_error("stream input size changed"); //FIXME
                        return -1;
                }
+               o_stream_unref(&output);
+               req->payload_output = NULL;
                req->state = HTTP_REQUEST_STATE_WAITING;
                conn->output_locked = FALSE;
                http_client_request_debug(req, "Sent all payload");
@@ -212,9 +219,15 @@ int http_client_request_send(struct http_client_request *req)
        if (req->payload_sync) {
                str_append(rtext, "Expect: 100-continue\r\n");
        }
-       if (req->input_size != 0) {
+       if (req->payload_chunked) {
+               str_append(rtext, "Transfer-Encoding: chunked\r\n");
+               req->payload_output =
+                       http_transfer_chunked_ostream_create(output);
+       } else if (req->payload_size != 0) {
                str_printfa(rtext, "Content-Length: %"PRIuUOFF_T"\r\n",
-                           req->input_size);
+                           req->payload_size);
+               req->payload_output = output;
+               o_stream_ref(output);
        }
 
        iov[0].iov_base = str_data(rtext);
@@ -231,7 +244,7 @@ int http_client_request_send(struct http_client_request *req)
 
        http_client_request_debug(req, "Sent");
 
-       if (ret >= 0 && req->input_size != 0) {
+       if (ret >= 0 && req->payload_output != NULL) {
                if (!req->payload_sync) {
                        if (http_client_request_send_more(req) < 0)
                                ret = -1;
@@ -342,14 +355,28 @@ void http_client_request_redirect(struct http_client_request *req,
        }
 
        /* rewind payload stream */
-       if (req->input != NULL && req->input_size > 0 && status != 303) {
-               if (req->input->v_offset != req->input_offset && !req->input->seekable) {
+       if (req->payload_input != NULL && req->payload_size > 0 && status != 303) {
+               if (req->payload_input->v_offset != req->payload_offset &&
+                       !req->payload_input->seekable) {
                        http_client_request_error(req,
                                HTTP_CLIENT_REQUEST_ERROR_ABORTED,
                                "Redirect failed: Cannot resend payload; stream is not seekable");
                        return;
                } else {
-                       i_stream_seek(req->input, req->input_offset);
+                       i_stream_seek(req->payload_input, req->payload_offset);
+               }
+       }
+
+       /* rewind payload stream */
+       if (req->payload_input != NULL && req->payload_size > 0 && status != 303) {
+               if (req->payload_input->v_offset != req->payload_offset &&
+                       !req->payload_input->seekable) {
+                       http_client_request_error(req,
+                               HTTP_CLIENT_REQUEST_ERROR_ABORTED,
+                               "Redirect failed: Cannot resend payload; stream is not seekable");
+                       return;
+               } else {
+                       i_stream_seek(req->payload_input, req->payload_offset);
                }
        }
 
@@ -380,10 +407,30 @@ void http_client_request_redirect(struct http_client_request *req,
                req->method = p_strdup(req->pool, "GET");
 
                /* drop payload */
-               if (req->input != NULL)
-                       i_stream_unref(&req->input);
-               req->input_size = 0;
-               req->input_offset = 0;
+               if (req->payload_input != NULL)
+                       i_stream_unref(&req->payload_input);
+               req->payload_size = 0;
+               req->payload_offset = 0;
+       }
+
+       /* https://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-21
+             Section-7.4.4
+       
+          -> A 303 `See Other' redirect status response is handled a bit differently.
+          Basically, the response content is located elsewhere, but the original
+          (POST) request is handled already.
+        */
+       if (status == 303 && strcasecmp(req->method, "HEAD") != 0 &&
+               strcasecmp(req->method, "GET") != 0) {
+               // FIXME: should we provide the means to skip this step? The original
+               // request was already handled at this point.
+               req->method = p_strdup(req->pool, "GET");
+
+               /* drop payload */
+               if (req->payload_input != NULL)
+                       i_stream_unref(&req->payload_input);
+               req->payload_size = 0;
+               req->payload_offset = 0;
        }
 
        /* resubmit */
@@ -397,14 +444,28 @@ void http_client_request_resubmit(struct http_client_request *req)
        http_client_request_debug(req, "Resubmitting request");
 
        /* rewind payload stream */
-       if (req->input != NULL && req->input_size > 0) {
-               if (req->input->v_offset != req->input_offset && !req->input->seekable) {
+       if (req->payload_input != NULL && req->payload_size > 0) {
+               if (req->payload_input->v_offset != req->payload_offset &&
+                       !req->payload_input->seekable) {
+                       http_client_request_error(req,
+                               HTTP_CLIENT_REQUEST_ERROR_ABORTED,
+                               "Resubmission failed: Cannot resend payload; stream is not seekable");
+                       return;
+               } else {
+                       i_stream_seek(req->payload_input, req->payload_offset);
+               }
+       }
+
+       /* rewind payload stream */
+       if (req->payload_input != NULL && req->payload_size > 0) {
+               if (req->payload_input->v_offset != req->payload_offset &&
+                       !req->payload_input->seekable) {
                        http_client_request_error(req,
                                HTTP_CLIENT_REQUEST_ERROR_ABORTED,
                                "Resubmission failed: Cannot resend payload; stream is not seekable");
                        return;
                } else {
-                       i_stream_seek(req->input, req->input_offset);
+                       i_stream_seek(req->payload_input, req->payload_offset);
                }
        }
 
index 450dffb68cf52d09c200042c7a46e631eebc131e..757a79ba6996d93b21aa4dee20c5d7f59e5a3a43 100644 (file)
@@ -475,6 +475,21 @@ http_transfer_chunked_istream_read(struct istream_private *stream)
        return -1;
 }
 
+static void
+http_transfer_chunked_istream_destroy(struct iostream_private *stream)
+{
+       struct http_transfer_chunked_istream *tcstream =
+               (struct http_transfer_chunked_istream *)stream;
+
+       if (tcstream->header_parser != NULL)
+               http_header_parser_deinit(&tcstream->header_parser);
+
+       // FIXME: copied from istream.c; there's got to be a better way.
+       i_free(tcstream->istream.w_buffer);
+       if (tcstream->istream.parent != NULL)
+               i_stream_unref(&tcstream->istream.parent);
+}
+
 struct istream *
 http_transfer_chunked_istream_create(struct istream *input)
 {
@@ -485,6 +500,7 @@ http_transfer_chunked_istream_create(struct istream *input)
        tcstream->istream.max_buffer_size =
                input->real_stream->max_buffer_size;
 
+       tcstream->istream.iostream.destroy = http_transfer_chunked_istream_destroy;
        tcstream->istream.read = http_transfer_chunked_istream_read;
 
        tcstream->istream.istream.readable_fd = FALSE;
index 94b76bde6aa94e7657d27f83eea19c28b5e39555..b4f2b0dd219559d4ce87ff7302570cdc37cdaed7 100644 (file)
@@ -206,6 +206,17 @@ int main(void)
        http_client_request_abort(&http_req);
        i_free(test_req);
 
+       test_req = i_new(struct http_test_request, 1);
+       http_req = http_client_request(http_client,
+               "POST", "posttestserver.com", "/post.php",
+               got_request_response, test_req);
+       post_payload = i_stream_create_from_data
+               ((unsigned char *)test_query1, strlen(test_query1));
+       http_client_request_set_payload(http_req, post_payload, TRUE);
+       i_stream_unref(&post_payload);
+       http_client_request_set_ssl(http_req, TRUE);
+       http_client_request_submit(http_req);
+
        test_req = i_new(struct http_test_request, 1);
        http_req = http_client_request(http_client,
                "GET", "wiki2.dovecot.org", "/Pigeonhole",
@@ -228,6 +239,16 @@ int main(void)
        i_stream_unref(&post_payload);
        http_client_request_submit(http_req);
 
+       test_req = i_new(struct http_test_request, 1);
+       http_req = http_client_request(http_client,
+               "POST", "jigsaw.w3.org", "/HTTP/300/Go_307",
+               got_request_response, test_req);
+       post_payload = i_stream_create_from_data
+               ((unsigned char *)test_query3, strlen(test_query3));
+       http_client_request_set_payload(http_req, post_payload, FALSE);
+       i_stream_unref(&post_payload);
+       http_client_request_submit(http_req);
+
        http_client_wait(http_client);
 
        http_client_deinit(&http_client);