Mercurial > dovecot > core-2.2
changeset 15579:d1eaa348482f
lib-http: Various fixes
Patch by Stephan Bosch.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 07 Jan 2013 12:24:29 +0200 |
parents | 13e74bd5ac8c |
children | 86bccdf46d17 |
files | 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 |
diffstat | 5 files changed, 135 insertions(+), 30 deletions(-) [+] |
line wrap: on
line diff
--- a/src/lib-http/http-client-connection.c Sat Jan 05 01:14:11 2013 +0200 +++ b/src/lib-http/http-client-connection.c Mon Jan 07 12:24:29 2013 +0200 @@ -232,6 +232,7 @@ 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 @@ 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); @@ -432,6 +433,10 @@ 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); + /* https://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-21 Section 7.2:
--- a/src/lib-http/http-client-private.h Sat Jan 05 01:14:11 2013 +0200 +++ b/src/lib-http/http-client-private.h Mon Jan 07 12:24:29 2013 +0200 @@ -56,8 +56,9 @@ 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 @@ enum http_request_state state; unsigned int payload_sync:1; + unsigned int payload_chunked:1; unsigned int ssl:1; unsigned int urgent:1; };
--- a/src/lib-http/http-client-request.c Sat Jan 05 01:14:11 2013 +0200 +++ b/src/lib-http/http-client-request.c Mon Jan 07 12:24:29 2013 +0200 @@ -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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } /* 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 @@ 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 @@ 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->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) { + 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); } }
--- a/src/lib-http/http-transfer-chunked.c Sat Jan 05 01:14:11 2013 +0200 +++ b/src/lib-http/http-transfer-chunked.c Mon Jan 07 12:24:29 2013 +0200 @@ -475,6 +475,21 @@ 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 @@ 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;
--- a/src/lib-http/test-http-client.c Sat Jan 05 01:14:11 2013 +0200 +++ b/src/lib-http/test-http-client.c Mon Jan 07 12:24:29 2013 +0200 @@ -208,6 +208,17 @@ 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", got_request_response, test_req); http_client_request_submit(http_req); @@ -228,6 +239,16 @@ 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);