mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
feat(esp_http_client): detect oversized headers in tx buffer while sending request
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
idf_component_register(SRC_DIRS "."
|
||||
PRIV_INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES esp_http_client test_utils unity)
|
||||
"../../lib/include"
|
||||
PRIV_REQUIRES esp_http_client tcp_transport test_utils unity
|
||||
WHOLE_ARCHIVE)
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <esp_system.h>
|
||||
#include <esp_http_client.h>
|
||||
|
||||
#include "unity.h"
|
||||
#include "test_utils.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define HOST "httpbin.org"
|
||||
#define USERNAME "user"
|
||||
@@ -221,6 +223,108 @@ TEST_CASE("esp_http_client_close() and cleanup() should not dispatch duplicate d
|
||||
TEST_ASSERT_LESS_OR_EQUAL(1, disconnect_event_count);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_HTTP_CLIENT_STRICT_HEADER_BUFFER
|
||||
|
||||
#if CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT
|
||||
#include "esp_transport.h"
|
||||
|
||||
/*
|
||||
* Minimal stub transport for the Case B test below. write() always reports
|
||||
* success so the test can isolate the strict-header check from the real
|
||||
* transport's failure path.
|
||||
*/
|
||||
static int stub_transport_connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms) { return 0; }
|
||||
static int stub_transport_write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms) { return len; }
|
||||
static int stub_transport_read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms) { return 0; }
|
||||
static int stub_transport_close(esp_transport_handle_t t) { return 0; }
|
||||
static int stub_transport_destroy(esp_transport_handle_t t) { return 0; }
|
||||
static int stub_transport_poll(esp_transport_handle_t t, int timeout_ms) { return 1; }
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT
|
||||
|
||||
TEST_CASE("esp_http_client_request_send fails when a header exceeds tx buffer", "[ESP HTTP CLIENT]")
|
||||
{
|
||||
/*
|
||||
* The "first header too big" path: with a small buffer_size_tx and an
|
||||
* oversized header, http_header_generate_string() returns 0 and the
|
||||
* helper exits the write loop without ever touching the transport.
|
||||
* The after-loop strict check must surface ESP_ERR_HTTP_HEADER_TOO_LONG
|
||||
* instead of silently completing as ESP_OK.
|
||||
*/
|
||||
esp_http_client_config_t config = {
|
||||
.url = "http://example.com/",
|
||||
.buffer_size_tx = 128,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
TEST_ASSERT_NOT_NULL(client);
|
||||
|
||||
/* Drop the default User-Agent and Host headers so the oversized header
|
||||
* is the very first entry. That keeps us on the Case A path (helper
|
||||
* returns 0 with wlen=0) so the after-loop strict check fires before
|
||||
* esp_transport_write is ever attempted — no network needed. */
|
||||
esp_http_client_set_header(client, "User-Agent", NULL);
|
||||
esp_http_client_set_header(client, "Host", NULL);
|
||||
|
||||
char huge_value[200];
|
||||
memset(huge_value, 'A', sizeof(huge_value) - 1);
|
||||
huge_value[sizeof(huge_value) - 1] = '\0';
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_http_client_set_header(client, "X-Huge", huge_value));
|
||||
|
||||
esp_err_t err = esp_http_client_request_send(client, 0);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_HTTP_HEADER_TOO_LONG, err);
|
||||
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
#if CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT
|
||||
TEST_CASE("esp_http_client_request_send fails when an oversized header is mid-list", "[ESP HTTP CLIENT]")
|
||||
{
|
||||
/*
|
||||
* Case B: the small header at index 0 paginates, the oversized header
|
||||
* at index 1 cannot fit on its own. A stub transport accepts the first
|
||||
* chunk so the loop runs a second iteration, where the strict check
|
||||
* fires. The stub can only succeed, so an ESP_ERR_HTTP_HEADER_TOO_LONG
|
||||
* return here is unambiguously the strict check (not transport failure).
|
||||
*/
|
||||
esp_transport_handle_t stub = esp_transport_init();
|
||||
TEST_ASSERT_NOT_NULL(stub);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_set_func(stub,
|
||||
stub_transport_connect,
|
||||
stub_transport_read,
|
||||
stub_transport_write,
|
||||
stub_transport_close,
|
||||
stub_transport_poll,
|
||||
stub_transport_poll,
|
||||
stub_transport_destroy));
|
||||
|
||||
esp_http_client_config_t config = {
|
||||
.url = "http://example.com/",
|
||||
.buffer_size_tx = 128,
|
||||
.transport = stub,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
TEST_ASSERT_NOT_NULL(client);
|
||||
|
||||
/* Clear defaults and set: one small header (fits), one huge header
|
||||
* (alone too big for the buffer). */
|
||||
esp_http_client_set_header(client, "User-Agent", NULL);
|
||||
esp_http_client_set_header(client, "Host", NULL);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_http_client_set_header(client, "K1", "V1"));
|
||||
|
||||
char huge_value[200];
|
||||
memset(huge_value, 'A', sizeof(huge_value) - 1);
|
||||
huge_value[sizeof(huge_value) - 1] = '\0';
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_http_client_set_header(client, "X-Huge", huge_value));
|
||||
|
||||
esp_err_t err = esp_http_client_request_send(client, 0);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_HTTP_HEADER_TOO_LONG, err);
|
||||
|
||||
esp_http_client_cleanup(client);
|
||||
esp_transport_destroy(stub);
|
||||
}
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT
|
||||
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_STRICT_HEADER_BUFFER
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
unity_run_menu();
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Unit tests for http_header_generate_string() — cover the empty list,
|
||||
* all-headers-fit and pagination paths.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "unity.h"
|
||||
#include "http_header.h"
|
||||
|
||||
TEST_CASE("http_header_generate_string: empty list returns 0 without touching buffer", "[http_header]")
|
||||
{
|
||||
http_header_handle_t hdr = http_header_init();
|
||||
TEST_ASSERT_NOT_NULL(hdr);
|
||||
|
||||
char buf[64] = "untouched";
|
||||
int buf_len = sizeof(buf);
|
||||
int ret = http_header_generate_string(hdr, 0, buf, &buf_len);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, ret);
|
||||
TEST_ASSERT_EQUAL_INT((int)sizeof(buf), buf_len);
|
||||
TEST_ASSERT_EQUAL_STRING("untouched", buf);
|
||||
|
||||
http_header_destroy(hdr);
|
||||
}
|
||||
|
||||
TEST_CASE("http_header_generate_string: all headers fit in one call", "[http_header]")
|
||||
{
|
||||
http_header_handle_t hdr = http_header_init();
|
||||
TEST_ASSERT_NOT_NULL(hdr);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, http_header_set(hdr, "K1", "V1"));
|
||||
TEST_ASSERT_EQUAL(ESP_OK, http_header_set(hdr, "K2", "V2"));
|
||||
|
||||
char buf[64] = {0};
|
||||
int buf_len = sizeof(buf);
|
||||
int ret = http_header_generate_string(hdr, 0, buf, &buf_len);
|
||||
|
||||
const char *expected = "K1: V1\r\nK2: V2\r\n\r\n";
|
||||
TEST_ASSERT_EQUAL_INT(2, ret);
|
||||
TEST_ASSERT_EQUAL_STRING(expected, buf);
|
||||
TEST_ASSERT_EQUAL_INT((int)strlen(expected), buf_len);
|
||||
|
||||
http_header_destroy(hdr);
|
||||
}
|
||||
|
||||
TEST_CASE("http_header_generate_string: pagination across two calls", "[http_header]")
|
||||
{
|
||||
http_header_handle_t hdr = http_header_init();
|
||||
TEST_ASSERT_NOT_NULL(hdr);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, http_header_set(hdr, "K1", "V1"));
|
||||
TEST_ASSERT_EQUAL(ESP_OK, http_header_set(hdr, "K2", "V2"));
|
||||
|
||||
/* 12-byte buffer fits one 8-byte header, but not both headers + terminator. */
|
||||
char buf[12] = {0};
|
||||
int buf_len = sizeof(buf);
|
||||
int ret = http_header_generate_string(hdr, 0, buf, &buf_len);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(1, ret);
|
||||
TEST_ASSERT_EQUAL_STRING("K1: V1\r\n", buf);
|
||||
TEST_ASSERT_EQUAL_INT((int)strlen("K1: V1\r\n"), buf_len);
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
buf_len = sizeof(buf);
|
||||
ret = http_header_generate_string(hdr, 1, buf, &buf_len);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(2, ret);
|
||||
TEST_ASSERT_EQUAL_STRING("K2: V2\r\n\r\n", buf);
|
||||
TEST_ASSERT_EQUAL_INT((int)strlen("K2: V2\r\n\r\n"), buf_len);
|
||||
|
||||
http_header_destroy(hdr);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('config', ['strict_header'], indirect=['config'])
|
||||
@idf_parametrize('target', ['supported_targets'], indirect=['target'])
|
||||
def test_esp_http_client(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_ESP_HTTP_CLIENT_STRICT_HEADER_BUFFER=y
|
||||
CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT=y
|
||||
Reference in New Issue
Block a user