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