]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: Implemented http-request-parser for HTTP servers.
authorTimo Sirainen <tss@iki.fi>
Thu, 11 Jul 2013 11:18:42 +0000 (14:18 +0300)
committerTimo Sirainen <tss@iki.fi>
Thu, 11 Jul 2013 11:18:42 +0000 (14:18 +0300)
Moved code common with it and http-response-parser to http-message-parser.

src/lib-http/Makefile.am
src/lib-http/http-message-parser.c [new file with mode: 0644]
src/lib-http/http-message-parser.h [new file with mode: 0644]
src/lib-http/http-request-parser.c [new file with mode: 0644]
src/lib-http/http-request-parser.h [new file with mode: 0644]
src/lib-http/http-response-parser.c

index 32693e7c5470ebdf66bc2421140750363caf0714..f773b403b8c3223abc72d7ecf1fc6fa7fe04ac00 100644 (file)
@@ -12,6 +12,8 @@ libhttp_la_SOURCES = \
        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 @@ headers = \
        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 @@ test_http_response_parser_LDADD = \
        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)
diff --git a/src/lib-http/http-message-parser.c b/src/lib-http/http-message-parser.c
new file mode 100644 (file)
index 0000000..a7fd637
--- /dev/null
@@ -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;
+}
diff --git a/src/lib-http/http-message-parser.h b/src/lib-http/http-message-parser.h
new file mode 100644 (file)
index 0000000..d5bcf91
--- /dev/null
@@ -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
diff --git a/src/lib-http/http-request-parser.c b/src/lib-http/http-request-parser.c
new file mode 100644 (file)
index 0000000..a06b34c
--- /dev/null
@@ -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;
+}
diff --git a/src/lib-http/http-request-parser.h b/src/lib-http/http-request-parser.h
new file mode 100644 (file)
index 0000000..7f1cb31
--- /dev/null
@@ -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
index 2eea86a9341f376780cd260d20ca4b3cae27a441..e215b0560bc4d1b51eb43db9e098e702449357d2 100644 (file)
@@ -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 @@ enum http_response_parser_state {
 };
 
 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 *http_response_parser_init(struct istream *input)
        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 @@ void http_response_parser_deinit(struct http_response_parser **_parser)
 {
        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 @@ static int http_response_parse_status(struct http_response_parser *parser)
                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 @@ static inline const char *_chr_sanitize(unsigned char c)
        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 @@ static int http_response_parse(struct http_response_parser *parser)
                        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 @@ static int http_response_parse(struct http_response_parser *parser)
        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 @@ int http_response_parse_next(struct http_response_parser *parser,
           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 @@ int http_response_parse_next(struct http_response_parser *parser,
           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;
-
-                       /* 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;
-                               }
-                       }
-               }
+               if (http_message_parse_body(&parser->parser, error_r) < 0)
+                       return -1;
        }
-
        parser->state = HTTP_RESPONSE_PARSE_STATE_INIT;
-       *response_r = parser->response;
+
+       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;
+
+       *response_r = &parser->response;
        return 1;
 }