From c5042b71d35d0d72f501d8be4e9ea95b1534ad8a Mon Sep 17 00:00:00 2001 From: Terry Carr Date: Sat, 10 Oct 2026 21:56:21 +0100 Subject: [PATCH] fix: improve hashtag span validation and enhance test coverage for edge cases --- src/ui-touch/ChatHashtag.h | 11 +++++++++-- test/test_utf8_text.cpp | 25 +++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/ui-touch/ChatHashtag.h b/src/ui-touch/ChatHashtag.h index 7a5769f..c46c20f 100644 --- a/src/ui-touch/ChatHashtag.h +++ b/src/ui-touch/ChatHashtag.h @@ -29,12 +29,18 @@ namespace ChatHashtag inline bool isHexColor(const char *text, int start, int end) { const int length = end - start; - if (length != 3 && length != 4 && length != 6 && length != 8) + if (length != 6 && length != 8) return false; + bool colorLike = false; for (int offset = start; offset < end; ++offset) + { if (!isxdigit((unsigned char)text[offset])) return false; - return true; + if (isdigit((unsigned char)text[offset]) || + (text[offset] >= 'A' && text[offset] <= 'F')) + colorLike = true; + } + return colorLike; } inline bool span(const char *text, int from, int *start, int *end) @@ -81,6 +87,7 @@ namespace ChatHashtag } if (finish == offset + 1 || finish - offset >= 32 || isdigit((unsigned char)text[offset + 1]) || + text[offset + 1] == '_' || text[offset + 1] == '-' || isHexColor(text, offset + 1, finish)) continue; *start = offset; diff --git a/test/test_utf8_text.cpp b/test/test_utf8_text.cpp index 512a781..8150f83 100644 --- a/test/test_utf8_text.cpp +++ b/test/test_utf8_text.cpp @@ -26,6 +26,19 @@ int main() { assert(!ChatHashtag::span("#caf\xC3", 0, &start, &end)); assert(!ChatHashtag::span("#1", 0, &start, &end)); assert(!ChatHashtag::span("#1channel", 0, &start, &end)); + assert(!ChatHashtag::span("#_", 0, &start, &end)); + assert(!ChatHashtag::span("#-", 0, &start, &end)); + assert(!ChatHashtag::span("#___", 0, &start, &end)); + assert(!ChatHashtag::span("#---", 0, &start, &end)); + assert(!ChatHashtag::span("#_-_", 0, &start, &end)); + assert(!ChatHashtag::span("#_42", 0, &start, &end)); + assert(!ChatHashtag::span("#-42", 0, &start, &end)); + assert(!ChatHashtag::span("#_mesh", 0, &start, &end)); + assert(!ChatHashtag::span("#-mesh", 0, &start, &end)); + const std::string separators_then_tag = "#_ #- #_-_ #_42 #-42 #mesh"; + assert(ChatHashtag::span(separators_then_tag.c_str(), 0, &start, &end)); + assert(separators_then_tag.substr(start, end - start) == "#mesh"); + assert(!ChatHashtag::span(separators_then_tag.c_str(), end, &start, &end)); assert(ChatHashtag::span("#channel1", 0, &start, &end)); const std::string trailing_hyphen = "#mesh-"; assert(ChatHashtag::span(trailing_hyphen.c_str(), 0, &start, &end)); @@ -39,11 +52,19 @@ int main() { const std::string underscored = "#mesh_channel"; assert(ChatHashtag::span(underscored.c_str(), 0, &start, &end)); assert(underscored.substr(start, end - start) == "#mesh_channel"); - assert(!ChatHashtag::span("#FFF", 0, &start, &end)); - assert(!ChatHashtag::span("#FFFF", 0, &start, &end)); + assert(ChatHashtag::span("#FFF", 0, &start, &end)); + assert(ChatHashtag::span("#FFFF", 0, &start, &end)); + assert(ChatHashtag::span("#beef", 0, &start, &end)); + assert(ChatHashtag::span("#cafe", 0, &start, &end)); + assert(ChatHashtag::span("#abcdef", 0, &start, &end)); + assert(ChatHashtag::span("#deadbeef", 0, &start, &end)); assert(!ChatHashtag::span("#FFFFFF", 0, &start, &end)); assert(!ChatHashtag::span("#FF0000", 0, &start, &end)); assert(!ChatHashtag::span("#FFFFFFFF", 0, &start, &end)); + assert(!ChatHashtag::span("#abcDef", 0, &start, &end)); + assert(!ChatHashtag::span("#ff0000", 0, &start, &end)); + assert(!ChatHashtag::span("#abcdef12", 0, &start, &end)); + assert(ChatHashtag::span("#mesh12", 0, &start, &end)); assert(ChatHashtag::span("#cable", 0, &start, &end)); std::string long_tag = "#"; for (int count = 0; count < 15; ++count)