diff --git a/api/String.cpp b/api/String.cpp index 4f176374..a6d6b2ee 100644 --- a/api/String.cpp +++ b/api/String.cpp @@ -444,7 +444,16 @@ int String::compareTo(const String &s) const if (buffer && len > 0) return *(unsigned char *)buffer; return 0; } - return strcmp(buffer, s.buffer); + // Use memcmp() rather than strcmp() so Strings holding embedded NUL + // bytes (which len tracks explicitly) are compared over their full, + // known length instead of stopping at the first NUL either buffer + // happens to contain. + unsigned int minlen = (len < s.len) ? len : s.len; + int r = minlen ? memcmp(buffer, s.buffer, minlen) : 0; + if (r != 0) return r; + if (len < s.len) return -1; + if (len > s.len) return 1; + return 0; } int String::compareTo(const char *cstr) const @@ -491,13 +500,16 @@ bool String::startsWith( const String &s2 ) const bool String::startsWith( const String &s2, unsigned int offset ) const { if (offset > len - s2.len || !buffer || !s2.buffer) return false; - return strncmp( &buffer[offset], s2.buffer, s2.len ) == 0; + // memcmp(), not strncmp(): strncmp() also stops at the first NUL byte + // in either operand, so a match containing an embedded NUL followed + // by different bytes would be misreported as a full match. + return s2.len == 0 || memcmp( &buffer[offset], s2.buffer, s2.len ) == 0; } bool String::endsWith( const String &s2 ) const { if ( len < s2.len || !buffer || !s2.buffer) return false; - return strcmp(&buffer[len - s2.len], s2.buffer) == 0; + return s2.len == 0 || memcmp(&buffer[len - s2.len], s2.buffer, s2.len) == 0; } /*********************************************/ diff --git a/test/src/String/test_compareTo.cpp b/test/src/String/test_compareTo.cpp index b5673201..c20036f1 100644 --- a/test/src/String/test_compareTo.cpp +++ b/test/src/String/test_compareTo.cpp @@ -37,6 +37,25 @@ TEST_CASE ("Testing String::compareTo(const String &)", "[String-compareTo-01]") arduino::String str1, str2("Hello"); REQUIRE(str1.compareTo(str2) < 0); } + + WHEN ("Strings are the same length but differ only after an embedded NUL byte") + { + char buf1[] = {'A', '\0', 'B'}; + char buf2[] = {'A', '\0', 'C'}; + arduino::String str1(buf1, sizeof(buf1)); + arduino::String str2(buf2, sizeof(buf2)); + REQUIRE(str1.compareTo(str2) != 0); + } + + WHEN ("Strings are equal and both contain an embedded NUL byte") + { + char buf1[] = {'A', '\0', 'B'}; + char buf2[] = {'A', '\0', 'B'}; + arduino::String str1(buf1, sizeof(buf1)); + arduino::String str2(buf2, sizeof(buf2)); + REQUIRE(str1.compareTo(str2) == 0); + REQUIRE(str1 == str2); + } } TEST_CASE ("Testing String::compareTo(const char *)", "[String-compareTo-02]") diff --git a/test/src/String/test_comparisonFunc.cpp b/test/src/String/test_comparisonFunc.cpp index 558012d1..885e71b6 100644 --- a/test/src/String/test_comparisonFunc.cpp +++ b/test/src/String/test_comparisonFunc.cpp @@ -157,3 +157,39 @@ TEST_CASE ("Testing String::endsWith(const String &)", "[String-endsWith-10]") REQUIRE(str1.endsWith(str2) == 0); } } + +TEST_CASE ("Testing String comparisons with embedded NUL bytes", "[String-embedded-nul-11]") +{ + WHEN ("startsWith prefix matches up to an embedded NUL but differs after it") + { + char buf1[] = {'A', '\0', 'B', 'C'}; + char buf2[] = {'A', '\0', 'X', 'Y'}; + arduino::String str1(buf1, sizeof(buf1)); + arduino::String str2(buf2, sizeof(buf2)); + REQUIRE(str1.startsWith(str2) == 0); + } + WHEN ("startsWith prefix genuinely matches, including the embedded NUL") + { + char buf1[] = {'A', '\0', 'B', 'C'}; + char buf2[] = {'A', '\0', 'B'}; + arduino::String str1(buf1, sizeof(buf1)); + arduino::String str2(buf2, sizeof(buf2)); + REQUIRE(str1.startsWith(str2) == 1); + } + WHEN ("endsWith suffix matches up to an embedded NUL but differs after it") + { + char buf1[] = {'X', 'Y', '\0', 'C'}; + char buf2[] = {'A', 'B', '\0', 'C'}; + arduino::String str1(buf1, sizeof(buf1)); + arduino::String str2(buf2, sizeof(buf2)); + REQUIRE(str1.endsWith(str2) == 0); + } + WHEN ("endsWith suffix genuinely matches, including the embedded NUL") + { + char buf1[] = {'X', 'Y', '\0', 'C'}; + char buf2[] = {'Y', '\0', 'C'}; + arduino::String str1(buf1, sizeof(buf1)); + arduino::String str2(buf2, sizeof(buf2)); + REQUIRE(str1.endsWith(str2) == 1); + } +}