From 684790c2aba71738fb0cbbb326bb1c00d8849dca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 11 Jan 2026 17:17:57 -1000 Subject: [PATCH] [web_server_idf] Reduce heap usage in DefaultHeaders and auth (#13141) --- .../components/web_server_base/__init__.py | 2 ++ .../web_server_base/web_server_base.h | 2 ++ .../web_server_idf/web_server_idf.cpp | 32 +++++++++++++------ .../web_server_idf/web_server_idf.h | 11 +++++-- esphome/core/defines.h | 1 + 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 4cf76eba0e..d5d75b395d 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -34,6 +34,8 @@ async def to_code(config): cg.add(cg.RawExpression(f"{web_server_base_ns}::global_web_server_base = {var}")) if CORE.is_esp32: + # Count for StaticVector in web_server_idf - matches headers added in init() + cg.add_define("WEB_SERVER_DEFAULT_HEADERS_COUNT", 1) return if CORE.using_arduino: diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 7e95e00f29..0c25467f1b 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -100,6 +100,8 @@ class WebServerBase : public Component { } this->server_ = std::make_unique(this->port_); // All content is controlled and created by user - so allowing all origins is fine here. + // NOTE: Currently 1 header. If more are added, update in __init__.py: + // cg.add_define("WEB_SERVER_DEFAULT_HEADERS_COUNT", 1) DefaultHeaders::Instance().addHeader(ESPHOME_F("Access-Control-Allow-Origin"), ESPHOME_F("*")); this->server_->begin(); diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 5062aa1e6c..55d2040a3a 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -309,8 +309,8 @@ void AsyncWebServerRequest::init_response_(AsyncWebServerResponse *rsp, int code } httpd_resp_set_hdr(*this, "Accept-Ranges", "none"); - for (const auto &pair : DefaultHeaders::Instance().headers_) { - httpd_resp_set_hdr(*this, pair.first.c_str(), pair.second.c_str()); + for (const auto &header : DefaultHeaders::Instance().headers_) { + httpd_resp_set_hdr(*this, header.name, header.value); } delete this->rsp_; @@ -335,17 +335,29 @@ bool AsyncWebServerRequest::authenticate(const char *username, const char *passw return false; } - std::string user_info; - user_info += username; - user_info += ':'; - user_info += password; + // Build user:pass in stack buffer to avoid heap allocation + constexpr size_t max_user_info_len = 256; + char user_info[max_user_info_len]; + size_t user_len = strlen(username); + size_t pass_len = strlen(password); + size_t user_info_len = user_len + 1 + pass_len; + + if (user_info_len >= max_user_info_len) { + ESP_LOGW(TAG, "Credentials too long for authentication"); + return false; + } + + memcpy(user_info, username, user_len); + user_info[user_len] = ':'; + memcpy(user_info + user_len + 1, password, pass_len); + user_info[user_info_len] = '\0'; size_t n = 0, out; - esp_crypto_base64_encode(nullptr, 0, &n, reinterpret_cast(user_info.c_str()), user_info.size()); + esp_crypto_base64_encode(nullptr, 0, &n, reinterpret_cast(user_info), user_info_len); auto digest = std::unique_ptr(new char[n + 1]); esp_crypto_base64_encode(reinterpret_cast(digest.get()), n, &out, - reinterpret_cast(user_info.c_str()), user_info.size()); + reinterpret_cast(user_info), user_info_len); return strcmp(digest.get(), auth_str + auth_prefix_len) == 0; } @@ -483,8 +495,8 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest * httpd_resp_set_hdr(req, "Cache-Control", "no-cache"); httpd_resp_set_hdr(req, "Connection", "keep-alive"); - for (const auto &pair : DefaultHeaders::Instance().headers_) { - httpd_resp_set_hdr(req, pair.first.c_str(), pair.second.c_str()); + for (const auto &header : DefaultHeaders::Instance().headers_) { + httpd_resp_set_hdr(req, header.name, header.value); } httpd_resp_send_chunk(req, CRLF_STR, CRLF_LEN); diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index cae7006d96..2a334a11e3 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -2,6 +2,7 @@ #ifdef USE_ESP32 #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include #include @@ -327,6 +328,11 @@ class AsyncEventSource : public AsyncWebHandler { }; #endif // USE_WEBSERVER +struct HttpHeader { + const char *name; + const char *value; +}; + class DefaultHeaders { friend class AsyncWebServerRequest; #ifdef USE_WEBSERVER @@ -335,13 +341,14 @@ class DefaultHeaders { public: // NOLINTNEXTLINE(readability-identifier-naming) - void addHeader(const char *name, const char *value) { this->headers_.emplace_back(name, value); } + void addHeader(const char *name, const char *value) { this->headers_.push_back({name, value}); } // NOLINTNEXTLINE(readability-identifier-naming) static DefaultHeaders &Instance(); protected: - std::vector> headers_; + // Stack-allocated, no reallocation machinery. Count defined in web_server_base where headers are added. + StaticVector headers_; }; } // namespace web_server_idf diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ae94f6ef5f..adb2921b68 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -213,6 +213,7 @@ #define USE_WEBSERVER_PORT 80 // NOLINT #define USE_WEBSERVER_GZIP #define USE_WEBSERVER_SORTING +#define WEB_SERVER_DEFAULT_HEADERS_COUNT 1 #define USE_CAPTIVE_PORTAL_GZIP #define USE_WIFI_11KV_SUPPORT #define USE_WIFI_FAST_CONNECT