]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: Adjusted message and request parsers to return an error code.
authorStephan Bosch <stephan@rename-it.nl>
Sun, 15 Sep 2013 00:50:08 +0000 (03:50 +0300)
committerStephan Bosch <stephan@rename-it.nl>
Sun, 15 Sep 2013 00:50:08 +0000 (03:50 +0300)
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
src/lib-http/test-http-server.c

index 1c1ea54b8a205304ea5d6bbe6557958961973f6b..5cf381895e6d3acf1e6aa15975251b2dcf18c3d5 100644 (file)
@@ -62,35 +62,45 @@ int http_message_parse_version(struct http_message_parser *parser)
        const unsigned char *p = parser->cur;
        const size_t size = parser->end - parser->cur;
 
+       parser->error_code = HTTP_MESSAGE_PARSE_ERROR_NONE;
+       parser->error = NULL;
+
        /* 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]))
+           !i_isdigit(p[5]) || p[6] != '.' || !i_isdigit(p[7])) {
+               parser->error = "Bad HTTP version";
+               parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
                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)
+int http_message_parse_finish_payload(struct http_message_parser *parser)
 {
        const unsigned char *data;
        size_t size;
        int ret;
 
+       parser->error_code = HTTP_MESSAGE_PARSE_ERROR_NONE;
+       parser->error = NULL;
+
        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";
+               if (ret < 0) {
+                       parser->error = "Stream error while skipping payload";
+                       parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_STREAM;
+               }
                return ret;
        }
        i_stream_unref(&parser->payload);
@@ -99,8 +109,7 @@ int http_message_parse_finish_payload(struct http_message_parser *parser,
 
 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)
+                         const char *name, const unsigned char *data, size_t size)
 {
        const struct http_header_field *hdr;
        struct http_parser hparser;
@@ -141,7 +150,8 @@ http_message_parse_header(struct http_message_parser *parser,
                        }
 
                        if (hparser.cur < hparser.end || num_tokens == 0) {
-                               *error_r = "Invalid Connection header";
+                               parser->error = "Invalid Connection header";
+                               parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
                                return -1;
                        }
 
@@ -150,12 +160,14 @@ http_message_parse_header(struct http_message_parser *parser,
                /* Content-Length: */
                if (strcasecmp(name, "Content-Length") == 0) {
                        if (parser->msg.have_content_length) {
-                               *error_r = "Duplicate Content-Length header";
+                               parser->error = "Duplicate Content-Length header";
+                               parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
                                return -1;
                        }
                        /* Content-Length = 1*DIGIT */
                        if (str_to_uoff(hdr->value, &parser->msg.content_length) < 0) {
-                               *error_r = "Invalid Content-Length header";
+                               parser->error= "Invalid Content-Length header";
+                               parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
                                return -1;
                        }
                        parser->msg.have_content_length = TRUE;
@@ -165,7 +177,8 @@ http_message_parse_header(struct http_message_parser *parser,
        case 'D': case 'd':
                if (strcasecmp(name, "Date") == 0) {
                        if (parser->msg.date != (time_t)-1) {
-                               *error_r = "Duplicate Date header";
+                               parser->error = "Duplicate Date header";
+                               parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
                                return -1;
                        }
 
@@ -269,7 +282,8 @@ http_message_parse_header(struct http_message_parser *parser,
 
                        if (hparser.cur < hparser.end ||
                                array_count(&parser->msg.transfer_encoding) == 0) {
-                               *error_r = "Invalid Transfer-Encoding header";
+                               parser->error = "Invalid Transfer-Encoding header";
+                               parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
                                return -1;
                        }
                        return 0;
@@ -281,14 +295,16 @@ http_message_parse_header(struct http_message_parser *parser,
        return 0;
 }
 
-int http_message_parse_headers(struct http_message_parser *parser,
-                              const char **error_r)
+int http_message_parse_headers(struct http_message_parser *parser)
 {
        const unsigned char *field_data;
        const char *field_name, *error;
        size_t field_size;
        int ret;
 
+       parser->error_code = HTTP_MESSAGE_PARSE_ERROR_NONE;
+       parser->error = NULL;
+
        /* *( header-field CRLF ) CRLF */
        while ((ret=http_header_parse_next_field(parser->header_parser,
                &field_name, &field_data, &field_size, &error)) > 0) {
@@ -298,23 +314,28 @@ int http_message_parse_headers(struct http_message_parser *parser,
                }
 
                if (http_message_parse_header(parser,
-                       field_name, field_data, field_size, error_r) < 0)
+                       field_name, field_data, field_size) < 0)
                        return -1;
        }
 
        if (ret < 0) {
-               *error_r = t_strdup_printf(
-                       "Failed to parse response header: %s", error);
+               if (parser->input->eof || parser->input->stream_errno != 0)  {                  
+                       parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_STREAM;
+                       parser->error = "Broken stream";
+               } else {
+                       parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+                       parser->error = t_strdup_printf("Failed to parse header: %s", error);
+               }
+       
        }
        return ret;
 }
 
-
-int http_message_parse_body(struct http_message_parser *parser, bool request,
-                           const char **error_r)
+int http_message_parse_body(struct http_message_parser *parser, bool request)
 {
-       *error_r = NULL;
-
+       parser->error_code = HTTP_MESSAGE_PARSE_ERROR_NONE;
+       parser->error = NULL;
        if (array_is_created(&parser->msg.transfer_encoding)) {
                const struct http_transfer_coding *coding;
 
@@ -324,20 +345,27 @@ int http_message_parse_body(struct http_message_parser *parser, bool request,
                        if (strcasecmp(coding->name, "chunked") == 0) {
                                chunked_last = TRUE;
                
-                               if (*error_r == NULL && array_is_created(&coding->parameters) &&
-                                       array_count(&coding->parameters) > 0) {
+                               if ((parser->error_code == HTTP_MESSAGE_PARSE_ERROR_NONE)
+                                       && array_is_created(&coding->parameters)
+                                       && array_count(&coding->parameters) > 0) {
                                        const struct http_transfer_param *param =
                                                array_idx(&coding->parameters, 0);
-                                       
-                                       *error_r = t_strdup_printf("Unexpected parameter `%s' specified"
+
+                                       parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BAD_MESSAGE;
+                                       parser->error = t_strdup_printf(
+                                               "Unexpected parameter `%s' specified"
                                                "for the `%s' transfer coding", param->attribute, coding->name);
+                                       /* recoverable */
                                }
                        } else if (chunked_last) {
-                               *error_r = "Chunked Transfer-Encoding must be last";
+                               parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+                               parser->error = "Chunked Transfer-Encoding must be last";
                                return -1;
-                       } else if (*error_r == NULL) {
-                               *error_r = t_strdup_printf(
+                       } else if (parser->error_code == HTTP_MESSAGE_PARSE_ERROR_NONE) {
+                               parser->error_code = HTTP_MESSAGE_PARSE_ERROR_NOT_IMPLEMENTED;
+                               parser->error = t_strdup_printf(
                                "Unknown transfer coding `%s'", coding->name);
+                               /* recoverable */
                }
        }
 
@@ -364,7 +392,8 @@ int http_message_parse_body(struct http_message_parser *parser, bool request,
                           length cannot be determined reliably; the server MUST respond with
                           the 400 (Bad Request) status code and then close the connection.
                         */
-                       *error_r = "Final Transfer-Encoding in request is not `chunked'";
+                       parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+                       parser->error = "Final Transfer-Encoding in request is not chunked";
                        return -1;
                }
 
@@ -401,5 +430,7 @@ int http_message_parse_body(struct http_message_parser *parser, bool request,
                parser->payload =
                        i_stream_create_limit(parser->input, (size_t)-1);
        }
+       if (parser->error_code != HTTP_MESSAGE_PARSE_ERROR_NONE)
+               return -1;
        return 0;
 }
index c439b8cc4604dbf49cfb4259fd86e5db5e7571ea..487b4ed0a6051cf66bff4ae4d7976e38d496d9e8 100644 (file)
@@ -6,6 +6,15 @@
 
 #include "http-header.h"
 
+enum http_message_parse_error {
+       HTTP_MESSAGE_PARSE_ERROR_NONE = 0,        /* no error */
+       HTTP_MESSAGE_PARSE_ERROR_BROKEN_STREAM,   /* stream error */
+       HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE,  /* unrecoverable generic error */
+       HTTP_MESSAGE_PARSE_ERROR_BAD_MESSAGE,     /* recoverable generic error */
+       HTTP_MESSAGE_PARSE_ERROR_NOT_IMPLEMENTED, /* used unimplemented feature
+                                                   (recoverable) */
+};
+
 struct http_message {
        pool_t pool;
 
@@ -30,6 +39,9 @@ struct http_message_parser {
 
        const unsigned char *cur, *end;
 
+       const char *error;
+       enum http_message_parse_error error_code;
+
        struct http_header_parser *header_parser;
        struct istream *payload;
 
@@ -44,12 +56,9 @@ void http_message_parser_deinit(struct http_message_parser *parser);
 void http_message_parser_restart(struct http_message_parser *parser,
        pool_t pool);
 
-int http_message_parse_finish_payload(struct http_message_parser *parser,
-                                     const char **error_r);
+int http_message_parse_finish_payload(struct http_message_parser *parser);
 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, bool request,
-                           const char **error_r);
+int http_message_parse_headers(struct http_message_parser *parser);
+int http_message_parse_body(struct http_message_parser *parser, bool request);
 
 #endif
index 3d750c683d9d0874e7d35b808ca0043496dcccb1..654a3641c410e35b98ba18d27bc84cc1907654d0 100644 (file)
@@ -24,6 +24,8 @@ struct http_request_parser {
        struct http_message_parser parser;
        enum http_request_parser_state state;
 
+       enum http_request_parse_error error_code;
+
        const char *request_method;
        const char *request_target;
 
@@ -106,7 +108,7 @@ static inline const char *_chr_sanitize(unsigned char c)
 }
 
 static int http_request_parse(struct http_request_parser *parser,
-                             pool_t pool, const char **error_r)
+                             pool_t pool)
 {
        struct http_message_parser *_parser = &parser->parser;
        int ret;
@@ -125,7 +127,8 @@ static int http_request_parse(struct http_request_parser *parser,
                        if (*_parser->cur == '\r' || *_parser->cur == '\n') {
                                if (parser->skipping_line) {
                                        /* second extra CRLF; not allowed */
-                                       *error_r = "Empty request line";
+                                       parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST;
+                                       _parser->error = "Empty request line";
                                        return -1;
                                }
                                /* HTTP/1.0 client sent one extra CRLF after body.
@@ -138,19 +141,17 @@ static int http_request_parse(struct http_request_parser *parser,
                        parser->skipping_line = FALSE;
                        /* 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";
+                       if ((ret=http_request_parse_method(parser)) <= 0)
                                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",
+                               parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST;
+                               _parser->error = t_strdup_printf
+                                       ("Unexpected character %s in request method",
                                                _chr_sanitize(*_parser->cur));
                                return -1;
                        }
@@ -160,19 +161,17 @@ static int http_request_parse(struct http_request_parser *parser,
                                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";
+                       if ((ret=http_request_parse_target(parser)) <= 0)
                                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",
+                               parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST;
+                               _parser->error = t_strdup_printf
+                                       ("Unexpected character %s in request target",
                                                _chr_sanitize(*_parser->cur));
                                return -1;
                        }
@@ -183,8 +182,10 @@ static int http_request_parse(struct http_request_parser *parser,
                        /* 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";
+                               if (ret < 0) {
+                                       parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST;
+                                       _parser->error = "Invalid HTTP version in request";
+                               }
                                return ret;
                        }
                        parser->state = HTTP_REQUEST_PARSE_STATE_CR;
@@ -200,8 +201,9 @@ static int http_request_parse(struct http_request_parser *parser,
                        /* 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",
+                               parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST;
+                               _parser->error = t_strdup_printf
+                                       ("Unexpected character %s at end of request line",
                                                _chr_sanitize(*_parser->cur));
                                return -1;
                        }
@@ -223,7 +225,7 @@ static int http_request_parse(struct http_request_parser *parser,
 }
 
 static int http_request_parse_request_line(struct http_request_parser *parser,
-                                          pool_t pool, const char **error_r)
+                                          pool_t pool)
 {
        struct http_message_parser *_parser = &parser->parser;
        const unsigned char *begin;
@@ -235,7 +237,7 @@ static int http_request_parse_request_line(struct http_request_parser *parser,
                _parser->cur = begin;
                _parser->end = _parser->cur + size;
 
-               if ((ret = http_request_parse(parser, pool, error_r)) < 0)
+               if ((ret = http_request_parse(parser, pool)) < 0)
                        return -1;
 
                i_stream_skip(_parser->input, _parser->cur - begin);
@@ -244,29 +246,61 @@ static int http_request_parse_request_line(struct http_request_parser *parser,
                old_bytes = i_stream_get_data_size(_parser->input);
        }
 
-       i_assert(ret != -2);
+       if (ret == -2) {
+               parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST;
+               _parser->error = "HTTP request line is too long";
+               return -1;
+       }
        if (ret < 0) {
                if (_parser->input->eof &&
-                   parser->state == HTTP_REQUEST_PARSE_STATE_INIT)
+           parser->state == HTTP_REQUEST_PARSE_STATE_INIT)
                        return 0;
-               *error_r = "Stream error";
+               parser->error_code = HTTP_REQUEST_PARSE_ERROR_BROKEN_STREAM;
+               _parser->error = "Broken stream";
                return -1;
        }
        return 0;
 }
 
+static inline enum http_request_parse_error
+http_request_parser_message_error(struct http_request_parser *parser)
+{
+       switch (parser->parser.error_code) {
+       case HTTP_MESSAGE_PARSE_ERROR_BROKEN_STREAM:
+               return HTTP_REQUEST_PARSE_ERROR_BROKEN_STREAM;
+       case HTTP_MESSAGE_PARSE_ERROR_BAD_MESSAGE:
+               return HTTP_REQUEST_PARSE_ERROR_BAD_REQUEST;
+       case HTTP_MESSAGE_PARSE_ERROR_NOT_IMPLEMENTED:
+               return HTTP_REQUEST_PARSE_ERROR_NOT_IMPLEMENTED;
+       case HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE:
+               return HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST;
+       default:
+               break;
+       }
+       i_unreached();
+       return HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST;
+}
+
 int http_request_parse_next(struct http_request_parser *parser,
                            pool_t pool, struct http_request *request,
-                           const char **error_r)
+                           enum http_request_parse_error *error_code_r, const char **error_r)
 {
        const struct http_header_field *hdr;
        const char *error;
        int ret;
 
+       *error_code_r = parser->error_code = HTTP_REQUEST_PARSE_ERROR_NONE;
+       *error_r = parser->parser.error = NULL;
+
        /* make sure we finished streaming payload from previous request
           before we continue. */
-       if ((ret = http_message_parse_finish_payload(&parser->parser, error_r)) <= 0)
+       if ((ret = http_message_parse_finish_payload(&parser->parser)) <= 0) {
+               if (ret < 0) {
+                       *error_code_r = http_request_parser_message_error(parser);
+                       *error_r = parser->parser.error;
+               }
                return ret;
+       }
 
        /* HTTP-message   = start-line
                           *( header-field CRLF )
@@ -274,13 +308,36 @@ int http_request_parse_next(struct http_request_parser *parser,
                            [ message-body ]
         */
        if (parser->state != HTTP_REQUEST_PARSE_STATE_HEADER) {
-               if ((ret = http_request_parse_request_line(parser, pool, error_r)) <= 0)
+               ret = http_request_parse_request_line(parser, pool);
+
+               /* assign early for error reporting */
+               request->method = parser->request_method;
+               request->target_raw = parser->request_target; 
+               request->version_major = parser->parser.msg.version_major;
+               request->version_minor = parser->parser.msg.version_minor;
+
+               if (ret <= 0) {
+                       if (ret < 0) {
+                               *error_code_r = parser->error_code;
+                               *error_r = parser->parser.error;
+                       }
                        return ret;
-       } 
-       if ((ret = http_message_parse_headers(&parser->parser, error_r)) <= 0)
+               }
+       }
+
+       if ((ret = http_message_parse_headers(&parser->parser)) <= 0) {
+               if (ret < 0) {
+                       *error_code_r = http_request_parser_message_error(parser);
+                       *error_r = parser->parser.error;
+               }
                return ret;
-       if (http_message_parse_body(&parser->parser, TRUE, error_r) < 0)
+       }
+
+       if (http_message_parse_body(&parser->parser, TRUE) < 0) {
+               *error_code_r = http_request_parser_message_error(parser);
+               *error_r = parser->parser.error;
                return -1;
+       }
        parser->state = HTTP_REQUEST_PARSE_STATE_INIT;
 
        /* https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23
@@ -293,6 +350,7 @@ int http_request_parse_next(struct http_request_parser *parser,
         */
        if ((ret=http_header_field_find_unique
                (parser->parser.msg.header, "Host", &hdr)) <= 0) {
+               *error_code_r = HTTP_REQUEST_PARSE_ERROR_BAD_REQUEST;
                if (ret == 0)
                        *error_r = "Missing Host header";
                else
@@ -304,6 +362,7 @@ int http_request_parse_next(struct http_request_parser *parser,
 
        if (http_url_request_target_parse(parser->request_target, hdr->value,
                parser->parser.msg.pool, &request->target, &error) < 0) {
+               *error_code_r = HTTP_REQUEST_PARSE_ERROR_BAD_REQUEST;
                *error_r = t_strdup_printf("Bad request target `%s': %s",
                        parser->request_target, error);
                return -1;
index 1f526560fbddd6e3fb4450cda1b6432906196a5e..cc9406743dd90afc27fd8b22b65d393e22798bc5 100644 (file)
@@ -2,6 +2,17 @@
 #define HTTP_REQUEST_PARSER_H
 
 #include "http-request.h"
+enum http_request_parse_error {
+       HTTP_REQUEST_PARSE_ERROR_NONE = 0,         /* no error */
+       HTTP_REQUEST_PARSE_ERROR_BROKEN_STREAM,    /* stream error */
+       HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST,   /* unrecoverable generic error */
+       HTTP_REQUEST_PARSE_ERROR_BAD_REQUEST,      /* recoverable generic error */
+       HTTP_REQUEST_PARSE_ERROR_NOT_IMPLEMENTED,  /* used unimplemented feature
+                                                     (recoverable) */
+       HTTP_REQUEST_PARSE_ERROR_METHOD_TOO_LONG,  /* method too long (fatal) */
+       HTTP_REQUEST_PARSE_ERROR_TARGET_TOO_LONG   /* target too long (fatal) */
+};
 
 struct http_request_parser *
 http_request_parser_init(struct istream *input,
@@ -10,6 +21,6 @@ void http_request_parser_deinit(struct http_request_parser **_parser);
 
 int http_request_parse_next(struct http_request_parser *parser,
                            pool_t pool, struct http_request *request,
-                           const char **error_r);
+                           enum http_request_parse_error *error_code_r, const char **error_r);
 
 #endif
index c79fb4d44cb4f27fad6f32e87476312cad2535c4..cdff4fa18aeddd8a97e767c6298d0beb410b5136 100644 (file)
@@ -102,8 +102,7 @@ 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,
-                              const char **error_r)
+static int http_response_parse(struct http_response_parser *parser)
 {
        struct http_message_parser *_parser = &parser->parser;
        int ret;
@@ -113,94 +112,92 @@ static int http_response_parse(struct http_response_parser *parser,
           reason-phrase = *( HTAB / SP / VCHAR / obs-text )
         */
 
-       for (;;) {
-               switch (parser->state) {
-               case HTTP_RESPONSE_PARSE_STATE_INIT:
-                       http_response_parser_restart(parser);
-                       parser->state = HTTP_RESPONSE_PARSE_STATE_VERSION;
-                       /* fall through */
-               case HTTP_RESPONSE_PARSE_STATE_VERSION:
-                       if ((ret=http_message_parse_version(_parser)) <= 0) {
-                               if (ret < 0)
-                                       *error_r = "Invalid HTTP version in response";
-                               return ret;
-                       }
-                       parser->state = HTTP_RESPONSE_PARSE_STATE_SP1;
-                       if (_parser->cur == _parser->end)
-                               return 0;
-                       /* fall through */
-               case HTTP_RESPONSE_PARSE_STATE_SP1:
-                       if (*_parser->cur != ' ') {
-                               *error_r = t_strdup_printf
-                                       ("Expected ' ' after response version, but found %s",
-                                               _chr_sanitize(*_parser->cur));
-                               return -1;
-                       }
-                       _parser->cur++;
-                       parser->state = HTTP_RESPONSE_PARSE_STATE_STATUS;
-                       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)
-                                       *error_r = "Invalid HTTP status code in response";
-                               return ret;
-                       }
-                       parser->state = HTTP_RESPONSE_PARSE_STATE_SP2;
-                       if (_parser->cur == _parser->end)
-                               return 0;
-                       /* fall through */
-               case HTTP_RESPONSE_PARSE_STATE_SP2:
-                       if (*_parser->cur != ' ') {
-                               *error_r = t_strdup_printf
-                                       ("Expected ' ' after response status code, but found %s",
-                                               _chr_sanitize(*_parser->cur));
-                               return -1;
-                       }
-                       _parser->cur++;
-                       parser->state = HTTP_RESPONSE_PARSE_STATE_REASON;
-                       if (_parser->cur >= _parser->end)
-                               return 0;
-                       /* fall through */
-               case HTTP_RESPONSE_PARSE_STATE_REASON:
-                       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)
-                               return 0;
-                       /* fall through */
-               case HTTP_RESPONSE_PARSE_STATE_CR:
-                       if (*_parser->cur == '\r')
-                               _parser->cur++;
-                       parser->state = HTTP_RESPONSE_PARSE_STATE_LF;
-                       if (_parser->cur == _parser->end)
-                               return 0;
-                       /* fall through */
-               case HTTP_RESPONSE_PARSE_STATE_LF:
-                       if (*_parser->cur != '\n') {
-                               *error_r = t_strdup_printf
-                                       ("Expected line end after response, but found %s",
-                                               _chr_sanitize(*_parser->cur));
-                               return -1;
-                       }
+       switch (parser->state) {
+       case HTTP_RESPONSE_PARSE_STATE_INIT:
+               http_response_parser_restart(parser);
+               parser->state = HTTP_RESPONSE_PARSE_STATE_VERSION;
+               /* fall through */
+       case HTTP_RESPONSE_PARSE_STATE_VERSION:
+               if ((ret=http_message_parse_version(_parser)) <= 0) {
+                       if (ret < 0)
+                               _parser->error = "Invalid HTTP version in response";
+                       return ret;
+               }
+               parser->state = HTTP_RESPONSE_PARSE_STATE_SP1;
+               if (_parser->cur == _parser->end)
+                       return 0;
+               /* fall through */
+       case HTTP_RESPONSE_PARSE_STATE_SP1:
+               if (*_parser->cur != ' ') {
+                       _parser->error = t_strdup_printf
+                               ("Expected ' ' after response version, but found %s",
+                                       _chr_sanitize(*_parser->cur));
+                       return -1;
+               }
+               _parser->cur++;
+               parser->state = HTTP_RESPONSE_PARSE_STATE_STATUS;
+               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";
+                       return ret;
+               }
+               parser->state = HTTP_RESPONSE_PARSE_STATE_SP2;
+               if (_parser->cur == _parser->end)
+                       return 0;
+               /* fall through */
+       case HTTP_RESPONSE_PARSE_STATE_SP2:
+               if (*_parser->cur != ' ') {
+                       _parser->error = t_strdup_printf
+                               ("Expected ' ' after response status code, but found %s",
+                                       _chr_sanitize(*_parser->cur));
+                       return -1;
+               }
+               _parser->cur++;
+               parser->state = HTTP_RESPONSE_PARSE_STATE_REASON;
+               if (_parser->cur >= _parser->end)
+                       return 0;
+               /* fall through */
+       case HTTP_RESPONSE_PARSE_STATE_REASON:
+               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)
+                       return 0;
+               /* fall through */
+       case HTTP_RESPONSE_PARSE_STATE_CR:
+               if (*_parser->cur == '\r')
                        _parser->cur++;
-                       parser->state = HTTP_RESPONSE_PARSE_STATE_HEADER;
-                       return 1;
-               case HTTP_RESPONSE_PARSE_STATE_HEADER:
-               default:
-                       i_unreached();
+               parser->state = HTTP_RESPONSE_PARSE_STATE_LF;
+               if (_parser->cur == _parser->end)
+                       return 0;
+               /* fall through */
+       case HTTP_RESPONSE_PARSE_STATE_LF:
+               if (*_parser->cur != '\n') {
+                       _parser->error = t_strdup_printf
+                               ("Expected line end after response, but found %s",
+                                       _chr_sanitize(*_parser->cur));
+                       return -1;
                }
+               _parser->cur++;
+               parser->state = HTTP_RESPONSE_PARSE_STATE_HEADER;
+               return 1;
+       case HTTP_RESPONSE_PARSE_STATE_HEADER:
+       default:
+               break;
        }
 
        i_unreached();
        return -1;
 }
 
-static int http_response_parse_status_line(struct http_response_parser *parser,
-                                          const char **error_r)
+static int
+http_response_parse_status_line(struct http_response_parser *parser)
 {
        struct http_message_parser *_parser = &parser->parser;
        const unsigned char *begin;
@@ -212,7 +209,7 @@ static int http_response_parse_status_line(struct http_response_parser *parser,
                _parser->cur = begin;
                _parser->end = _parser->cur + size;
 
-               if ((ret = http_response_parse(parser, error_r)) < 0)
+               if ((ret = http_response_parse(parser)) < 0)
                        return -1;
 
                i_stream_skip(_parser->input, _parser->cur - begin);
@@ -221,12 +218,15 @@ static int http_response_parse_status_line(struct http_response_parser *parser,
                old_bytes = i_stream_get_data_size(_parser->input);
        }
 
-       i_assert(ret != -2);
+       if (ret == -2) {
+               _parser->error = "HTTP status line is too long";
+               return -1;
+       }
        if (ret < 0) {
                if (_parser->input->eof &&
                    parser->state == HTTP_RESPONSE_PARSE_STATE_INIT)
                        return 0;
-               *error_r = "Stream error";
+               _parser->error = "Stream error";
                return -1;
        }
        return 0;
@@ -240,8 +240,10 @@ int http_response_parse_next(struct http_response_parser *parser,
 
        /* make sure we finished streaming payload from previous response
           before we continue. */
-       if ((ret = http_message_parse_finish_payload(&parser->parser, error_r)) <= 0)
+       if ((ret = http_message_parse_finish_payload(&parser->parser)) <= 0) {
+               *error_r = parser->parser.error;
                return ret;
+       }
 
        /* HTTP-message   = start-line
                           *( header-field CRLF )
@@ -249,11 +251,15 @@ int http_response_parse_next(struct http_response_parser *parser,
                            [ message-body ]
         */
        if (parser->state != HTTP_RESPONSE_PARSE_STATE_HEADER) {
-               if ((ret = http_response_parse_status_line(parser, error_r)) <= 0)
+               if ((ret = http_response_parse_status_line(parser)) <= 0) {
+                       *error_r = parser->parser.error;
                        return ret;
+               }
        } 
-       if ((ret = http_message_parse_headers(&parser->parser, error_r)) <= 0)
+       if ((ret = http_message_parse_headers(&parser->parser)) <= 0) {
+               *error_r = parser->parser.error;
                return ret;
+       }
 
        /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21
             Section 3.3.2:
@@ -286,9 +292,12 @@ int http_response_parse_next(struct http_response_parser *parser,
 
        if (!no_payload) {
                /* [ message-body ] */
-               if (http_message_parse_body(&parser->parser, FALSE, error_r) < 0)
-                       return -1;
+               if (http_message_parse_body(&parser->parser, FALSE) < 0) {
+                       *error_r = parser->parser.error;
+                       return -1;
+               }
        }
+
        parser->state = HTTP_RESPONSE_PARSE_STATE_INIT;
 
        memset(response, 0, sizeof(*response));
index ad05ffcc5c65a0c0e79211a54c98d0832f883f06..f49e3f526b417063f8956129986e91d1355ba35b 100644 (file)
@@ -49,11 +49,12 @@ static void client_input(struct connection *conn)
 {
        struct client *client = (struct client *)conn;
        struct http_request request;
+       enum http_request_parse_error error_code;
        const char *error;
        int ret;
 
        while ((ret = http_request_parse_next
-               (client->parser, NULL, &request, &error)) > 0) {
+               (client->parser, NULL, &request, &error_code, &error)) > 0) {
                if (client_handle_request(client, &request) < 0 ||
                    request.connection_close) {
                        client_destroy(conn);