summaryrefslogtreecommitdiffstats
path: root/src/lib-http/http-message-parser.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/lib-http/http-message-parser.c
parentInitial commit. (diff)
downloaddovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz
dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-http/http-message-parser.c')
-rw-r--r--src/lib-http/http-message-parser.c658
1 files changed, 658 insertions, 0 deletions
diff --git a/src/lib-http/http-message-parser.c b/src/lib-http/http-message-parser.c
new file mode 100644
index 0000000..a42bddd
--- /dev/null
+++ b/src/lib-http/http-message-parser.c
@@ -0,0 +1,658 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "iostream.h"
+#include "istream-sized.h"
+#include "http-parser.h"
+#include "http-header.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,
+ const struct http_header_limits *hdr_limits,
+ uoff_t max_payload_size,
+ enum http_message_parse_flags flags)
+{
+ i_zero(parser);
+ parser->input = input;
+ i_stream_ref(parser->input);
+ if (hdr_limits != NULL)
+ parser->header_limits = *hdr_limits;
+ parser->max_payload_size = max_payload_size;
+ parser->flags = flags;
+}
+
+void http_message_parser_deinit(struct http_message_parser *parser)
+{
+ if (parser->header_parser != NULL)
+ http_header_parser_deinit(&parser->header_parser);
+ pool_unref(&parser->msg.pool);
+ i_stream_unref(&parser->payload);
+ i_stream_unref(&parser->input);
+}
+
+void http_message_parser_restart(struct http_message_parser *parser,
+ pool_t pool)
+{
+ i_assert(parser->payload == NULL);
+
+ if (parser->header_parser == NULL) {
+ enum http_header_parse_flags hdr_flags = 0;
+
+ if ((parser->flags & HTTP_MESSAGE_PARSE_FLAG_STRICT) != 0)
+ hdr_flags |= HTTP_HEADER_PARSE_FLAG_STRICT;
+ parser->header_parser = http_header_parser_init(
+ parser->input, &parser->header_limits, hdr_flags);
+ } else {
+ http_header_parser_reset(parser->header_parser);
+ }
+
+ pool_unref(&parser->msg.pool);
+ i_zero(&parser->msg);
+ if (pool != NULL) {
+ parser->msg.pool = pool;
+ pool_ref(pool);
+ }
+ parser->msg.date = (time_t)-1;
+}
+
+pool_t http_message_parser_get_pool(struct http_message_parser *parser)
+{
+ if (parser->msg.pool == NULL)
+ parser->msg.pool = pool_alloconly_create("http_message", 4096);
+ return parser->msg.pool;
+}
+
+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;
+
+ /* RFC 7230, Section 2.6: Protocol Versioning
+
+ 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])) {
+ 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;
+}
+
+static void
+http_message_parse_finish_payload_error(struct http_message_parser *parser)
+{
+ if (parser->payload->stream_errno == EMSGSIZE) {
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_PAYLOAD_TOO_LARGE;
+ parser->error = "Payload is too large";
+ } else if (parser->payload->stream_errno == EIO) {
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+ parser->error = t_strdup_printf(
+ "Invalid payload: %s",
+ i_stream_get_error(parser->payload));
+ } else {
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_STREAM;
+ parser->error = t_strdup_printf(
+ "Stream error while skipping payload: %s",
+ i_stream_get_error(parser->payload));
+ }
+}
+
+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_more(parser->payload, &data, &size)) > 0)
+ i_stream_skip(parser->payload, size);
+ if (ret == 0 || parser->payload->stream_errno != 0) {
+ if (ret < 0)
+ http_message_parse_finish_payload_error(parser);
+ return ret;
+ }
+
+ i_stream_destroy(&parser->payload);
+ return 1;
+}
+
+static int
+http_message_parse_hdr_connection(struct http_message_parser *parser,
+ const unsigned char *data, size_t size)
+{
+ pool_t pool = http_message_parser_get_pool(parser);
+ struct http_parser hparser;
+ const char **opt_idx;
+ const char *option;
+ unsigned int num_tokens = 0;
+
+ /* RFC 7230, Section 6.1: Connection
+
+ Connection = 1#connection-option
+ connection-option = token
+ */
+
+ /* Multiple Connection headers are allowed and combined
+ into one */
+ http_parser_init(&hparser, data, size);
+ for (;;) {
+ if (http_parse_token_list_next(&hparser, &option) <= 0)
+ break;
+ num_tokens++;
+ if (strcasecmp(option, "close") == 0)
+ parser->msg.connection_close = TRUE;
+ if (!array_is_created(&parser->msg.connection_options))
+ p_array_init(&parser->msg.connection_options, pool, 4);
+ opt_idx = array_append_space(&parser->msg.connection_options);
+ *opt_idx = p_strdup(pool, option);
+ }
+
+ if (hparser.cur < hparser.end || num_tokens == 0) {
+ parser->error = "Invalid Connection header";
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+http_message_parse_hdr_content_length(struct http_message_parser *parser,
+ const struct http_header_field *hdr)
+{
+ if (parser->msg.have_content_length) {
+ /* There is no acceptable way to allow duplicates for this
+ header. */
+ parser->error = "Duplicate Content-Length header";
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+ return -1;
+ }
+
+ /* RFC 7230, Section 3.3.2: Content-Length
+
+ Content-Length = 1*DIGIT
+ */
+ if (str_to_uoff(hdr->value, &parser->msg.content_length) < 0) {
+ parser->error = "Invalid Content-Length header";
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+ return -1;
+ }
+ parser->msg.have_content_length = TRUE;
+ return 0;
+}
+
+static int
+http_message_parse_hdr_date(struct http_message_parser *parser,
+ const unsigned char *data, size_t size)
+{
+ if (parser->msg.date != (time_t)-1) {
+ if ((parser->flags & HTTP_MESSAGE_PARSE_FLAG_STRICT) != 0) {
+ parser->error = "Duplicate Date header";
+ parser->error_code =
+ HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+ return -1;
+ }
+ /* Allow the duplicate; last instance is used */
+ }
+
+ /* RFC 7231, Section 7.1.1.2: Date
+
+ Date = HTTP-date
+ */
+ if (!http_date_parse(data, size, &parser->msg.date) &&
+ (parser->flags & HTTP_MESSAGE_PARSE_FLAG_STRICT) != 0) {
+ parser->error = "Invalid Date header";
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+ return -1;
+ }
+ return 0;
+}
+
+static int
+http_message_parse_hdr_location(struct http_message_parser *parser,
+ const struct http_header_field *hdr)
+{
+ /* RFC 7231, Section 7.1.2: Location
+
+ Location = URI-reference
+
+ -> not parsed here
+ */
+ /* FIXME: move this to response parser */
+ parser->msg.location = hdr->value;
+ return 0;
+
+}
+
+static int
+http_message_parse_hdr_transfer_encoding(struct http_message_parser *parser,
+ const unsigned char *data, size_t size)
+{
+ pool_t pool = http_message_parser_get_pool(parser);
+ struct http_parser hparser;
+ const char *trenc = NULL;
+
+ /* Multiple Transfer-Encoding headers are allowed and combined into one
+ */
+ if (!array_is_created(&parser->msg.transfer_encoding))
+ p_array_init(&parser->msg.transfer_encoding, pool, 4);
+
+ /* RFC 7230, Section 3.3.1: Transfer-Encoding
+
+ Transfer-Encoding = 1#transfer-coding
+
+ RFC 7230, Section 4: Transfer Codings
+
+ transfer-coding = "chunked" ; RFC 7230, Section 4.1
+ / "compress" ; RFC 7230, Section 4.2.1
+ / "deflate" ; RFC 7230, Section 4.2.2
+ / "gzip" ; RFC 7230, Section 4.2.3
+ / transfer-extension
+ transfer-extension = token *( OWS ";" OWS transfer-parameter )
+ transfer-parameter = token BWS "=" BWS ( token / quoted-string )
+ */
+ http_parser_init(&hparser, data, size);
+ for (;;) {
+ /* transfer-coding */
+ if (http_parse_token(&hparser, &trenc) > 0) {
+ struct http_transfer_coding *coding;
+ bool parse_error;
+
+ coding = array_append_space(
+ &parser->msg.transfer_encoding);
+ coding->name = p_strdup(pool, trenc);
+
+ /* *( OWS ";" OWS transfer-parameter ) */
+ parse_error = FALSE;
+ for (;;) {
+ struct http_transfer_param *param;
+ const char *attribute, *value;
+
+ /* OWS ";" OWS */
+ http_parse_ows(&hparser);
+ if (hparser.cur >= hparser.end ||
+ *hparser.cur != ';')
+ break;
+ hparser.cur++;
+ http_parse_ows(&hparser);
+
+ /* attribute */
+ if (http_parse_token(&hparser,
+ &attribute) <= 0) {
+ parse_error = TRUE;
+ break;
+ }
+
+ /* BWS "=" BWS */
+ http_parse_ows(&hparser);
+ if (hparser.cur >= hparser.end ||
+ *hparser.cur != '=') {
+ parse_error = TRUE;
+ break;
+ }
+ hparser.cur++;
+ http_parse_ows(&hparser);
+
+ /* token / quoted-string */
+ if (http_parse_token_or_qstring(&hparser,
+ &value) <= 0) {
+ parse_error = TRUE;
+ break;
+ }
+
+ if (!array_is_created(&coding->parameters)) {
+ p_array_init(&coding->parameters,
+ pool, 2);
+ }
+ param = array_append_space(&coding->parameters);
+ param->attribute = p_strdup(pool, attribute);
+ param->value = p_strdup(pool, value);
+ }
+ if (parse_error)
+ break;
+
+ } else {
+ /* RFC 7230, Section 7: ABNF List Extension: #rule
+
+ For compatibility with legacy list rules, a recipient
+ MUST parse and ignore a reasonable number of empty
+ list elements: enough to handle common mistakes by
+ senders that merge values, but not so much that they
+ could be used as a denial-of-service mechanism.
+ */
+ // FIXME: limit allowed number of empty list elements
+ // FIXME: handle invalid transfer encoding
+ }
+ http_parse_ows(&hparser);
+ if (hparser.cur >= hparser.end || *hparser.cur != ',')
+ break;
+ hparser.cur++;
+ http_parse_ows(&hparser);
+ }
+
+ if (hparser.cur < hparser.end ||
+ array_count(&parser->msg.transfer_encoding) == 0) {
+ parser->error = "Invalid Transfer-Encoding header";
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+ return -1;
+ }
+ return 0;
+}
+
+static int
+http_message_parse_header(struct http_message_parser *parser,
+ const char *name, const unsigned char *data,
+ size_t size)
+{
+ const struct http_header_field *hdr;
+ pool_t pool;
+
+ pool = http_message_parser_get_pool(parser);
+ if (parser->msg.header == NULL)
+ parser->msg.header = http_header_create(pool, 32);
+ hdr = http_header_field_add(parser->msg.header, name, data, size);
+
+ /* RFC 7230, Section 3.2.2: Field Order
+
+ A sender MUST NOT generate multiple header fields with the same field
+ name in a message unless either the entire field value for that
+ header field is defined as a comma-separated list [i.e., #(values)]
+ or the header field is a well-known exception.
+ */
+
+ switch (name[0]) {
+ case 'C': case 'c':
+ /* Connection: */
+ if (strcasecmp(name, "Connection") == 0) {
+ return http_message_parse_hdr_connection(
+ parser, data, size);
+ }
+ /* Content-Length: */
+ if (strcasecmp(name, "Content-Length") == 0)
+ return http_message_parse_hdr_content_length(
+ parser, hdr);
+ break;
+ case 'D': case 'd':
+ /* Date: */
+ if (strcasecmp(name, "Date") == 0)
+ return http_message_parse_hdr_date(parser, data, size);
+ break;
+ case 'L': case 'l':
+ /* Location: */
+ if (strcasecmp(name, "Location") == 0)
+ return http_message_parse_hdr_location(parser, hdr);
+ break;
+ case 'T': case 't':
+ /* Transfer-Encoding: */
+ if (strcasecmp(name, "Transfer-Encoding") == 0) {
+ return http_message_parse_hdr_transfer_encoding(
+ parser, data, size);
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int http_message_parse_eoh(struct http_message_parser *parser)
+{
+ struct http_message *msg = &parser->msg;
+ pool_t pool;
+
+ /* EOH */
+
+ /* Create empty header if there is none */
+ pool = http_message_parser_get_pool(parser);
+ if (msg->header == NULL)
+ msg->header = http_header_create(pool, 1);
+
+ /* handle HTTP/1.0 persistence */
+ if (msg->version_major == 1 && msg->version_minor == 0 &&
+ !msg->connection_close) {
+ const char *option;
+
+ msg->connection_close = TRUE;
+ if (array_is_created(&msg->connection_options)) {
+ array_foreach_elem(&msg->connection_options, option) {
+ if (strcasecmp(option, "Keep-Alive") == 0) {
+ msg->connection_close = FALSE;
+ break;
+ }
+ }
+ }
+ }
+ return 1;
+}
+
+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) {
+ if (field_name == NULL)
+ return http_message_parse_eoh(parser);
+
+ if (http_message_parse_header(parser,
+ field_name, field_data, field_size) < 0)
+ return -1;
+ }
+
+ if (ret < 0) {
+ 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;
+}
+
+static const char *
+http_istream_error_callback(const struct istream_sized_error_data *data,
+ struct istream *input)
+{
+ i_assert(data->eof);
+ i_assert(data->v_offset + data->new_bytes < data->wanted_size);
+
+ return t_strdup_printf(
+ "Disconnected while reading message payload at offset %"PRIuUOFF_T
+ " (wanted %"PRIuUOFF_T"): %s", data->v_offset + data->new_bytes,
+ data->wanted_size, io_stream_get_disconnect_reason(input, NULL));
+}
+
+static int
+http_message_parse_body_coding(struct http_message_parser *parser,
+ const struct http_transfer_coding *coding,
+ bool *seen_chunked)
+{
+ if (strcasecmp(coding->name, "chunked") == 0) {
+ *seen_chunked = TRUE;
+
+ 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_front(&coding->parameters);
+
+ 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 (*seen_chunked) {
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+ parser->error = "Chunked Transfer-Encoding must be last";
+ return -1;
+ } 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 */
+ }
+ return 0;
+}
+
+static int
+http_message_parse_body_encoded(struct http_message_parser *parser,
+ bool request)
+{
+ const struct http_transfer_coding *coding;
+ bool seen_chunked = FALSE;
+
+ array_foreach(&parser->msg.transfer_encoding, coding) {
+ if (http_message_parse_body_coding(parser, coding,
+ &seen_chunked) < 0)
+ return -1;
+ }
+
+ if (seen_chunked) {
+ parser->payload = http_transfer_chunked_istream_create(
+ parser->input, parser->max_payload_size);
+ } else if (!request) {
+ /* RFC 7230, Section 3.3.3: Message Body Length
+
+ If a Transfer-Encoding header field is present in a response
+ and the chunked transfer coding is not the final encoding,
+ the message body length is determined by reading the
+ connection until it is closed by the server.
+ */
+ /* FIXME: enforce max payload size (relevant to http-client
+ only) */
+ parser->payload =
+ i_stream_create_limit(parser->input, SIZE_MAX);
+ } else {
+ /* RFC 7230, Section 3.3.3: Message Body Length
+
+ If a Transfer-Encoding header field is present in a request
+ and the chunked transfer coding is not the final encoding,
+ the message body length cannot be determined reliably; the
+ server MUST respond with the 400 (Bad Request) status code
+ and then close the connection.
+ */
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE;
+ parser->error =
+ "Final Transfer-Encoding in request is not chunked";
+ return -1;
+ }
+
+ /* RFC 7230, Section 3.3.3: Message Body Length
+
+ If a message is received with both a Transfer-Encoding and a
+ Content-Length header field, the Transfer-Encoding overrides the
+ Content-Length. Such a message might indicate an attempt to perform
+ request smuggling (Section 9.5 of [RFC7230]) or response splitting
+ (Section 9.4 of [RFC7230]) and ought to be handled as an error. A
+ sender MUST remove the received Content-Length field prior to
+ forwarding such a message downstream.
+ */
+ // FIXME: make this an error?
+ if (parser->msg.have_content_length)
+ http_header_field_delete(parser->msg.header, "Content-Length");
+
+ return 0;
+}
+
+static int http_message_parse_body_sized(struct http_message_parser *parser)
+{
+ struct istream *input;
+
+ if (parser->max_payload_size > 0
+ && parser->msg.content_length > parser->max_payload_size) {
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_PAYLOAD_TOO_LARGE;
+ parser->error = "Payload is too large";
+ return -1;
+ }
+
+ /* Got explicit message size from Content-Length: header */
+ input = i_stream_create_limit(parser->input,
+ parser->msg.content_length);
+ /* Make sure we return failure if HTTP connection closes before we've
+ finished reading the full input. */
+ parser->payload = i_stream_create_sized_with_callback(input,
+ parser->msg.content_length,
+ http_istream_error_callback, input);
+ i_stream_unref(&input);
+ return 0;
+}
+
+static int http_message_parse_body_closed(struct http_message_parser *parser)
+{
+ /* RFC 7230, Section 3.3.3: Message Body Length
+
+ 6. If this is a request message and none of the above are true, then
+ the message body length is zero (no message body is present).
+
+ 7. Otherwise, this is a response message without a declared message
+ body length, so the message body length is determined by the
+ number of octets received prior to the server closing the
+ connection
+ */
+ // FIXME: enforce max payload size (relevant to http-client only)
+ // FIXME: handle request case correctly.
+ parser->payload = i_stream_create_limit(parser->input, SIZE_MAX);
+ return 0;
+}
+
+int http_message_parse_body(struct http_message_parser *parser, bool request)
+{
+ i_assert(parser->payload == NULL);
+
+ parser->error_code = HTTP_MESSAGE_PARSE_ERROR_NONE;
+ parser->error = NULL;
+
+ if (array_is_created(&parser->msg.transfer_encoding)) {
+ if (http_message_parse_body_encoded(parser, request) < 0)
+ return -1;
+ } else if (parser->msg.content_length > 0) {
+ if (http_message_parse_body_sized(parser) < 0)
+ return -1;
+ } else if (!parser->msg.have_content_length && !request) {
+ if (http_message_parse_body_closed(parser) < 0)
+ return -1;
+ }
+ if (parser->error_code != HTTP_MESSAGE_PARSE_ERROR_NONE)
+ return -1;
+ return 0;
+}