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;
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;
}
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);
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;
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)
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;