mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[core] Add portable strcasestr implementation named str_contains_ignore_case (#18497)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
co-authored by
J. Nick Koston
parent
5177972c04
commit
46f90d0c54
@@ -86,7 +86,7 @@ AudioFileType detect_audio_file_type(const char *content_type, const char *url)
|
|||||||
// Match "audio/ogg" with a codecs parameter containing "opus"
|
// Match "audio/ogg" with a codecs parameter containing "opus"
|
||||||
// Valid forms: audio/ogg;codecs=opus, audio/ogg; codecs="opus", etc.
|
// Valid forms: audio/ogg;codecs=opus, audio/ogg; codecs="opus", etc.
|
||||||
// Plain "audio/ogg" without opus is not matched (almost always Ogg Vorbis)
|
// Plain "audio/ogg" without opus is not matched (almost always Ogg Vorbis)
|
||||||
if (strncasecmp(content_type, "audio/ogg", 9) == 0 && strcasestr(content_type + 9, "opus") != nullptr) {
|
if (strncasecmp(content_type, "audio/ogg", 9) == 0 && str_contains_ignore_case(content_type + 9, "opus")) {
|
||||||
return AudioFileType::OPUS;
|
return AudioFileType::OPUS;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -220,6 +220,19 @@ bool str_endswith_ignore_case(const char *str, size_t str_len, const char *suffi
|
|||||||
return strncasecmp(str + str_len - suffix_len, suffix, suffix_len) == 0;
|
return strncasecmp(str + str_len - suffix_len, suffix, suffix_len) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool str_contains_ignore_case_fallback(const char *haystack, const char *needle) {
|
||||||
|
const size_t needle_len = strlen(needle);
|
||||||
|
if (needle_len == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const char *p = haystack; *p != '\0'; p++) {
|
||||||
|
if (strncasecmp(p, needle, needle_len) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// str_truncate, str_until, str_lower_case, str_upper_case, str_snake_case moved to alloc_helpers.cpp
|
// 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) {
|
char *str_sanitize_to(char *buffer, size_t buffer_size, const char *str) {
|
||||||
if (buffer_size == 0) {
|
if (buffer_size == 0) {
|
||||||
|
|||||||
@@ -981,6 +981,25 @@ inline bool str_endswith_ignore_case(const std::string &str, const char *suffix)
|
|||||||
return str_endswith_ignore_case(str.c_str(), str.size(), suffix, strlen(suffix));
|
return str_endswith_ignore_case(str.c_str(), str.size(), suffix, strlen(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).
|
||||||
|
inline bool str_contains_ignore_case(const char *haystack, const char *needle) {
|
||||||
|
if (!needle || !haystack) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// strcasestr is a GNU extension: newlib only declares it when _GNU_SOURCE is set.
|
||||||
|
// ESP32/ESP8266/host builds get it from their framework or from g++ on Linux;
|
||||||
|
// LibreTiny, RP2 and Zephyr do not, so they use the hand-rolled fallback.
|
||||||
|
#if defined(USE_LIBRETINY) || defined(USE_RP2) || defined(USE_ZEPHYR)
|
||||||
|
return str_contains_ignore_case_fallback(haystack, needle);
|
||||||
|
#else // defined(USE_LIBRETINY) || defined(USE_RP2) || defined(USE_ZEPHYR)
|
||||||
|
return strcasestr(haystack, needle) != nullptr;
|
||||||
|
#endif // defined(USE_LIBRETINY) || defined(USE_RP2) || defined(USE_ZEPHYR)
|
||||||
|
}
|
||||||
|
|
||||||
// str_truncate moved to alloc_helpers.h - remove this include before 2026.11.0
|
// 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
|
// str_until, str_lower_case, str_upper_case moved to alloc_helpers.h - remove this comment before 2026.11.0
|
||||||
|
|||||||
@@ -83,4 +83,50 @@ TEST(StaticVectorTest, ConvertingConstructorSameSize) {
|
|||||||
EXPECT_EQ(dst[2], 3);
|
EXPECT_EQ(dst[2], 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(StringContainsIgnoreCaseTest, NullPointerAlwaysFalse) {
|
||||||
|
const char *haystack = nullptr;
|
||||||
|
const char *needle = nullptr;
|
||||||
|
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StringContainsIgnoreCaseTest, EmptySearchMatches) {
|
||||||
|
const char *haystack = "Hello World";
|
||||||
|
|
||||||
|
EXPECT_TRUE(str_contains_ignore_case_fallback(haystack, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StringContainsIgnoreCaseTest, MiscCaseMatches) {
|
||||||
|
const char *haystack = "Hello World";
|
||||||
|
|
||||||
|
EXPECT_TRUE(str_contains_ignore_case_fallback(haystack, "Hello"));
|
||||||
|
EXPECT_TRUE(str_contains_ignore_case_fallback(haystack, "hello"));
|
||||||
|
EXPECT_TRUE(str_contains_ignore_case_fallback(haystack, "HELLO"));
|
||||||
|
EXPECT_TRUE(str_contains_ignore_case_fallback(haystack, "hELLO"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StringContainsIgnoreCaseTest, MiscNotMatching) {
|
||||||
|
const char *haystack = "Hello World";
|
||||||
|
|
||||||
|
// Expected to match
|
||||||
|
EXPECT_TRUE(str_contains_ignore_case_fallback(haystack, "Hell"));
|
||||||
|
|
||||||
|
// Expected not to match
|
||||||
|
EXPECT_FALSE(str_contains_ignore_case_fallback(haystack, "Heaven"));
|
||||||
|
EXPECT_FALSE(str_contains_ignore_case_fallback(haystack, "Hello!"));
|
||||||
|
EXPECT_FALSE(str_contains_ignore_case_fallback(haystack, "world!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StringContainsIgnoreCaseTest, FallbackMatchesLibc) {
|
||||||
|
const char *haystack = "Hello World";
|
||||||
|
for (const char *needle : {"", "Hello", "hELLO", "Hell", "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_fallback("", ""), str_contains_ignore_case("", ""));
|
||||||
|
EXPECT_EQ(str_contains_ignore_case_fallback("ab", "abc"), str_contains_ignore_case("ab", "abc"));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
Reference in New Issue
Block a user