Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions api/String.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

/*********************************************/
Expand Down
19 changes: 19 additions & 0 deletions test/src/String/test_compareTo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]")
Expand Down
36 changes: 36 additions & 0 deletions test/src/String/test_comparisonFunc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading