mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
Merge branch 'fix/http_server_chunked_501' into 'master'
Fix(esp_http_server): Fail cleanly on incoming chunked request See merge request espressif/esp-idf!52126
This commit is contained in:
@@ -55,6 +55,8 @@ typedef struct {
|
||||
size_t raw_datalen; /*!< Full length of the raw data in scratch buffer */
|
||||
} parser_data_t;
|
||||
|
||||
static const char *httpd_find_hdr_value(struct httpd_req_aux *ra, const char *field);
|
||||
|
||||
static esp_err_t verify_url (http_parser *parser)
|
||||
{
|
||||
parser_data_t *parser_data = (parser_data_t *) parser->data;
|
||||
@@ -66,12 +68,10 @@ static esp_err_t verify_url (http_parser *parser)
|
||||
const char *at = parser_data->last.at;
|
||||
size_t length = parser_data->last.length;
|
||||
|
||||
/* http_parser stops with HPE_INVALID_METHOD before the URL callback for
|
||||
* any method token it does not know, so the method is always valid here.
|
||||
* That parser error is mapped to 501 in parse_block(). */
|
||||
r->method = parser->method;
|
||||
if (r->method < 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("HTTP method not supported (%d)"), r->method);
|
||||
parser_data->error = HTTPD_501_METHOD_NOT_IMPLEMENTED;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (sizeof(r->uri) < (length + 1)) {
|
||||
ESP_LOGW(TAG, LOG_FMT("URI length (%"NEWLIB_NANO_COMPAT_FORMAT") greater than supported (%"NEWLIB_NANO_COMPAT_FORMAT")"),
|
||||
@@ -372,6 +372,21 @@ static esp_err_t cb_headers_complete(http_parser *parser)
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* This server implements no transfer codings, so reject any request that
|
||||
* carries a Transfer-Encoding header with 501 (RFC 9112 section 6.1).
|
||||
* Without this check a chunked body is treated as an empty body and its
|
||||
* chunk framing is parsed as a separate pipelined request, which is a
|
||||
* request-smuggling primitive when the server sits behind a proxy. The
|
||||
* header-presence check also covers codings that http_parser does not
|
||||
* flag as chunked (e.g. "gzip, chunked"). */
|
||||
if ((parser->flags & F_CHUNKED) ||
|
||||
httpd_find_hdr_value(ra, "Transfer-Encoding") != NULL) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Transfer-Encoding is not supported"));
|
||||
parser_data->error = HTTPD_501_METHOD_NOT_IMPLEMENTED;
|
||||
parser_data->status = PARSING_FAILED;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* In absence of body/chunked encoding, http_parser sets content_len to ULLONG_MAX */
|
||||
if (parser->content_length != ULLONG_MAX) {
|
||||
/* Content-Length was specified. Reject any value above UINT32_MAX: it is
|
||||
@@ -583,6 +598,17 @@ static int read_block(httpd_req_t *req, http_parser *parser, size_t offset, size
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
/* Map an http_parser failure to the HTTP status sent back. An unrecognized
|
||||
* request method is a 501 (RFC 9110 section 15.6.2), every other parser
|
||||
* error means the request syntax is malformed. */
|
||||
static httpd_err_code_t parser_errno_to_err_code(const http_parser *parser)
|
||||
{
|
||||
if (HTTP_PARSER_ERRNO(parser) == HPE_INVALID_METHOD) {
|
||||
return HTTPD_501_METHOD_NOT_IMPLEMENTED;
|
||||
}
|
||||
return HTTPD_400_BAD_REQUEST;
|
||||
}
|
||||
|
||||
static int parse_block(http_parser *parser, size_t offset, size_t length)
|
||||
{
|
||||
parser_data_t *data = (parser_data_t *)(parser->data);
|
||||
@@ -644,14 +670,14 @@ static int parse_block(http_parser *parser, size_t offset, size_t length)
|
||||
return 0;
|
||||
} else if (nparsed != length) {
|
||||
/* http_parser error */
|
||||
data->error = HTTPD_400_BAD_REQUEST;
|
||||
data->error = parser_errno_to_err_code(parser);
|
||||
data->status = PARSING_FAILED;
|
||||
ESP_LOGW(TAG, LOG_FMT("incomplete (%"NEWLIB_NANO_COMPAT_FORMAT"/%"NEWLIB_NANO_COMPAT_FORMAT") with parser error = %d"),
|
||||
NEWLIB_NANO_COMPAT_CAST(nparsed), NEWLIB_NANO_COMPAT_CAST(length), parser->http_errno);
|
||||
return -1;
|
||||
} else if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
|
||||
/* http_parser error */
|
||||
data->error = HTTPD_400_BAD_REQUEST;
|
||||
data->error = parser_errno_to_err_code(parser);
|
||||
data->status = PARSING_FAILED;
|
||||
ESP_LOGE(TAG, LOG_FMT("parser error: %s"), http_errno_description(HTTP_PARSER_ERRNO(parser)));
|
||||
return -1;
|
||||
|
||||
@@ -603,9 +603,16 @@ esp_err_t httpd_req_handle_err(httpd_req_t *req, httpd_err_code_t error)
|
||||
if (hd->err_handler_fns[error]) {
|
||||
ret = hd->err_handler_fns[error](req, error);
|
||||
|
||||
/* If error code is 500, force return failure
|
||||
* irrespective of the handler's return value */
|
||||
ret = (error == HTTPD_500_INTERNAL_SERVER_ERROR ? ESP_FAIL : ret);
|
||||
/* Force failure irrespective of the handler's return value for
|
||||
* errors after which the session cannot continue safely: 500
|
||||
* (internal state is broken) and 501 (parsing aborted mid-request,
|
||||
* so the position of the next request in the byte stream is
|
||||
* unknown and keep-alive would let leftover bytes be parsed and
|
||||
* answered as a further request). */
|
||||
if (error == HTTPD_500_INTERNAL_SERVER_ERROR ||
|
||||
error == HTTPD_501_METHOD_NOT_IMPLEMENTED) {
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
} else {
|
||||
/* If no handler is registered for this error default
|
||||
* behavior is to send the HTTP error response and
|
||||
|
||||
Reference in New Issue
Block a user