diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 0ab5b238b5..7d4050284f 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -635,7 +635,8 @@ void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; } void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix, const std::string &check_topic_prefix) { if (App.is_name_add_mac_suffix_enabled() && (topic_prefix == check_topic_prefix)) { - this->topic_prefix_ = str_sanitize(App.get_name()); + char buf[ESPHOME_DEVICE_NAME_MAX_LEN + 1]; + this->topic_prefix_ = str_sanitize_to(buf, App.get_name().c_str()); } else { this->topic_prefix_ = topic_prefix; } diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 8e4b3437ab..3b9290259b 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -48,7 +48,8 @@ void MQTTComponent::set_subscribe_qos(uint8_t qos) { this->subscribe_qos_ = qos; void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; } std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const { - std::string sanitized_name = str_sanitize(App.get_name()); + char sanitized_name[ESPHOME_DEVICE_NAME_MAX_LEN + 1]; + str_sanitize_to(sanitized_name, App.get_name().c_str()); const char *comp_type = this->component_type(); char object_id_buf[OBJECT_ID_MAX_LEN]; StringRef object_id = this->get_default_object_id_to_(object_id_buf); @@ -60,7 +61,7 @@ std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discove p = append_char(p, '/'); p = append_str(p, comp_type, strlen(comp_type)); p = append_char(p, '/'); - p = append_str(p, sanitized_name.data(), sanitized_name.size()); + p = append_str(p, sanitized_name, strlen(sanitized_name)); p = append_char(p, '/'); p = append_str(p, object_id.c_str(), object_id.size()); p = append_str(p, "/config", 7); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 44c20845e8..e7b901d71f 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -206,11 +206,22 @@ std::string str_snake_case(const std::string &str) { } return result; } -std::string str_sanitize(const std::string &str) { - std::string result = str; - for (char &c : result) { - c = to_sanitized_char(c); +char *str_sanitize_to(char *buffer, size_t buffer_size, const char *str) { + if (buffer_size == 0) { + return buffer; } + size_t i = 0; + while (*str && i < buffer_size - 1) { + buffer[i++] = to_sanitized_char(*str++); + } + buffer[i] = '\0'; + return buffer; +} + +std::string str_sanitize(const std::string &str) { + std::string result; + result.resize(str.size()); + str_sanitize_to(&result[0], str.size() + 1, str.c_str()); return result; } std::string str_snprintf(const char *fmt, size_t len, ...) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 4185596c7b..bd3a4def05 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -609,7 +609,25 @@ std::string str_snake_case(const std::string &str); constexpr char to_sanitized_char(char c) { return (c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ? c : '_'; } + +/** Sanitize a string to buffer, keeping only alphanumerics, dashes, and underscores. + * + * @param buffer Output buffer to write to. + * @param buffer_size Size of the output buffer. + * @param str Input string to sanitize. + * @return Pointer to buffer. + * + * Buffer size needed: strlen(str) + 1. + */ +char *str_sanitize_to(char *buffer, size_t buffer_size, const char *str); + +/// Sanitize a string to buffer. Automatically deduces buffer size. +template inline char *str_sanitize_to(char (&buffer)[N], const char *str) { + return str_sanitize_to(buffer, N, str); +} + /// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores. +/// @warning Allocates heap memory. Use str_sanitize_to() with a stack buffer instead. std::string str_sanitize(const std::string &str); /// Calculate FNV-1 hash of a string while applying snake_case + sanitize transformations. diff --git a/script/ci-custom.py b/script/ci-custom.py index e227ec873e..ee2e73872c 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -688,6 +688,7 @@ HEAP_ALLOCATING_HELPERS = { "format_mac_address_pretty": "format_mac_addr_upper() with a stack buffer", "get_mac_address": "get_mac_address_into_buffer() with a stack buffer", "get_mac_address_pretty": "get_mac_address_pretty_into_buffer() with a stack buffer", + "str_sanitize": "str_sanitize_to() with a stack buffer", "str_truncate": "removal (function is unused)", "str_upper_case": "removal (function is unused)", "str_snake_case": "removal (function is unused)", @@ -706,6 +707,7 @@ HEAP_ALLOCATING_HELPERS = { r"format_mac_address_pretty|" r"get_mac_address_pretty(?!_)|" r"get_mac_address(?!_)|" + r"str_sanitize(?!_)|" r"str_truncate|" r"str_upper_case|" r"str_snake_case"