changeset 16749:28e5d58e49dc

lib-http: Adjusted message and request parsers to return an error code.
author Stephan Bosch <stephan@rename-it.nl>
date Sun, 15 Sep 2013 03:50:08 +0300
parents eeaa68773f73
children a335db9dca6a
files 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
diffstat 6 files changed, 277 insertions(+), 157 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-http/http-message-parser.c	Sun Sep 15 03:47:54 2013 +0300
+++ b/src/lib-http/http-message-parser.c	Sun Sep 15 03:50:08 2013 +0300
@@ -62,35 +62,45 @@
 	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 @@
 
 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 @@
 			}
 
 			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 @@
 		/* 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 @@
 	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 @@
 
 			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 @@
 	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 @@
 		}
 
 		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 @@
 			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 @@
 			   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 @@
 		parser->payload =
 			i_stream_create_limit(parser->input, (size_t)-1);
 	}
+	if (parser->error_code != HTTP_MESSAGE_PARSE_ERROR_NONE)
+		return -1;
 	return 0;
 }
--- a/src/lib-http/http-message-parser.h	Sun Sep 15 03:47:54 2013 +0300
+++ b/src/lib-http/http-message-parser.h	Sun Sep 15 03:50:08 2013 +0300
@@ -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 @@
 
 	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_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
--- a/src/lib-http/http-request-parser.c	Sun Sep 15 03:47:54 2013 +0300
+++ b/src/lib-http/http-request-parser.c	Sun Sep 15 03:50:08 2013 +0300
@@ -24,6 +24,8 @@
 	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 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 @@
 			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 @@
 			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 @@
 				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 @@
 			/* 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 @@
 			/* 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_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 @@
 		_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 @@
 		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 @@
 	                    [ 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 @@
 	 */
 	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 @@
 
 	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;
--- a/src/lib-http/http-request-parser.h	Sun Sep 15 03:47:54 2013 +0300
+++ b/src/lib-http/http-request-parser.h	Sun Sep 15 03:50:08 2013 +0300
@@ -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 @@
 
 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
--- a/src/lib-http/http-response-parser.c	Sun Sep 15 03:47:54 2013 +0300
+++ b/src/lib-http/http-response-parser.c	Sun Sep 15 03:50:08 2013 +0300
@@ -102,8 +102,7 @@
 	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 @@
 	   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;
-			}
+	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_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;
-			}
-			_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 @@
 		_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 @@
 		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 @@
 
 	/* 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 @@
 	                    [ 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 @@
 
 	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));
--- a/src/lib-http/test-http-server.c	Sun Sep 15 03:47:54 2013 +0300
+++ b/src/lib-http/test-http-server.c	Sun Sep 15 03:50:08 2013 +0300
@@ -49,11 +49,12 @@
 {
 	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);