Simplify PROGMEM variant to strncasecmp_P and compile it only on ESP8266

This commit is contained in:
J. Nick Koston
2026-08-20 17:49:02 -05:00
parent 2399a0bb83
commit 3d946596e9
4 changed files with 23 additions and 35 deletions
+3 -1
View File
@@ -1,6 +1,7 @@
#include "audio.h"
#include "esphome/core/helpers.h"
#include "esphome/core/progmem.h"
#include <cstring>
@@ -86,7 +87,8 @@ AudioFileType detect_audio_file_type(const char *content_type, const char *url)
// Match "audio/ogg" with a codecs parameter containing "opus"
// Valid forms: audio/ogg;codecs=opus, audio/ogg; codecs="opus", etc.
// Plain "audio/ogg" without opus is not matched (almost always Ogg Vorbis)
if (strncasecmp(content_type, "audio/ogg", 9) == 0 && str_contains_ignore_case(content_type + 9, "opus")) {
if (strncasecmp(content_type, "audio/ogg", 9) == 0 &&
str_contains_ignore_case_p(content_type + 9, ESPHOME_PSTR("opus"))) {
return AudioFileType::OPUS;
}
#endif
+6 -10
View File
@@ -233,27 +233,23 @@ bool str_contains_ignore_case_fallback(const char *haystack, const char *needle)
return false;
}
bool str_contains_ignore_case_p(const char *haystack, const char *needle) {
#ifdef USE_ESP8266
bool str_contains_ignore_case_p(const char *haystack, PGM_P needle) {
if (haystack == nullptr || needle == nullptr) {
return false;
}
const auto *needle_p = reinterpret_cast<const uint8_t *>(needle);
if (progmem_read_byte(needle_p) == '\0') {
const size_t needle_len = strlen_P(needle);
if (needle_len == 0) {
return true;
}
for (const char *p = haystack; *p != '\0'; p++) {
size_t i = 0;
uint8_t n;
// Never reads past the haystack terminator: comparing '\0' against a non-'\0' needle byte fails
while ((n = progmem_read_byte(needle_p + i)) != '\0' && ::tolower(static_cast<uint8_t>(p[i])) == ::tolower(n)) {
i++;
}
if (n == '\0') {
if (strncasecmp_P(p, needle, needle_len) == 0) {
return true;
}
}
return false;
}
#endif // USE_ESP8266
// str_truncate, str_until, str_lower_case, str_upper_case, str_snake_case moved to alloc_helpers.cpp
char *str_sanitize_to(char *buffer, size_t buffer_size, const char *str) {
+7 -6
View File
@@ -984,13 +984,10 @@ inline bool str_endswith_ignore_case(const std::string &str, const char *suffix)
/// Fallback implementation for case insensitive substring comparison.
bool str_contains_ignore_case_fallback(const char *haystack, const char *needle);
/// Case-insensitive check if needle string is contained in haystack (no heap allocation).
/// ESP8266 internal implementation — prefer the `str_contains_ignore_case` macro which wraps
/// needle literals with `PSTR()` automatically so they stay in flash instead of eating RAM.
/// The needle must be a PROGMEM pointer on ESP8266; reads are plain dereferences elsewhere.
bool str_contains_ignore_case_p(const char *haystack, const char *needle);
#ifdef USE_ESP8266
/// ESP8266 internal implementation reading the needle from flash — prefer the
/// `str_contains_ignore_case` macro which wraps needle literals with PSTR() automatically.
bool str_contains_ignore_case_p(const char *haystack, PGM_P needle);
/// Case-insensitive check if needle string is contained in haystack (no heap allocation).
/// On ESP8266 the needle literal is wrapped with PSTR() so it stays in flash.
#define str_contains_ignore_case(haystack, needle) str_contains_ignore_case_p(haystack, PSTR(needle))
@@ -1010,6 +1007,10 @@ inline bool str_contains_ignore_case(const char *haystack, const char *needle) {
return strcasestr(haystack, needle) != nullptr;
#endif // defined(USE_LIBRETINY) || defined(USE_RP2) || defined(USE_ZEPHYR)
}
/// PROGMEM is a no-op on this platform, so the flash-needle variant is the plain check.
inline bool str_contains_ignore_case_p(const char *haystack, const char *needle) {
return str_contains_ignore_case(haystack, needle);
}
#endif // USE_ESP8266
// str_truncate moved to alloc_helpers.h - remove this include before 2026.11.0
+7 -18
View File
@@ -90,6 +90,10 @@ TEST(StringContainsIgnoreCaseTest, NullPointerAlwaysFalse) {
EXPECT_FALSE(str_contains_ignore_case(haystack, needle));
EXPECT_FALSE(str_contains_ignore_case("Hello World", needle));
EXPECT_FALSE(str_contains_ignore_case(haystack, "anything"));
EXPECT_FALSE(str_contains_ignore_case_p(haystack, needle));
EXPECT_FALSE(str_contains_ignore_case_p("Hello World", needle));
EXPECT_FALSE(str_contains_ignore_case_p(haystack, "anything"));
}
TEST(StringContainsIgnoreCaseTest, EmptySearchMatches) {
@@ -121,29 +125,14 @@ TEST(StringContainsIgnoreCaseTest, MiscNotMatching) {
TEST(StringContainsIgnoreCaseTest, FallbackMatchesLibc) {
const char *haystack = "Hello World";
for (const char *needle : {"", "Hello", "hELLO", "Hell", "world", "Heaven", "Hello!", "d"}) {
for (const char *needle : {"", "Hello", "hELLO", "HELLO", "Hell", "world", "World", "Heaven", "Hello!", "d"}) {
EXPECT_EQ(str_contains_ignore_case_fallback(haystack, needle), str_contains_ignore_case(haystack, needle))
<< "needle: " << needle;
EXPECT_EQ(str_contains_ignore_case_p(haystack, needle), str_contains_ignore_case(haystack, needle))
<< "needle: " << needle;
}
EXPECT_EQ(str_contains_ignore_case_fallback("", ""), str_contains_ignore_case("", ""));
EXPECT_EQ(str_contains_ignore_case_fallback("ab", "abc"), str_contains_ignore_case("ab", "abc"));
}
TEST(StringContainsIgnoreCaseTest, ProgmemVariantNullPointerAlwaysFalse) {
const char *haystack = nullptr;
const char *needle = nullptr;
EXPECT_FALSE(str_contains_ignore_case_p(haystack, needle));
EXPECT_FALSE(str_contains_ignore_case_p("Hello World", needle));
EXPECT_FALSE(str_contains_ignore_case_p(haystack, "anything"));
}
TEST(StringContainsIgnoreCaseTest, ProgmemVariantMatchesFallback) {
const char *haystack = "Hello World";
for (const char *needle : {"", "Hello", "hELLO", "HELLO", "Hell", "world", "World", "Heaven", "Hello!", "d"}) {
EXPECT_EQ(str_contains_ignore_case_p(haystack, needle), str_contains_ignore_case_fallback(haystack, needle))
<< "needle: " << needle;
}
EXPECT_TRUE(str_contains_ignore_case_p("", ""));
EXPECT_FALSE(str_contains_ignore_case_p("", "a"));
EXPECT_FALSE(str_contains_ignore_case_p("ab", "abc"));