changeset 16604:c55bd41133a2

lib-http: Implemented http-request-parser for HTTP servers. Moved code common with it and http-response-parser to http-message-parser.
author Timo Sirainen <tss@iki.fi>
date Thu, 11 Jul 2013 14:18:42 +0300
parents c5952dd642c6
children 6d986d132fb8
files src/lib-http/Makefile.am src/lib-http/http-message-parser.c src/lib-http/http-message-parser.h src/lib-http/http-request-parser.c src/lib-http/http-request-parser.h src/lib-http/http-response-parser.c
diffstat 6 files changed, 650 insertions(+), 253 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-http/Makefile.am	Thu Jul 11 12:54:02 2013 +0300
+++ b/src/lib-http/Makefile.am	Thu Jul 11 14:18:42 2013 +0300
@@ -12,6 +12,8 @@
 	http-parser.c \
 	http-header-parser.c \
 	http-transfer-chunked.c \
+	http-message-parser.c \
+	http-request-parser.c \
 	http-response-parser.c \
 	http-client-request.c \
 	http-client-connection.c \
@@ -25,6 +27,8 @@
 	http-parser.h \
 	http-header-parser.h \
 	http-transfer.h \
+	http-message-parser.h \
+	http-request-parser.h \
 	http-response.h \
 	http-response-parser.h \
 	http-client-private.h \
@@ -81,6 +85,7 @@
 	http-parser.lo \
 	http-header-parser.lo \
 	http-transfer-chunked.lo \
+	http-message-parser.lo \
 	http-response-parser.lo \
 	$(test_libs)
 test_http_response_parser_DEPENDENCIES = $(test_deps)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-http/http-message-parser.c	Thu Jul 11 14:18:42 2013 +0300
@@ -0,0 +1,221 @@
+/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "http-parser.h"
+#include "http-header-parser.h"
+#include "http-date.h"
+#include "http-transfer.h"
+#include "http-message-parser.h"
+
+#include <ctype.h>
+
+void http_message_parser_init(struct http_message_parser *parser,
+			      struct istream *input)
+{
+	memset(parser, 0, sizeof(*parser));
+	parser->input = input;
+}
+
+void http_message_parser_deinit(struct http_message_parser *parser)
+{
+	if (parser->header_parser != NULL)
+		http_header_parser_deinit(&parser->header_parser);
+	if (parser->msg_pool != NULL)
+		pool_unref(&parser->msg_pool);
+	if (parser->payload != NULL)
+		i_stream_unref(&parser->payload);
+}
+
+void http_message_parser_restart(struct http_message_parser *parser)
+{
+	i_assert(parser->payload == NULL);
+
+	if (parser->header_parser == NULL)
+		parser->header_parser = http_header_parser_init(parser->input);
+	else
+		http_header_parser_reset(parser->header_parser);
+
+	if (parser->msg_pool != NULL)
+		pool_unref(&parser->msg_pool);
+	parser->msg_pool = pool_alloconly_create("http_message", 4096);
+	memset(&parser->msg, 0, sizeof(parser->msg));
+	parser->msg.date = (time_t)-1;
+	p_array_init(&parser->msg.headers, parser->msg_pool, 32);
+}
+
+int http_message_parse_version(struct http_message_parser *parser)
+{
+	const unsigned char *p = parser->cur;
+	const size_t size = parser->end - parser->cur;
+
+	/* HTTP-version  = HTTP-name "/" DIGIT "." DIGIT
+	   HTTP-name     = %x48.54.54.50 ; "HTTP", case-sensitive
+	 */
+	if (size < 8)
+		return 0;
+	if (memcmp(p, "HTTP/", 5) != 0 ||
+	    !i_isdigit(p[5]) || p[6] != '.' || !i_isdigit(p[7]))
+		return -1;
+	parser->msg.version_major = p[5] - '0';
+	parser->msg.version_minor = p[7] - '0';
+	parser->cur += 8;
+	return 1;
+}
+
+int http_message_parse_finish_payload(struct http_message_parser *parser,
+				      const char **error_r)
+{
+	const unsigned char *data;
+	size_t size;
+	int ret;
+
+	if (parser->payload == NULL)
+		return 1;
+
+	while ((ret = i_stream_read_data(parser->payload, &data, &size, 0)) > 0)
+		i_stream_skip(parser->payload, size);
+	if (ret == 0 || parser->payload->stream_errno != 0) {
+		if (ret < 0)
+			*error_r = "Stream error while skipping payload";
+		return ret;
+	}
+	i_stream_unref(&parser->payload);
+	return 1;
+}
+
+static int
+http_message_parse_header(struct http_message_parser *parser, const char *name,
+			  const unsigned char *data, size_t size,
+			  const char **error_r)
+{
+	struct http_response_header *hdr;
+	struct http_parser hparser;
+	void *value;
+
+	hdr = array_append_space(&parser->msg.headers);
+	hdr->key = p_strdup(parser->msg_pool, name);
+	hdr->value = value = p_malloc(parser->msg_pool, size+1);
+	memcpy(value, data, size);
+	hdr->size = size;
+
+	switch (name[0]) {
+	case 'C': case 'c':
+		if (strcasecmp(name, "Connection") == 0) {
+			const char *option;
+
+			/* Connection        = 1#connection-option
+				 connection-option = token
+			*/
+			http_parser_init(&hparser, data, size);
+			for (;;) {
+				if (http_parse_token_list_next(&hparser, &option) <= 0)
+					break;
+				if (strcasecmp(option, "close") == 0) {
+					parser->msg.connection_close = TRUE;
+					break; // not interested in any other options
+				}
+			}
+			return 0;
+		}
+		if (strcasecmp(name, "Content-Length") == 0) {
+			/* Content-Length = 1*DIGIT */
+			if (str_to_uoff(hdr->value, &parser->msg.content_length) < 0) {
+				*error_r = "Invalid Content-Length header";
+				return -1;
+			}
+			return 0;
+		}
+		break;
+	case 'D': case 'd':
+		if (strcasecmp(name, "Date") == 0) {
+			/* Date = HTTP-date */
+			(void)http_date_parse(data, size, &parser->msg.date);
+			return 0;
+		}
+		break;
+	case 'L': case 'l':
+		if (strcasecmp(name, "Location") == 0) {
+			/* Location = URI-reference (not parsed here) */
+			parser->msg.location = hdr->value;
+			return 0;
+		}
+		break;
+	case 'T': case 't':
+		if (strcasecmp(name, "Transfer-Encoding") == 0) {
+			/* Transfer-Encoding = 1#transfer-coding */
+			parser->msg.transfer_encoding = hdr->value;
+			return 0;
+		}
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+int http_message_parse_headers(struct http_message_parser *parser,
+			       const char **error_r)
+{
+	const char *field_name, *error;
+	const unsigned char *field_data;
+	size_t field_size;
+	int ret;
+
+	/* *( header-field CRLF ) CRLF */
+	while ((ret=http_header_parse_next_field
+		(parser->header_parser, &field_name, &field_data, &field_size, &error)) > 0) {
+		if (field_name == NULL) {
+			/* EOH */
+			return 1;
+		}
+		if (http_message_parse_header(parser, field_name, field_data,
+					      field_size, error_r) < 0)
+			return -1;
+	}
+
+	if (ret < 0) {
+		*error_r = t_strdup_printf(
+			"Failed to parse response header: %s", error);
+	}
+	return ret;
+}
+
+int http_message_parse_body(struct http_message_parser *parser,
+			    const char **error_r)
+{
+	struct http_parser hparser;
+
+	if (parser->msg.content_length > 0) {
+		/* Got explicit message size from Content-Length: header */
+		parser->payload =
+			i_stream_create_limit(parser->input,
+					      parser->msg.content_length);
+	} else if (parser->msg.transfer_encoding != NULL) {
+		const char *tenc;
+
+		/* Transfer-Encoding = 1#transfer-coding
+		   transfer-coding    = "chunked" / "compress" / "deflate" / "gzip"
+				      / transfer-extension       ;  [FIXME]
+		   transfer-extension = token *( OWS ";" OWS transfer-parameter )
+		*/
+		http_parser_init(&hparser,
+				 (const unsigned char *)parser->msg.transfer_encoding,
+				 strlen(parser->msg.transfer_encoding));
+		for (;;) {
+			if (http_parse_token_list_next(&hparser, &tenc) <= 0)
+				break;
+			if (strcasecmp(tenc, "chunked") == 0) {
+				parser->payload =
+					http_transfer_chunked_istream_create(parser->input);
+				break; // FIXME
+			} else {
+				*error_r = t_strdup_printf(
+					"Unknown Transfer-Encoding `%s'", tenc);
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-http/http-message-parser.h	Thu Jul 11 14:18:42 2013 +0300
@@ -0,0 +1,45 @@
+#ifndef HTTP_MESSAGE_PARSER_H
+#define HTTP_MESSAGE_PARSER_H
+
+#include "http-response.h"
+
+struct http_message {
+	unsigned int version_major;
+	unsigned int version_minor;
+
+	ARRAY_TYPE(http_response_header) headers;
+	time_t date;
+
+	uoff_t content_length;
+	const char *location;
+	const char *transfer_encoding;
+
+	unsigned int connection_close:1;
+};
+
+struct http_message_parser {
+	struct istream *input;
+
+	const unsigned char *cur, *end;
+
+	struct http_header_parser *header_parser;
+	struct istream *payload;
+
+	pool_t msg_pool;
+	struct http_message msg;
+};
+
+void http_message_parser_init(struct http_message_parser *parser,
+			      struct istream *input);
+void http_message_parser_deinit(struct http_message_parser *parser);
+void http_message_parser_restart(struct http_message_parser *parser);
+
+int http_message_parse_finish_payload(struct http_message_parser *parser,
+				      const char **error_r);
+int http_message_parse_version(struct http_message_parser *parser);
+int http_message_parse_headers(struct http_message_parser *parser,
+			       const char **error_r);
+int http_message_parse_body(struct http_message_parser *parser,
+			    const char **error_r);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-http/http-request-parser.c	Thu Jul 11 14:18:42 2013 +0300
@@ -0,0 +1,267 @@
+/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "http-parser.h"
+#include "http-message-parser.h"
+#include "http-request-parser.h"
+
+enum http_request_parser_state {
+	HTTP_REQUEST_PARSE_STATE_INIT = 0,
+	HTTP_REQUEST_PARSE_STATE_METHOD,
+	HTTP_REQUEST_PARSE_STATE_SP1,
+	HTTP_REQUEST_PARSE_STATE_TARGET,
+	HTTP_REQUEST_PARSE_STATE_SP2,
+	HTTP_REQUEST_PARSE_STATE_VERSION,
+	HTTP_REQUEST_PARSE_STATE_CR,
+	HTTP_REQUEST_PARSE_STATE_LF,
+	HTTP_REQUEST_PARSE_STATE_HEADER
+};
+
+struct http_request_parser {
+	struct http_message_parser parser;
+	enum http_request_parser_state state;
+
+	struct http_request request;
+};
+
+struct http_request_parser *http_request_parser_init(struct istream *input)
+{
+	struct http_request_parser *parser;
+
+	parser = i_new(struct http_request_parser, 1);
+	http_message_parser_init(&parser->parser, input);
+	return parser;
+}
+
+void http_request_parser_deinit(struct http_request_parser **_parser)
+{
+	struct http_request_parser *parser = *_parser;
+
+	http_message_parser_deinit(&parser->parser);
+	i_free(parser);
+}
+
+static void
+http_request_parser_restart(struct http_request_parser *parser)
+{
+	http_message_parser_restart(&parser->parser);
+	memset(&parser->request, 0, sizeof(parser->request));
+}
+
+static int http_request_parse_method(struct http_request_parser *parser)
+{
+	const unsigned char *p = parser->parser.cur;
+
+	/* method         = token
+	 */
+	while (p < parser->parser.end && http_char_is_token(*p))
+		p++;
+
+	if (p == parser->parser.end)
+		return 0;
+	parser->request.method =
+		p_strdup_until(parser->parser.msg_pool, parser->parser.cur, p);
+	parser->parser.cur = p;
+	return 1;
+}
+
+static int http_request_parse_target(struct http_request_parser *parser)
+{
+	const unsigned char *p = parser->parser.cur;
+
+	/* We'll just parse anything up to the first SP or a control char.
+	   We could also implement workarounds for buggy HTTP clients and
+	   parse anything up to the HTTP-version and return 301 with the
+	   target properly encoded. */
+	while (p < parser->parser.end && *p > ' ')
+		p++;
+
+	if (p == parser->parser.end)
+		return 0;
+	parser->request.target =
+		p_strdup_until(parser->parser.msg_pool, parser->parser.cur, p);
+	parser->parser.cur = p;
+	return 1;
+}
+
+static inline const char *_chr_sanitize(unsigned char c)
+{
+	if (c >= 0x20 && c < 0x7F)
+		return t_strdup_printf("'%c'", c);
+	return t_strdup_printf("0x%02x", c);
+}
+
+static int http_request_parse(struct http_request_parser *parser,
+			      const char **error_r)
+{
+	struct http_message_parser *_parser = &parser->parser;
+	int ret;
+
+	/* request-line = method SP request-target SP HTTP-version CRLF
+	 */
+
+	for (;;) {
+		switch (parser->state) {
+		case HTTP_REQUEST_PARSE_STATE_INIT:
+			http_request_parser_restart(parser);
+			parser->state = HTTP_REQUEST_PARSE_STATE_VERSION;
+			if (_parser->cur == _parser->end)
+				return 0;
+			if (*_parser->cur == '\r' || *_parser->cur == '\n') {
+				/* HTTP/1.0 client sent a CRLF after body.
+				   ignore it. */
+				parser->state = HTTP_REQUEST_PARSE_STATE_CR;
+				return http_request_parse(parser, error_r);
+			}
+			/* fall through */
+		case HTTP_REQUEST_PARSE_STATE_METHOD:
+			if ((ret=http_request_parse_method(parser)) <= 0) {
+				if (ret < 0)
+					*error_r = "Invalid HTTP method in request";
+				return ret;
+			}
+			parser->state = HTTP_REQUEST_PARSE_STATE_SP1;
+			if (_parser->cur == _parser->end)
+				return 0;
+			/* fall through */
+		case HTTP_REQUEST_PARSE_STATE_SP1:
+			if (*_parser->cur != ' ') {
+				*error_r = t_strdup_printf
+					("Expected ' ' after request method, but found %s",
+						_chr_sanitize(*_parser->cur));
+				return -1;
+			}
+			_parser->cur++;
+			parser->state = HTTP_REQUEST_PARSE_STATE_TARGET;
+			if (_parser->cur >= _parser->end)
+				return 0;
+			/* fall through */
+		case HTTP_REQUEST_PARSE_STATE_TARGET:
+			if ((ret=http_request_parse_target(parser)) <= 0) {
+				if (ret < 0)
+					*error_r = "Invalid HTTP target in request";
+				return ret;
+			}
+			parser->state = HTTP_REQUEST_PARSE_STATE_SP2;
+			if (_parser->cur == _parser->end)
+				return 0;
+			/* fall through */
+		case HTTP_REQUEST_PARSE_STATE_SP2:
+			if (*_parser->cur != ' ') {
+				*error_r = t_strdup_printf
+					("Expected ' ' after request target, but found %s",
+						_chr_sanitize(*_parser->cur));
+				return -1;
+			}
+			_parser->cur++;
+			parser->state = HTTP_REQUEST_PARSE_STATE_VERSION;
+			if (_parser->cur >= _parser->end)
+				return 0;
+			/* fall through */
+		case HTTP_REQUEST_PARSE_STATE_VERSION:
+			if ((ret=http_message_parse_version(&parser->parser)) <= 0) {
+				if (ret < 0)
+					*error_r = "Invalid HTTP version in request";
+				return ret;
+			}
+			parser->state = HTTP_REQUEST_PARSE_STATE_CR;
+			if (_parser->cur == _parser->end)
+				return 0;
+			/* fall through */
+		case HTTP_REQUEST_PARSE_STATE_CR:
+			if (*_parser->cur == '\r')
+				_parser->cur++;
+			parser->state = HTTP_REQUEST_PARSE_STATE_LF;
+			if (_parser->cur == _parser->end)
+				return 0;
+			/* fall through */
+		case HTTP_REQUEST_PARSE_STATE_LF:
+			if (*_parser->cur != '\n') {
+				*error_r = t_strdup_printf
+					("Expected line end after request, but found %s",
+						_chr_sanitize(*_parser->cur));
+				return -1;
+			}
+			_parser->cur++;
+			parser->state = HTTP_REQUEST_PARSE_STATE_HEADER;
+			return 1;
+		case HTTP_REQUEST_PARSE_STATE_HEADER:
+		default:
+			i_unreached();
+		}
+	}
+
+	i_unreached();
+	return -1;
+}
+
+static int http_request_parse_request_line(struct http_request_parser *parser,
+					   const char **error_r)
+{
+	struct http_message_parser *_parser = &parser->parser;
+	const unsigned char *begin;
+	size_t size, old_bytes = 0;
+	int ret;
+
+	while ((ret = i_stream_read_data(_parser->input, &begin, &size,
+					 old_bytes)) > 0) {
+		_parser->cur = begin;
+		_parser->end = _parser->cur + size;
+
+		if ((ret = http_request_parse(parser, error_r)) < 0)
+			return -1;
+
+		i_stream_skip(_parser->input, _parser->cur - begin);
+		if (ret > 0)
+			return 1;
+		old_bytes = i_stream_get_data_size(_parser->input);
+	}
+
+	i_assert(ret != -2);
+	if (ret < 0) {
+		if (_parser->input->eof &&
+		    parser->state == HTTP_REQUEST_PARSE_STATE_INIT)
+			return 0;
+		*error_r = "Stream error";
+		return -1;
+	}
+	return 0;
+}
+
+int http_request_parse_next(struct http_request_parser *parser,
+			    struct http_request **request_r,
+			    const char **error_r)
+{
+	int ret;
+
+	/* make sure we finished streaming payload from previous request
+	   before we continue. */
+	if ((ret = http_message_parse_finish_payload(&parser->parser, error_r)) <= 0)
+		return ret;
+
+	/* HTTP-message   = start-line
+	                   *( header-field CRLF )
+	                    CRLF
+	                    [ message-body ]
+	 */
+	if (parser->state != HTTP_REQUEST_PARSE_STATE_HEADER) {
+		if ((ret = http_request_parse_request_line(parser, error_r)) <= 0)
+			return ret;
+	} 
+	if ((ret = http_message_parse_headers(&parser->parser, error_r)) <= 0)
+		return ret;
+	if (http_message_parse_body(&parser->parser, error_r) < 0)
+		return -1;
+	parser->state = HTTP_REQUEST_PARSE_STATE_INIT;
+
+	parser->request.version_major = parser->parser.msg.version_major;
+	parser->request.version_minor = parser->parser.msg.version_minor;
+	parser->request.date = parser->parser.msg.date;
+	parser->request.payload = parser->parser.payload;
+	parser->request.headers = parser->parser.msg.headers;
+	parser->request.connection_close = parser->parser.msg.connection_close;
+
+	*request_r = &parser->request;
+	return 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-http/http-request-parser.h	Thu Jul 11 14:18:42 2013 +0300
@@ -0,0 +1,29 @@
+#ifndef HTTP_REQUEST_PARSER_H
+#define HTTP_REQUEST_PARSER_H
+
+#include "http-response.h"
+
+struct http_request {
+	const char *method;
+	const char *target;
+
+	unsigned char version_major;
+	unsigned char version_minor;
+
+	time_t date;
+	struct istream *payload;
+
+	ARRAY_TYPE(http_response_header) headers;
+
+	unsigned int connection_close:1;
+};
+
+struct http_request_parser *
+http_request_parser_init(struct istream *input);
+void http_request_parser_deinit(struct http_request_parser **_parser);
+
+int http_request_parse_next(struct http_request_parser *parser,
+			    struct http_request **request_r,
+			    const char **error_r);
+
+#endif
--- a/src/lib-http/http-response-parser.c	Thu Jul 11 12:54:02 2013 +0300
+++ b/src/lib-http/http-response-parser.c	Thu Jul 11 14:18:42 2013 +0300
@@ -1,15 +1,9 @@
 /* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
-#include "array.h"
-#include "str.h"
-#include "strfuncs.h"
 #include "istream.h"
 #include "http-parser.h"
-#include "http-date.h"
-#include "http-header-parser.h"
-#include "http-transfer.h"
-
+#include "http-message-parser.h"
 #include "http-response-parser.h"
 
 #include <ctype.h>
@@ -27,20 +21,10 @@
 };
 
 struct http_response_parser {
-	struct istream *input;
-
-	const unsigned char *begin, *cur, *end;
-	const char *error;
-
+	struct http_message_parser parser;
 	enum http_response_parser_state state;
-	struct http_header_parser *header_parser;
 
-	uoff_t content_length;
-	const char *transfer_encoding;
-	struct istream *payload;
-
-	struct http_response *response;
-	pool_t response_pool;
+	struct http_response response;
 };
 
 struct http_response_parser *http_response_parser_init(struct istream *input)
@@ -48,7 +32,7 @@
 	struct http_response_parser *parser;
 
 	parser = i_new(struct http_response_parser, 1);
-	parser->input = input;
+	http_message_parser_init(&parser->parser, input);
 	return parser;
 }
 
@@ -56,52 +40,21 @@
 {
 	struct http_response_parser *parser = *_parser;
 
-	if (parser->header_parser != NULL)
-		http_header_parser_deinit(&parser->header_parser);
-	if (parser->response_pool != NULL)
-		pool_unref(&parser->response_pool);
-	if (parser->payload != NULL)
-		i_stream_unref(&parser->payload);
+	http_message_parser_deinit(&parser->parser);
 	i_free(parser);
 }
 
 static void
 http_response_parser_restart(struct http_response_parser *parser)
 {
-	i_assert(parser->payload == NULL);
-	parser->content_length = 0;
-	parser->transfer_encoding = NULL;
-	if (parser->response_pool != NULL)
-		pool_unref(&parser->response_pool);
-	parser->response_pool = pool_alloconly_create("http_response", 4096);
-	parser->response = p_new(parser->response_pool, struct http_response, 1);
-	parser->response->date = (time_t)-1;
-	p_array_init(&parser->response->headers, parser->response_pool, 32);
-}
-
-static int http_response_parse_version(struct http_response_parser *parser)
-{
-	const unsigned char *p = parser->cur;
-	const size_t size = parser->end - parser->cur;
-
-	/* HTTP-version  = HTTP-name "/" DIGIT "." DIGIT
-	   HTTP-name     = %x48.54.54.50 ; "HTTP", case-sensitive
-	 */
-	if (size < 8)
-		return 0;
-	if (memcmp(p, "HTTP/", 5) != 0 ||
-	    !i_isdigit(p[5]) || p[6] != '.' || !i_isdigit(p[7]))
-		return -1;
-	parser->response->version_major = p[5] - '0';
-	parser->response->version_minor = p[7] - '0';
-	parser->cur += 8;
-	return 1;
+	http_message_parser_restart(&parser->parser);
+	memset(&parser->response, 0, sizeof(parser->response));
 }
 
 static int http_response_parse_status(struct http_response_parser *parser)
 {
-	const unsigned char *p = parser->cur;
-	const size_t size = parser->end - parser->cur;
+	const unsigned char *p = parser->parser.cur;
+	const size_t size = parser->parser.end - parser->parser.cur;
 
 	/* status-code   = 3DIGIT
 	 */
@@ -109,26 +62,26 @@
 		return 0;
 	if (!i_isdigit(p[0]) || !i_isdigit(p[1]) || !i_isdigit(p[2]))
 		return -1;
-	parser->response->status =
+	parser->response.status =
 		(p[0] - '0')*100 + (p[1] - '0')*10 + (p[2] - '0');
-	parser->cur += 3;
+	parser->parser.cur += 3;
 	return 1;
 }
 
 static int http_response_parse_reason(struct http_response_parser *parser)
 {
-	const unsigned char *p = parser->cur;
+	const unsigned char *p = parser->parser.cur;
 
 	/* reason-phrase = *( HTAB / SP / VCHAR / obs-text )
 	 */
-	while (p < parser->end && http_char_is_text(*p))
+	while (p < parser->parser.end && http_char_is_text(*p))
 		p++;
 
-	if (p == parser->end)
+	if (p == parser->parser.end)
 		return 0;
-	parser->response->reason =
-		p_strdup_until(parser->response_pool, parser->cur, p);
-	parser->cur = p;
+	parser->response.reason =
+		p_strdup_until(parser->parser.msg_pool, parser->parser.cur, p);
+	parser->parser.cur = p;
 	return 1;
 }
 
@@ -139,8 +92,10 @@
 	return t_strdup_printf("0x%02x", c);
 }
 
-static int http_response_parse(struct http_response_parser *parser)
+static int http_response_parse(struct http_response_parser *parser,
+			       const char **error_r)
 {
+	struct http_message_parser *_parser = &parser->parser;
 	int ret;
 
 	/* status-line   = HTTP-version SP status-code SP reason-phrase CRLF
@@ -155,71 +110,73 @@
 			parser->state = HTTP_RESPONSE_PARSE_STATE_VERSION;
 			/* fall through */
 		case HTTP_RESPONSE_PARSE_STATE_VERSION:
-			if ((ret=http_response_parse_version(parser)) <= 0) {
+			if ((ret=http_message_parse_version(_parser)) <= 0) {
 				if (ret < 0)
-					parser->error = "Invalid HTTP version in response";
+					*error_r = "Invalid HTTP version in response";
 				return ret;
 			}
 			parser->state = HTTP_RESPONSE_PARSE_STATE_SP1;
-			if (parser->cur == parser->end)
+			if (_parser->cur == _parser->end)
 				return 0;
 			/* fall through */
 		case HTTP_RESPONSE_PARSE_STATE_SP1:
-			if (*parser->cur != ' ') {
-				parser->error = t_strdup_printf
+			if (*_parser->cur != ' ') {
+				*error_r = t_strdup_printf
 					("Expected ' ' after response version, but found %s",
-						_chr_sanitize(*parser->cur));
+						_chr_sanitize(*_parser->cur));
 				return -1;
 			}
-			parser->cur++;
+			_parser->cur++;
 			parser->state = HTTP_RESPONSE_PARSE_STATE_STATUS;
-			if (parser->cur >= parser->end)
+			if (_parser->cur >= _parser->end)
 				return 0;
 			/* fall through */
 		case HTTP_RESPONSE_PARSE_STATE_STATUS:
 			if ((ret=http_response_parse_status(parser)) <= 0) {
 				if (ret < 0)
-					parser->error = "Invalid HTTP status code in response";
+					*error_r = "Invalid HTTP status code in response";
 				return ret;
 			}
 			parser->state = HTTP_RESPONSE_PARSE_STATE_SP2;
-			if (parser->cur == parser->end)
+			if (_parser->cur == _parser->end)
 				return 0;
 			/* fall through */
 		case HTTP_RESPONSE_PARSE_STATE_SP2:
-			if (*parser->cur != ' ') {
-				parser->error = t_strdup_printf
+			if (*_parser->cur != ' ') {
+				*error_r = t_strdup_printf
 					("Expected ' ' after response status code, but found %s",
-						_chr_sanitize(*parser->cur));
+						_chr_sanitize(*_parser->cur));
 				return -1;
 			}
-			parser->cur++;
+			_parser->cur++;
 			parser->state = HTTP_RESPONSE_PARSE_STATE_REASON;
-			if (parser->cur >= parser->end)
+			if (_parser->cur >= _parser->end)
 				return 0;
 			/* fall through */
 		case HTTP_RESPONSE_PARSE_STATE_REASON:
-			if ((ret=http_response_parse_reason(parser)) <= 0)
-				return ret;
+			if ((ret=http_response_parse_reason(parser)) <= 0) {
+				i_assert(ret == 0);
+				return 0;
+			}
 			parser->state = HTTP_RESPONSE_PARSE_STATE_CR;
-			if (parser->cur == parser->end)
+			if (_parser->cur == _parser->end)
 				return 0;
 			/* fall through */
 		case HTTP_RESPONSE_PARSE_STATE_CR:
-			if (*parser->cur == '\r')
-				parser->cur++;
+			if (*_parser->cur == '\r')
+				_parser->cur++;
 			parser->state = HTTP_RESPONSE_PARSE_STATE_LF;
-			if (parser->cur == parser->end)
+			if (_parser->cur == _parser->end)
 				return 0;
 			/* fall through */
 		case HTTP_RESPONSE_PARSE_STATE_LF:
-			if (*parser->cur != '\n') {
-				parser->error = t_strdup_printf
+			if (*_parser->cur != '\n') {
+				*error_r = t_strdup_printf
 					("Expected line end after response, but found %s",
-						_chr_sanitize(*parser->cur));
+						_chr_sanitize(*_parser->cur));
 				return -1;
 			}
-			parser->cur++;
+			_parser->cur++;
 			parser->state = HTTP_RESPONSE_PARSE_STATE_HEADER;
 			return 1;
 		case HTTP_RESPONSE_PARSE_STATE_HEADER:
@@ -232,168 +189,61 @@
 	return -1;
 }
 
-static int http_response_parse_status_line(struct http_response_parser *parser)
+static int http_response_parse_status_line(struct http_response_parser *parser,
+					   const char **error_r)
 {
+	struct http_message_parser *_parser = &parser->parser;
+	const unsigned char *begin;
 	size_t size, old_bytes = 0;
 	int ret;
 
-	while ((ret = i_stream_read_data(parser->input,
-					 &parser->begin, &size, old_bytes)) > 0) {
-		parser->cur = parser->begin;
-		parser->end = parser->cur + size;
+	while ((ret = i_stream_read_data(_parser->input, &begin, &size,
+					 old_bytes)) > 0) {
+		_parser->cur = begin;
+		_parser->end = _parser->cur + size;
 
-		if ((ret = http_response_parse(parser)) < 0)
+		if ((ret = http_response_parse(parser, error_r)) < 0)
 			return -1;
 
-		i_stream_skip(parser->input, parser->cur - parser->begin);
+		i_stream_skip(_parser->input, _parser->cur - begin);
 		if (ret > 0)
 			return 1;
-		old_bytes = i_stream_get_data_size(parser->input);
+		old_bytes = i_stream_get_data_size(_parser->input);
 	}
 
 	i_assert(ret != -2);
 	if (ret < 0) {
-		if (parser->input->eof && parser->state == HTTP_RESPONSE_PARSE_STATE_INIT)
+		if (_parser->input->eof &&
+		    parser->state == HTTP_RESPONSE_PARSE_STATE_INIT)
 			return 0;
-		parser->error = "Stream error";
+		*error_r = "Stream error";
 		return -1;
 	}
 	return 0;
 }
 
-static int
-http_response_parse_header(struct http_response_parser *parser,
-			   const char *name, const unsigned char *data, size_t size)
-{
-	struct http_response_header *hdr;
-	struct http_parser hparser;
-	void *value;
-
-	hdr = array_append_space(&parser->response->headers);
-	hdr->key = p_strdup(parser->response_pool, name);
-	hdr->value = value = p_malloc(parser->response_pool, size+1);
-	memcpy(value, data, size);
-	hdr->size = size;
-
-	switch (name[0]) {
-	case 'C': case 'c':
-		if (strcasecmp(name, "Connection") == 0) {
-			const char *option;
-
-			/* Connection        = 1#connection-option
-				 connection-option = token
-			*/
-			http_parser_init(&hparser, data, size);
-			for (;;) {
-				if (http_parse_token_list_next(&hparser, &option) <= 0)
-					break;
-				if (strcasecmp(option, "close") == 0) {
-					parser->response->connection_close = TRUE;
-					break; // not interested in any other options
-				}
-			}
-			return 1;
-		}
-		if (strcasecmp(name, "Content-Length") == 0) {
-			/* Content-Length = 1*DIGIT */
-			if (str_to_uoff(hdr->value, &parser->content_length) < 0) {
-				parser->error = "Invalid Content-Length header";
-				return -1;
-			}
-			return 1;
-		}
-		break;
-	case 'D': case 'd':
-		if (strcasecmp(name, "Date") == 0) {
-			/* Date = HTTP-date */
-			(void)http_date_parse(data, size, &parser->response->date);
-			return 1;
-		}
-		break;
-	case 'L': case 'l':
-		if (strcasecmp(name, "Location") == 0) {
-			/* Location = URI-reference (not parsed here) */
-			parser->response->location =
-				p_strndup(parser->response_pool, data, size);
-			return 1;
-		}
-		break;
-	case 'T': case 't':
-		if (strcasecmp(name, "Transfer-Encoding") == 0) {
-			/* Transfer-Encoding = 1#transfer-coding */
-			parser->transfer_encoding = hdr->value;
-			return 1;
-		}
-		break;
-	default:
-		break;
-	}
-	return 1;
-}
-
 int http_response_parse_next(struct http_response_parser *parser,
 			     bool no_payload, struct http_response **response_r,
 			     const char **error_r)
 {
-	struct http_parser hparser;
-	const char *field_name, *error;
-	const unsigned char *field_data;
-	size_t field_size;
 	int ret;
 
 	/* make sure we finished streaming payload from previous response
 	   before we continue. */
-	if (parser->payload != NULL) {
-		struct istream *payload = parser->payload;
-		const unsigned char *data;
-		size_t size;
-
-		i_assert(parser->state == HTTP_RESPONSE_PARSE_STATE_INIT);
-
-		while ((ret = i_stream_read_data(payload, &data, &size, 0)) > 0)
-			i_stream_skip(payload, size);
-		if (ret == 0 || payload->stream_errno != 0) {
-			if (ret < 0)
-				*error_r = "Stream error while skipping payload";
-			return ret;
-		}
-		i_stream_unref(&parser->payload);
-	}
+	if ((ret = http_message_parse_finish_payload(&parser->parser, error_r)) <= 0)
+		return ret;
 
 	/* HTTP-message   = start-line
 	                   *( header-field CRLF )
 	                    CRLF
 	                    [ message-body ]
 	 */
-
-	/* start-line */
 	if (parser->state != HTTP_RESPONSE_PARSE_STATE_HEADER) {
-		if ((ret=http_response_parse_status_line(parser)) <= 0) {
-			*error_r = parser->error;
+		if ((ret = http_response_parse_status_line(parser, error_r)) <= 0)
 			return ret;
-		}
-		/* *( header-field CRLF ) CRLF */
-		if (parser->header_parser == NULL)
-			parser->header_parser = http_header_parser_init(parser->input);
-		else
-			http_header_parser_reset(parser->header_parser);
 	} 
-
-	while ((ret=http_header_parse_next_field
-		(parser->header_parser, &field_name, &field_data, &field_size, &error)) > 0) {
-		if (field_name == NULL) break;
-		if ((ret=http_response_parse_header
-		     (parser, field_name, field_data, field_size)) < 0) {
-			*error_r = parser->error;
-			return -1;
-		}
-	}
-
-	if (ret <= 0) {
-		if (ret < 0)
-			*error_r = t_strdup_printf("Failed to parse response header: %s", error);
+	if ((ret = http_message_parse_headers(&parser->parser, error_r)) <= 0)
 		return ret;
-	}
 
 	/* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21
 	     Section 3.3.2:
@@ -401,12 +251,12 @@
 	   A server MUST NOT send a Content-Length header field in any response
 	   with a status code of 1xx (Informational) or 204 (No Content). [...]
 	 */
-	if ((parser->response->status / 100 == 1 ||
-		parser->response->status == 204) && parser->content_length > 0) {
+	if ((parser->response.status / 100 == 1 || parser->response.status == 204) &&
+	    parser->parser.msg.content_length > 0) {
 		*error_r = t_strdup_printf(
 			"Unexpected Content-Length header field for %u response "
-			"(length=%"PRIuUOFF_T")", parser->response->status,
-			parser->content_length);
+			"(length=%"PRIuUOFF_T")", parser->response.status,
+			parser->parser.msg.content_length);
 		return -1;
 	}
 
@@ -419,46 +269,26 @@
 	   header fields, regardless of the header fields present in the
 	   message, and thus cannot contain a message body.
 	 */
-	if (parser->response->status / 100 == 1 || parser->response->status == 204
-		|| parser->response->status == 304) { // HEAD is handled in caller
+	if (parser->response.status / 100 == 1 || parser->response.status == 204
+		|| parser->response.status == 304) { // HEAD is handled in caller
 		no_payload = TRUE;
 	}
 
 	if (!no_payload) {
 		/* [ message-body ] */
-		if (parser->content_length > 0) {
-			/* Got explicit message size from Content-Length: header */
-			parser->payload = parser->response->payload =
-				i_stream_create_limit(parser->input, parser->content_length);
-		} else if (parser->transfer_encoding != NULL) {
-			const char *tenc;
+		if (http_message_parse_body(&parser->parser, error_r) < 0)
+			return -1;
+	}
+	parser->state = HTTP_RESPONSE_PARSE_STATE_INIT;
 
-			/* Transfer-Encoding = 1#transfer-coding
-			   transfer-coding    = "chunked" / "compress" / "deflate" / "gzip"
-			                      / transfer-extension       ;  [FIXME]
-			   transfer-extension = token *( OWS ";" OWS transfer-parameter )
-			*/
-			http_parser_init(&hparser,
-				(const unsigned char *)parser->transfer_encoding,
-				strlen(parser->transfer_encoding));
-			for (;;) {
-				if (http_parse_token_list_next(&hparser, &tenc) <= 0)
-					break;
-				if (strcasecmp(tenc, "chunked") == 0) {
-					parser->payload =  parser->response->payload =
-						http_transfer_chunked_istream_create(parser->input);
-					break; // FIXME
-				} else {
-					*error_r = t_strdup_printf(
-						"Unkown Transfer-Encoding `%s' for %u response",
-						tenc, parser->response->status);
-					return -1;
-				}
-			}
-		}
-	}
+	parser->response.version_major = parser->parser.msg.version_major;
+	parser->response.version_minor = parser->parser.msg.version_minor;
+	parser->response.location = parser->parser.msg.location;
+	parser->response.date = parser->parser.msg.date;
+	parser->response.payload = parser->parser.payload;
+	parser->response.headers = parser->parser.msg.headers;
+	parser->response.connection_close = parser->parser.msg.connection_close;
 
-	parser->state = HTTP_RESPONSE_PARSE_STATE_INIT;
-	*response_r = parser->response;
+	*response_r = &parser->response;
 	return 1;
 }