Skip to content

Commit e3e49d7

Browse files
committed
Merge branch 'fix/make_scratch_buffer_dynamic' into 'master'
fix(esp_http_server): Make the http server scratch buffer dynamic Closes IDF-11879 See merge request espressif/esp-idf!36651
2 parents 90bad97 + 9846584 commit e3e49d7

File tree

17 files changed

+129
-55
lines changed

17 files changed

+129
-55
lines changed

components/esp_http_server/Kconfig

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
menu "HTTP Server"
22

3+
34
config HTTPD_MAX_REQ_HDR_LEN
4-
int "Max HTTP Request Header Length"
5-
default 512
5+
int "HTTP Request Header Length limit"
6+
default 1024
7+
range 128 65536
68
help
7-
This sets the maximum supported size of headers section in HTTP request packet to be processed by the
8-
server
9+
This sets the default limit for the HTTP request header length. The limit can be
10+
configured at run time by setting max_req_hdr_len member of httpd_config_t structure.
11+
The memory allocated will depend on the actual header length. Hence keeping a sufficiently
12+
large max header length is recommended.
13+
914

1015
config HTTPD_MAX_URI_LEN
1116
int "Max HTTP URI Length"

components/esp_http_server/include/esp_http_server.h

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -55,6 +55,8 @@ initializer that should be kept in sync
5555
.stack_size = 4096, \
5656
.core_id = tskNO_AFFINITY, \
5757
.task_caps = (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT), \
58+
.max_req_hdr_len = CONFIG_HTTPD_MAX_REQ_HDR_LEN, \
59+
.max_uri_len = CONFIG_HTTPD_MAX_URI_LEN, \
5860
.server_port = 80, \
5961
.ctrl_port = ESP_HTTPD_DEF_CTRL_PORT, \
6062
.max_open_sockets = 7, \
@@ -173,6 +175,13 @@ typedef struct httpd_config {
173175
BaseType_t core_id; /*!< The core the HTTP server task will run on */
174176
uint32_t task_caps; /*!< The memory capabilities to use when allocating the HTTP server task's stack */
175177

178+
/**
179+
* Size limits for the header and URI buffers respectively.
180+
* These are just limits, allocation would depend upon actual size of URI/header.
181+
*/
182+
size_t max_req_hdr_len; /*!< Size limit for the header buffer (By default this value is set to CONFIG_HTTPD_MAX_REQ_HDR_LEN, overwrite is possible) */
183+
size_t max_uri_len; /*!< Size limit for the URI buffer By default this value is set to CONFIG_HTTPD_MAX_URI_LEN, overwrite is possible) */
184+
176185
/**
177186
* TCP Port number for receiving and transmitting HTTP traffic
178187
*/
@@ -358,19 +367,13 @@ esp_err_t httpd_stop(httpd_handle_t handle);
358367
* @{
359368
*/
360369

361-
/* Max supported HTTP request header length */
362-
#define HTTPD_MAX_REQ_HDR_LEN CONFIG_HTTPD_MAX_REQ_HDR_LEN
363-
364-
/* Max supported HTTP request URI length */
365-
#define HTTPD_MAX_URI_LEN CONFIG_HTTPD_MAX_URI_LEN
366-
367370
/**
368371
* @brief HTTP Request Data Structure
369372
*/
370373
typedef struct httpd_req {
371374
httpd_handle_t handle; /*!< Handle to server instance */
372375
int method; /*!< The type of HTTP request, -1 if unsupported method, HTTP_ANY for wildcard method to support every method */
373-
const char uri[HTTPD_MAX_URI_LEN + 1]; /*!< The URI of this request (1 byte extra for null termination) */
376+
const char uri[CONFIG_HTTPD_MAX_URI_LEN + 1]; /*!< The URI of this request (1 byte extra for null termination) */
374377
size_t content_len; /*!< Length of the request body */
375378
void *aux; /*!< Internally used members */
376379

@@ -611,10 +614,10 @@ typedef enum {
611614
/* Incoming payload is too large */
612615
HTTPD_413_CONTENT_TOO_LARGE,
613616

614-
/* URI length greater than CONFIG_HTTPD_MAX_URI_LEN */
617+
/* URI length greater than CONFIG_CONFIG_HTTPD_MAX_URI_LEN */
615618
HTTPD_414_URI_TOO_LONG,
616619

617-
/* Headers section larger than CONFIG_HTTPD_MAX_REQ_HDR_LEN */
620+
/* Headers section larger than CONFIG_CONFIG_HTTPD_MAX_REQ_HDR_LEN */
618621
HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE,
619622

620623
/* Used internally for retrieving the total count of errors */

components/esp_http_server/src/esp_httpd_priv.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -31,13 +31,9 @@ extern "C" {
3131
#endif
3232

3333
/* Size of request data block/chunk (not to be confused with chunked encoded data)
34-
* that is received and parsed in one turn of the parsing process. This should not
35-
* exceed the scratch buffer size and should at least be 8 bytes */
34+
* that is received and parsed in one turn of the parsing process. */
3635
#define PARSER_BLOCK_SIZE 128
3736

38-
/* Calculate the maximum size needed for the scratch buffer */
39-
#define HTTPD_SCRATCH_BUF MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN)
40-
4137
/* Formats a log string to prepend context function name */
4238
#define LOG_FMT(x) "%s: " x, __func__
4339

@@ -88,7 +84,11 @@ struct sock_db {
8884
*/
8985
struct httpd_req_aux {
9086
struct sock_db *sd; /*!< Pointer to socket database */
91-
char scratch[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */
87+
char *scratch; /*!< Temporary buffer for our operations (1 byte extra for null termination) */
88+
size_t scratch_size_limit; /*!< Scratch buffer size limit (By default this value is set to CONFIG_HTTPD_MAX_REQ_HDR_LEN, overwrite is possible) */
89+
size_t scratch_cur_size; /*!< Scratch buffer cur size (By default this value is set to CONFIG_HTTPD_MAX_URI_LEN, overwrite is possible) */
90+
size_t max_req_hdr_len; /*!< Header buffer size limit */
91+
size_t max_uri_len; /*!< URI buffer size limit */
9292
size_t remaining_len; /*!< Amount of data remaining to be fetched */
9393
char *status; /*!< HTTP response's status code */
9494
char *content_type; /*!< HTTP response's content type */

components/esp_http_server/src/httpd_parse.c

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ static esp_err_t cb_url(http_parser *parser,
112112
const char *at, size_t length)
113113
{
114114
parser_data_t *parser_data = (parser_data_t *) parser->data;
115+
httpd_req_t *req = parser_data->req;
116+
struct httpd_req_aux *raux = req->aux;
115117

116118
if (parser_data->status == PARSING_IDLE) {
117119
ESP_LOGD(TAG, LOG_FMT("message begin"));
@@ -130,9 +132,9 @@ static esp_err_t cb_url(http_parser *parser,
130132
ESP_LOGD(TAG, LOG_FMT("processing url = %.*s"), (int)length, at);
131133

132134
/* Update length of URL string */
133-
if ((parser_data->last.length += length) > HTTPD_MAX_URI_LEN) {
135+
if ((parser_data->last.length += length) > raux->max_uri_len) {
134136
ESP_LOGW(TAG, LOG_FMT("URI length (%"NEWLIB_NANO_COMPAT_FORMAT") greater than supported (%d)"),
135-
NEWLIB_NANO_COMPAT_CAST(parser_data->last.length), HTTPD_MAX_URI_LEN);
137+
NEWLIB_NANO_COMPAT_CAST(parser_data->last.length), raux->max_uri_len);
136138
parser_data->error = HTTPD_414_URI_TOO_LONG;
137139
parser_data->status = PARSING_FAILED;
138140
return ESP_FAIL;
@@ -215,6 +217,7 @@ static esp_err_t cb_header_field(http_parser *parser, const char *at, size_t len
215217
parser_data->last.at = ra->scratch;
216218
parser_data->last.length = 0;
217219
parser_data->status = PARSING_HDR_FIELD;
220+
ra->scratch_size_limit = ra->max_req_hdr_len;
218221

219222
/* Stop parsing for now and give control to process */
220223
if (pause_parsing(parser, at) != ESP_OK) {
@@ -232,6 +235,7 @@ static esp_err_t cb_header_field(http_parser *parser, const char *at, size_t len
232235
parser_data->last.at = at;
233236
parser_data->last.length = 0;
234237
parser_data->status = PARSING_HDR_FIELD;
238+
ra->scratch_size_limit = ra->max_req_hdr_len;
235239

236240
/* Increment header count */
237241
ra->req_hdrs_count++;
@@ -480,16 +484,32 @@ static esp_err_t cb_no_body(http_parser *parser)
480484
return ESP_OK;
481485
}
482486

483-
static int read_block(httpd_req_t *req, size_t offset, size_t length)
487+
static int read_block(httpd_req_t *req, http_parser *parser, size_t offset, size_t length)
484488
{
485489
struct httpd_req_aux *raux = req->aux;
490+
parser_data_t *parser_data = (parser_data_t *) parser->data;
486491

487492
/* Limits the read to scratch buffer size */
488-
ssize_t buf_len = MIN(length, (sizeof(raux->scratch) - offset));
493+
ssize_t buf_len = MIN(length, (raux->scratch_size_limit - offset));
489494
if (buf_len <= 0) {
490495
return 0;
491496
}
492-
497+
/* Calculate the offset of the current position from the start of the buffer,
498+
* as after reallocating the buffer, the base address of the buffer may change.
499+
*/
500+
size_t at_offset = parser_data->last.at - raux->scratch;
501+
/* Allocate the buffer according to offset and buf_len. Offset is
502+
from where the reading will start and buf_len is till what length
503+
the buffer will be read.
504+
*/
505+
raux->scratch = (char*) realloc(raux->scratch, offset + buf_len);
506+
if (raux->scratch == NULL) {
507+
ESP_LOGE(TAG, "Unable to allocate the scratch buffer");
508+
return 0;
509+
}
510+
parser_data->last.at = raux->scratch + at_offset;
511+
raux->scratch_cur_size = offset + buf_len;
512+
ESP_LOGD(TAG, "scratch buf qsize = %d", raux->scratch_cur_size);
493513
/* Receive data into buffer. If data is pending (from unrecv) then return
494514
* immediately after receiving pending data, as pending data may just complete
495515
* this request packet. */
@@ -533,13 +553,14 @@ static int parse_block(http_parser *parser, size_t offset, size_t length)
533553
* parse means no more space left on buffer,
534554
* therefore it can be inferred that the
535555
* request URI/header must be too long */
536-
ESP_LOGW(TAG, LOG_FMT("request URI/header too long"));
537556
switch (data->status) {
538557
case PARSING_URL:
558+
ESP_LOGW(TAG, LOG_FMT("request URI too long"));
539559
data->error = HTTPD_414_URI_TOO_LONG;
540560
break;
541561
case PARSING_HDR_FIELD:
542562
case PARSING_HDR_VALUE:
563+
ESP_LOGW(TAG, LOG_FMT("request header too long"));
543564
data->error = HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE;
544565
break;
545566
default:
@@ -632,7 +653,7 @@ static esp_err_t httpd_parse_req(struct httpd_data *hd)
632653
offset = 0;
633654
do {
634655
/* Read block into scratch buffer */
635-
if ((blk_len = read_block(r, offset, PARSER_BLOCK_SIZE)) < 0) {
656+
if ((blk_len = read_block(r, &parser, offset, PARSER_BLOCK_SIZE)) < 0) {
636657
if (blk_len == HTTPD_SOCK_ERR_TIMEOUT) {
637658
/* Retry read in case of non-fatal timeout error.
638659
* read_block() ensures that the timeout error is
@@ -679,13 +700,17 @@ static void init_req(httpd_req_t *r, httpd_config_t *config)
679700
static void init_req_aux(struct httpd_req_aux *ra, httpd_config_t *config)
680701
{
681702
ra->sd = 0;
682-
memset(ra->scratch, 0, sizeof(ra->scratch));
683703
ra->remaining_len = 0;
684704
ra->status = 0;
685705
ra->content_type = 0;
686706
ra->first_chunk_sent = 0;
687707
ra->req_hdrs_count = 0;
688708
ra->resp_hdrs_count = 0;
709+
ra->scratch = NULL;
710+
ra->scratch_cur_size = 0;
711+
ra->max_req_hdr_len = (config->max_req_hdr_len > 0) ? config->max_req_hdr_len : CONFIG_HTTPD_MAX_REQ_HDR_LEN;
712+
ra->max_uri_len = (config->max_uri_len > 0) ? config->max_uri_len : CONFIG_HTTPD_MAX_URI_LEN;
713+
ra->scratch_size_limit = ra->max_uri_len;
689714
#if CONFIG_HTTPD_WS_SUPPORT
690715
ra->ws_handshake_detect = false;
691716
#endif
@@ -716,6 +741,10 @@ static void httpd_req_cleanup(httpd_req_t *r)
716741

717742
/* Clear out the request and request_aux structures */
718743
ra->sd = NULL;
744+
free(ra->scratch);
745+
ra->scratch = NULL;
746+
ra->scratch_size_limit = 0;
747+
ra->scratch_cur_size = 0;
719748
r->handle = NULL;
720749
r->aux = NULL;
721750
r->user_ctx = NULL;

components/esp_http_server/src/httpd_txrx.c

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -247,14 +247,26 @@ esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len)
247247
/* Request headers are no longer available */
248248
ra->req_hdrs_count = 0;
249249

250-
/* Size of essential headers is limited by scratch buffer size */
251-
if (snprintf(ra->scratch, sizeof(ra->scratch), httpd_hdr_str,
252-
ra->status, ra->content_type, buf_len) >= sizeof(ra->scratch)) {
250+
/* Calculate the size of the headers. +1 for the null terminator */
251+
size_t required_size = snprintf(NULL, 0, httpd_hdr_str, ra->status, ra->content_type, buf_len) + 1;
252+
if (required_size > ra->max_req_hdr_len) {
253253
return ESP_ERR_HTTPD_RESP_HDR;
254254
}
255+
char *res_buf = malloc(required_size); /* Temporary buffer to store the headers */
256+
if (res_buf == NULL) {
257+
ESP_LOGE(TAG, "Unable to allocate httpd send buffer");
258+
return ESP_ERR_HTTPD_ALLOC_MEM;
259+
}
260+
ESP_LOGD(TAG, "httpd send buffer size = %d", strlen(res_buf));
255261

256-
/* Sending essential headers */
257-
if (httpd_send_all(r, ra->scratch, strlen(ra->scratch)) != ESP_OK) {
262+
esp_err_t ret = snprintf(res_buf, required_size, httpd_hdr_str, ra->status, ra->content_type, buf_len);
263+
if (ret < 0 || ret >= required_size) {
264+
free(res_buf);
265+
return ESP_ERR_HTTPD_RESP_HDR;
266+
}
267+
ret = httpd_send_all(r, res_buf, strlen(res_buf));
268+
free(res_buf);
269+
if (ret != ESP_OK) {
258270
return ESP_ERR_HTTPD_RESP_SEND;
259271
}
260272

@@ -320,15 +332,27 @@ esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, ssize_t buf_len
320332
/* Request headers are no longer available */
321333
ra->req_hdrs_count = 0;
322334

335+
/* Calculate the size of the headers. +1 for the null terminator */
336+
size_t required_size = snprintf(NULL, 0, httpd_chunked_hdr_str, ra->status, ra->content_type) + 1;
337+
if (required_size > ra->max_req_hdr_len) {
338+
return ESP_ERR_HTTPD_RESP_HDR;
339+
}
340+
char *res_buf = malloc(required_size); /* Temporary buffer to store the headers */
341+
if (res_buf == NULL) {
342+
ESP_LOGE(TAG, "Unable to allocate httpd send chunk buffer");
343+
return ESP_ERR_HTTPD_ALLOC_MEM;
344+
}
345+
ESP_LOGD(TAG, "httpd send chunk buffer size = %d", strlen(res_buf));
323346
if (!ra->first_chunk_sent) {
324-
/* Size of essential headers is limited by scratch buffer size */
325-
if (snprintf(ra->scratch, sizeof(ra->scratch), httpd_chunked_hdr_str,
326-
ra->status, ra->content_type) >= sizeof(ra->scratch)) {
347+
esp_err_t ret = snprintf(res_buf, required_size, httpd_chunked_hdr_str, ra->status, ra->content_type);
348+
if (ret < 0 || ret >= required_size) {
349+
free(res_buf);
327350
return ESP_ERR_HTTPD_RESP_HDR;
328351
}
329-
330-
/* Sending essential headers */
331-
if (httpd_send_all(r, ra->scratch, strlen(ra->scratch)) != ESP_OK) {
352+
/* Size of essential headers is limited by scratch buffer size */
353+
ret = httpd_send_all(r, res_buf, strlen(res_buf));
354+
free(res_buf);
355+
if (ret != ESP_OK) {
332356
return ESP_ERR_HTTPD_RESP_SEND;
333357
}
334358

@@ -650,7 +674,10 @@ esp_err_t httpd_req_async_handler_complete(httpd_req_t *r)
650674

651675
struct httpd_req_aux *ra = r->aux;
652676
ra->sd->for_async_req = false;
653-
677+
free(ra->scratch);
678+
ra->scratch = NULL;
679+
ra->scratch_cur_size = 0;
680+
ra->scratch_size_limit = 0;
654681
free(ra->resp_hdrs);
655682
free(r->aux);
656683
free(r);

components/esp_https_server/include/esp_https_server.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ typedef struct httpd_ssl_config httpd_ssl_config_t;
150150
.stack_size = 10240, \
151151
.core_id = tskNO_AFFINITY, \
152152
.task_caps = (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT), \
153+
.max_req_hdr_len = CONFIG_HTTPD_MAX_REQ_HDR_LEN, \
154+
.max_uri_len = CONFIG_HTTPD_MAX_URI_LEN, \
153155
.server_port = 0, \
154156
.ctrl_port = ESP_HTTPD_DEF_CTRL_PORT+1, \
155157
.max_open_sockets = 4, \

docs/en/migration-guides/release-5.x/5.5/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Migration from 5.4 to 5.5
88

99
system
1010
peripherals
11+
protocols
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Protocols
2+
=========
3+
4+
:link_to_translation:`zh_CN:[中文]`
5+
6+
ESP HTTP SERVER
7+
---------------
8+
9+
:ref:`CONFIG_HTTPD_MAX_REQ_HDR_LEN`
10+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11+
12+
The :ref:`CONFIG_HTTPD_MAX_REQ_HDR_LEN` option now defines the maximum limit for the memory that can be allocated internally for the HTTP request header. The actual memory allocated for the header will depend on the size of the header received in the HTTP request, rather than being fixed to this value which was the case previously. This provides more flexible memory usage based on the actual header size.

docs/zh_CN/migration-guides/release-5.x/5.5/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88

99
system
1010
peripherals
11+
protocols
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
协议
2+
====
3+
4+
:link_to_translation:`en:[English]`

0 commit comments

Comments
 (0)