From b37cb812a71a70082a40018a9ad50a56ae6dbd4f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 15 Jan 2026 18:03:11 -1000 Subject: [PATCH] [core] Add buf_append_printf helper for safe buffer formatting (#13258) --- esphome/core/helpers.h | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index d5a04b7eb1..9dc289c743 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1,8 +1,11 @@ #pragma once +#include #include #include +#include #include +#include #include #include #include @@ -18,6 +21,7 @@ #ifdef USE_ESP8266 #include +#include #endif #ifdef USE_RP2040 @@ -568,6 +572,53 @@ std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, /// sprintf-like function returning std::string. std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); +#ifdef USE_ESP8266 +// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM) +// Format strings must be wrapped with PSTR() macro +/// Safely append formatted string to buffer, returning new position (capped at size). +/// @param buf Output buffer +/// @param size Total buffer size +/// @param pos Current position in buffer +/// @param fmt Format string (must be in PROGMEM on ESP8266) +/// @return New position after appending (capped at size on overflow) +inline size_t buf_append_printf_p(char *buf, size_t size, size_t pos, PGM_P fmt, ...) { + if (pos >= size) { + return size; + } + va_list args; + va_start(args, fmt); + int written = vsnprintf_P(buf + pos, size - pos, fmt, args); + va_end(args); + if (written < 0) { + return pos; // encoding error + } + return std::min(pos + static_cast(written), size); +} +#define buf_append_printf(buf, size, pos, fmt, ...) buf_append_printf_p(buf, size, pos, PSTR(fmt), ##__VA_ARGS__) +#else +/// Safely append formatted string to buffer, returning new position (capped at size). +/// Handles snprintf edge cases: negative returns (encoding errors) and truncation. +/// @param buf Output buffer +/// @param size Total buffer size +/// @param pos Current position in buffer +/// @param fmt printf-style format string +/// @return New position after appending (capped at size on overflow) +__attribute__((format(printf, 4, 5))) inline size_t buf_append_printf(char *buf, size_t size, size_t pos, + const char *fmt, ...) { + if (pos >= size) { + return size; + } + va_list args; + va_start(args, fmt); + int written = vsnprintf(buf + pos, size - pos, fmt, args); + va_end(args); + if (written < 0) { + return pos; // encoding error + } + return std::min(pos + static_cast(written), size); +} +#endif + /// Concatenate a name with a separator and suffix using an efficient stack-based approach. /// This avoids multiple heap allocations during string construction. /// Maximum name length supported is 120 characters for friendly names.