dovecot-2.2: lib-http: Added support for chunked input/output st...

dovecot at dovecot.org dovecot at dovecot.org
Wed Dec 12 11:34:58 EET 2012


details:   http://hg.dovecot.org/dovecot-2.2/rev/7a4d8cd0e079
changeset: 15460:7a4d8cd0e079
user:      Stephan Bosch <stephan at rename-it.nl>
date:      Wed Dec 12 11:34:09 2012 +0200
description:
lib-http: Added support for chunked input/output streams and some bugfixes.

diffstat:

 src/lib-http/http-client-connection.c |   10 +-
 src/lib-http/http-client-private.h    |    4 +-
 src/lib-http/http-client-request.c    |   48 ++++++-
 src/lib-http/http-response-parser.c   |    1 -
 src/lib-http/http-transfer-chunked.c  |  202 ++++++++++++++++++++++++---
 src/lib-http/http-transfer.h          |    4 +
 src/lib-http/test-http-client.c       |   13 +-
 src/lib-http/test-http-transfer.c     |  243 ++++++++++++++++++++++++++++++++-
 8 files changed, 478 insertions(+), 47 deletions(-)

diffs (truncated from 816 to 300 lines):

diff -r 4a110cf281aa -r 7a4d8cd0e079 src/lib-http/http-client-connection.c
--- a/src/lib-http/http-client-connection.c	Tue Dec 11 08:08:12 2012 +0200
+++ b/src/lib-http/http-client-connection.c	Wed Dec 12 11:34:09 2012 +0200
@@ -62,7 +62,7 @@
 bool http_client_connection_is_ready(struct http_client_connection *conn)
 {
 	return (conn->connected && !conn->output_locked &&
-		array_count(&conn->request_wait_list) <
+		!conn->close_indicated &&	array_count(&conn->request_wait_list) <
 			conn->client->set.max_pipelined_requests);
 }
 
@@ -428,6 +428,10 @@
 			return;
 		}
 
+		/* 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:
 
@@ -438,8 +442,6 @@
 		 */
 		if (req->payload_sync && response->status == 100) {
 			conn->payload_continue = TRUE;
-			if (conn->to_response != NULL)
-				timeout_remove(&conn->to_response);
 			http_client_connection_debug(conn,
 				"Got expected 100-continue response");
 			if (http_client_request_send_more(req) < 0) {
@@ -469,7 +471,7 @@
 		if (!aborted) {
 			if (response->status / 100 == 3) {
 				/* redirect */
-				http_client_request_redirect(req, response->location);
+				http_client_request_redirect(req, response->status, response->location);
 			} else {
 				/* response for application */
 				if (!http_client_connection_return_response(conn, req, response))
diff -r 4a110cf281aa -r 7a4d8cd0e079 src/lib-http/http-client-private.h
--- a/src/lib-http/http-client-private.h	Tue Dec 11 08:08:12 2012 +0200
+++ b/src/lib-http/http-client-private.h	Wed Dec 12 11:34:09 2012 +0200
@@ -57,7 +57,7 @@
 
 	string_t *headers;
 	struct istream *input;
-	uoff_t input_size;
+	uoff_t input_size, input_offset;
 
 	unsigned int attempts;
 	unsigned int redirects;
@@ -198,7 +198,7 @@
 void http_client_request_error(struct http_client_request *req,
 	unsigned int status, const char *error);
 void http_client_request_redirect(struct http_client_request *req,
-	const char *location);
+	unsigned int status, const char *location);
 void http_client_request_finish(struct http_client_request **_req);
 
 struct connection_list *http_client_connection_list_init(void);
diff -r 4a110cf281aa -r 7a4d8cd0e079 src/lib-http/http-client-request.c
--- a/src/lib-http/http-client-request.c	Tue Dec 11 08:08:12 2012 +0200
+++ b/src/lib-http/http-client-request.c	Wed Dec 12 11:34:09 2012 +0200
@@ -137,6 +137,7 @@
 	req->input = input;
 	if (i_stream_get_size(input, TRUE, &req->input_size) <= 0)
 		i_unreached(); //FIXME	
+	req->input_offset = input->v_offset;
 
 	/* prepare request payload sync using 100 Continue response from server */
 	if (req->input_size > 0 && sync) {
@@ -175,7 +176,6 @@
 			i_error("stream input size changed"); //FIXME
 			return -1;
 		}
-		i_stream_unref(&req->input);
 		req->state = HTTP_REQUEST_STATE_WAITING;
 		conn->output_locked = FALSE;
 		http_client_request_debug(req, "Sent all payload");
@@ -313,7 +313,7 @@
 }
 
 void http_client_request_redirect(struct http_client_request *req,
-	const char *location)
+	unsigned int status, const char *location)
 {
 	struct http_url *url;
 	const char *error;
@@ -341,6 +341,18 @@
 		return;
 	}
 
+	/* rewind payload stream */
+	if (req->input != NULL && req->input_size > 0 && status != 303) {
+		if (req->input->v_offset != req->input_offset && !req->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);
+		}
+	}
+
 	newport = (url->have_port ? url->port : (url->have_ssl ? 443 : 80));
 
 	http_client_request_debug(req, "Redirecting to http://%s:%u%s",
@@ -354,6 +366,26 @@
 	req->target = p_strdup(req->pool, url->path);
 	req->ssl = url->have_ssl;
 
+	/* 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->input != NULL)
+			i_stream_unref(&req->input);
+		req->input_size = 0;
+		req->input_offset = 0;
+	}
+
 	/* resubmit */
 	req->client->pending_requests--;
 	req->state = HTTP_REQUEST_STATE_NEW;
@@ -364,6 +396,18 @@
 {
 	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) {
+			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);
+		}
+	}
+
 	req->conn = NULL;
 	req->peer = NULL;
 	req->state = HTTP_REQUEST_STATE_QUEUED;
diff -r 4a110cf281aa -r 7a4d8cd0e079 src/lib-http/http-response-parser.c
--- a/src/lib-http/http-response-parser.c	Tue Dec 11 08:08:12 2012 +0200
+++ b/src/lib-http/http-response-parser.c	Wed Dec 12 11:34:09 2012 +0200
@@ -476,7 +476,6 @@
 			                      / transfer-extension       ;  [FIXME]
 			   transfer-extension = token *( OWS ";" OWS transfer-parameter )
 			*/
-			// FIXME: parse this directly when the header is parsed
 			http_parser_init(&hparser,
 				(const unsigned char *)parser->transfer_encoding,
 				strlen(parser->transfer_encoding));
diff -r 4a110cf281aa -r 7a4d8cd0e079 src/lib-http/http-transfer-chunked.c
--- a/src/lib-http/http-transfer-chunked.c	Tue Dec 11 08:08:12 2012 +0200
+++ b/src/lib-http/http-transfer-chunked.c	Wed Dec 12 11:34:09 2012 +0200
@@ -132,25 +132,25 @@
 	/* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21;
 		   Section 4.1:
 
-		 chunked-body   = *chunk
-		                  last-chunk
-		                  trailer-part
-		                  CRLF
+	   chunked-body   = *chunk
+	                    last-chunk
+	                    trailer-part
+	                    CRLF
 
-		 chunk          = chunk-size [ chunk-ext ] CRLF
-		                  chunk-data CRLF
-		 chunk-size     = 1*HEXDIG
-		 last-chunk     = 1*("0") [ chunk-ext ] CRLF
+	   chunk          = chunk-size [ chunk-ext ] CRLF
+	                    chunk-data CRLF
+	   chunk-size     = 1*HEXDIG
+	   last-chunk     = 1*("0") [ chunk-ext ] CRLF
 
-		 chunk-ext      = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
-		 chunk-ext-name = token
-		 chunk-ext-val  = token / quoted-str-nf
-		 chunk-data     = 1*OCTET ; a sequence of chunk-size octets
-		 trailer-part   = *( header-field CRLF )
+	   chunk-ext      = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
+	   chunk-ext-name = token
+	   chunk-ext-val  = token / quoted-str-nf
+	   chunk-data     = 1*OCTET ; a sequence of chunk-size octets
+	   trailer-part   = *( header-field CRLF )
 
-		 quoted-str-nf  = DQUOTE *( qdtext-nf / quoted-pair ) DQUOTE
-		                ; like quoted-string, but disallowing line folding
-		 qdtext-nf      = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
+	   quoted-str-nf  = DQUOTE *( qdtext-nf / quoted-pair ) DQUOTE
+	                  ; like quoted-string, but disallowing line folding
+	   qdtext-nf      = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
 	   quoted-pair    = "\" ( HTAB / SP / VCHAR / obs-text )
 	*/
 
@@ -202,7 +202,7 @@
 			if (*tcstream->cur != '"') {
 				tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_TOKEN;
 				break;
-			} 
+			}
 			tcstream->cur++;
 			tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_STRING;
 			if (tcstream->cur >= tcstream->end)
@@ -219,11 +219,11 @@
 				} else if ((ret=http_transfer_chunked_skip_qdtext(tcstream)) <= 0) {
 					if (ret < 0)
 						tcstream->error = "Invalid chunked extension value";
-					return ret;	
+					return ret;
 				} else if (*tcstream->cur == '\\') {
 					tcstream->cur++;
 					tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_ESCAPE;
-					if (tcstream->cur >= tcstream->end)						
+					if (tcstream->cur >= tcstream->end)
 						return 0;
 					break;
 				} else {
@@ -243,7 +243,7 @@
 				return -1;
 			}
 			tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_STRING;
-			if (tcstream->cur >= tcstream->end)						
+			if (tcstream->cur >= tcstream->end)
 				return 0;
 			break;
 		case HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_TOKEN:
@@ -258,7 +258,7 @@
 			tcstream->state = HTTP_CHUNKED_PARSE_STATE_LF;
 			if (*tcstream->cur == '\r') {
 				tcstream->cur++;
-				if (tcstream->cur >= tcstream->end)						
+				if (tcstream->cur >= tcstream->end)
 					return 0;
 			}
 			/* fall through */
@@ -281,7 +281,7 @@
 			tcstream->state = HTTP_CHUNKED_PARSE_STATE_DATA_LF;
 			if (*tcstream->cur == '\r') {
 				tcstream->cur++;
-				if (tcstream->cur >= tcstream->end)						
+				if (tcstream->cur >= tcstream->end)
 					return 0;
 			}
 			/* fall through */
@@ -307,6 +307,7 @@
 static int http_transfer_chunked_parse_next(
 	struct http_transfer_chunked_istream *tcstream)
 {
+	struct istream_private *stream = &tcstream->istream;
 	struct istream *input = tcstream->istream.parent;
 	size_t size;
 	int ret;
@@ -316,8 +317,10 @@
 		tcstream->cur = tcstream->begin;
 		tcstream->end = tcstream->cur + size;
 
-		if ((ret=http_transfer_chunked_parse(tcstream)) < 0)
+		if ((ret=http_transfer_chunked_parse(tcstream)) < 0) {
+			stream->istream.stream_errno = EIO;
 			return -1;
+		}
 
 		i_stream_skip(input, tcstream->cur - tcstream->begin);
 
@@ -329,9 +332,23 @@
 	}
 
 	i_assert(ret != -2);
+
+	if (ret < 0) {
+		if ( stream->parent->eof && stream->parent->stream_errno == 0 ) {
+			/* unexpected EOF */
+			tcstream->error = "Unexpected end of payload";
+			stream->istream.stream_errno = EIO;
+		} else {
+			/* parent stream error */
+			tcstream->error = "Stream error";
+			stream->istream.stream_errno = stream->parent->stream_errno;
+		}
+	}
 	return ret;
 }
 


More information about the dovecot-cvs mailing list