[core] Add str_contains_ignore_case_p for PROGMEM needle strings

This commit is contained in:
J. Nick Koston
2026-08-20 17:34:39 -05:00
parent 46f90d0c54
commit f3a7b4795b
3 changed files with 47 additions and 0 deletions
+22
View File
@@ -233,6 +233,28 @@ 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) {
if (haystack == nullptr || needle == nullptr) {
return false;
}
const auto *needle_p = reinterpret_cast<const uint8_t *>(needle);
if (progmem_read_byte(needle_p) == '\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') {
return true;
}
}
return false;
}
// 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) {
if (buffer_size == 0) {
+5
View File
@@ -1000,6 +1000,11 @@ inline bool str_contains_ignore_case(const char *haystack, const char *needle) {
#endif // defined(USE_LIBRETINY) || defined(USE_RP2) || defined(USE_ZEPHYR)
}
/// Case-insensitive check if needle string is contained in haystack (no heap allocation).
/// The needle may live in flash (PROGMEM) on ESP8266 — wrap literals with PSTR().
/// On all other platforms PROGMEM reads are plain dereferences, so any string works.
bool str_contains_ignore_case_p(const char *haystack, const char *needle);
// str_truncate moved to alloc_helpers.h - remove this include before 2026.11.0
// str_until, str_lower_case, str_upper_case moved to alloc_helpers.h - remove this comment before 2026.11.0
+20
View File
@@ -129,4 +129,24 @@ TEST(StringContainsIgnoreCaseTest, FallbackMatchesLibc) {
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"));
}
} // namespace esphome