From: Stephan Bosch Date: Thu, 27 Feb 2020 00:24:03 +0000 (+0100) Subject: lib-http: Restructure http-message-parser.c. X-Git-Tag: 2.3.11.2~298 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=08f970f5f59fa6610fe2a20323ea5176ee6bbed6;p=thirdparty%2Fdovecot%2Fcore.git lib-http: Restructure http-message-parser.c. Reduce line length. --- diff --git a/src/lib-http/http-message-parser.c b/src/lib-http/http-message-parser.c index bf3df8eab5..e25f27fe8e 100644 --- a/src/lib-http/http-message-parser.c +++ b/src/lib-http/http-message-parser.c @@ -97,6 +97,23 @@ int http_message_parse_version(struct http_message_parser *parser) 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 = "Invalid 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; @@ -112,20 +129,8 @@ int http_message_parse_finish_payload(struct http_message_parser *parser) 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) { - 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 = "Invalid 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)); - } - } + if (ret < 0) + http_message_parse_finish_payload_error(parser); return ret; } @@ -133,13 +138,235 @@ int http_message_parse_finish_payload(struct http_message_parser *parser) 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; - struct http_parser hparser; pool_t pool; pool = http_message_parser_get_pool(parser); @@ -159,214 +386,70 @@ http_message_parse_header(struct http_message_parser *parser, case 'C': case 'c': /* Connection: */ if (strcasecmp(name, "Connection") == 0) { - 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; + return http_message_parse_hdr_connection( + parser, data, size); } /* Content-Length: */ - if (strcasecmp(name, "Content-Length") == 0) { - 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; - } + 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) { - 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; - } + 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) { - /* 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; - } + 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) { - 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 + return http_message_parse_hdr_transfer_encoding( + parser, data, size); + } + break; + default: + break; + } + return 0; +} - Transfer-Encoding = 1#transfer-coding +static int http_message_parse_eoh(struct http_message_parser *parser) +{ + struct http_message *msg = &parser->msg; + pool_t pool; - RFC 7230, Section 4: Transfer Codings + /* EOH */ - 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 != ',') + /* 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 *const *option; + + msg->connection_close = TRUE; + if (array_is_created(&msg->connection_options)) { + array_foreach(&msg->connection_options, option) { + if (strcasecmp(*option, "Keep-Alive") == 0) { + msg->connection_close = FALSE; 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; } - break; - default: - break; } - return 0; + return 1; } int http_message_parse_headers(struct http_message_parser *parser) { const unsigned char *field_data; - struct http_message *msg = &parser->msg; const char *field_name, *error; size_t field_size; int ret; @@ -378,32 +461,8 @@ int http_message_parse_headers(struct http_message_parser *parser) while ((ret = http_header_parse_next_field( parser->header_parser, &field_name, &field_data, &field_size, &error)) > 0) { - if (field_name == NULL) { - pool_t pool; - /* EOH */ - - /* Create empty header if there is none */ - pool = http_message_parser_get_pool(parser); - if (parser->msg.header == NULL) - parser->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 *const *option; - - msg->connection_close = TRUE; - if (array_is_created(&parser->msg.connection_options)) { - array_foreach(&msg->connection_options, option) { - if (strcasecmp(*option, "Keep-Alive") == 0) { - msg->connection_close = FALSE; - break; - } - } - } - } - return 1; - } + if (field_name == NULL) + return http_message_parse_eoh(parser); if (http_message_parse_header(parser, field_name, field_data, field_size) < 0) @@ -439,122 +498,157 @@ http_istream_error_callback(const struct istream_sized_error_data *data, data->wanted_size, io_stream_get_disconnect_reason(input, NULL)); } -int http_message_parse_body(struct http_message_parser *parser, bool request) +static int +http_message_parse_body_coding(struct http_message_parser *parser, + const struct http_transfer_coding *coding, + bool *seen_chunked) { - struct istream *input; + if (strcasecmp(coding->name, "chunked") == 0) { + *seen_chunked = TRUE; - i_assert(parser->payload == NULL); + 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_NONE; - parser->error = NULL; - - if (array_is_created(&parser->msg.transfer_encoding)) { - const struct http_transfer_coding *coding; - - bool chunked_last = FALSE; - - array_foreach(&parser->msg.transfer_encoding, coding) { - if (strcasecmp(coding->name, "chunked") == 0) { - chunked_last = 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 (chunked_last) { - 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 */ - } + 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; +} - if (chunked_last) { - 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_t)-1); - } else { - /* RFC 7230, Section 3.3.3: Message Body Length +static int +http_message_parse_body_encoded(struct http_message_parser *parser, + bool request) +{ + const struct http_transfer_coding *coding; + bool seen_chunked = FALSE; - 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"; + 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 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. + 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: make this an error? - if (parser->msg.have_content_length) - http_header_field_delete(parser->msg.header, "Content-Length"); + /* FIXME: enforce max payload size (relevant to http-client + only) */ + parser->payload = + i_stream_create_limit(parser->input, (size_t)-1); + } else { + /* RFC 7230, Section 3.3.3: Message Body Length - } else if (parser->msg.content_length > 0) { - 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; - } + 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; + } - /* 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); - } else if (!parser->msg.have_content_length && !request) { - /* RFC 7230, Section 3.3.3: Message Body Length + /* 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). + 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"); - 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_t)-1); + 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_t)-1); + 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;