From: Armaan Sandhu Date: Fri, 31 Jul 2026 13:14:35 +0000 (+0530) Subject: string-util: don't miss ANSI sequence at the very end in previous_ansi_sequence() X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2bb179632f2c4c3e03378cb8dd3c23f335472cb4;p=thirdparty%2Fsystemd.git string-util: don't miss ANSI sequence at the very end in previous_ansi_sequence() The backwards scan started at offset length-3, so the last position at which a sequence can begin, length-2, was never examined. CSI sequences are at least three bytes long and were thus unaffected, but two byte Fe sequences (ESC followed by 0x40…0x5F) terminating the examined slice were missed. ellipsize_mem() calls this to figure out whether a sequence ends exactly at the current position, so that it can be skipped over, which is precisely the case that was broken: such a sequence was instead counted as two visible cells and copied through as text, and the string was ellipsized more aggressively than requested. For example ellipsize("🐱🐱\x1bM🐱🐱\x1bM", 5, 0) returned a three cell wide string rather than the five cells asked for. --- diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 9de61aafe88..efd690b0762 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -293,13 +293,15 @@ static size_t previous_ansi_sequence(const char *s, size_t length, const char ** return 0; } - for (size_t i = length - 2; i > 0; i--) { /* -2 because at least two bytes are needed */ - size_t slen = ansi_sequence_length(s + (i - 1), length - (i - 1)); - if (slen == 0) - continue; + for (size_t i = length - 2;; i--) { /* -2 because at least two bytes are needed */ + size_t slen = ansi_sequence_length(s + i, length - i); + if (slen > 0) { + *ret_where = s + i; + return slen; + } - *ret_where = s + (i - 1); - return slen; + if (i == 0) + break; } *ret_where = NULL; diff --git a/src/test/test-ellipsize.c b/src/test/test-ellipsize.c index 3d47cfebe26..806d4f54ca4 100644 --- a/src/test/test-ellipsize.c +++ b/src/test/test-ellipsize.c @@ -162,6 +162,21 @@ TEST(ellipsize_ansi_cats) { ASSERT_STREQ(h, "🐱…" ANSI_NORMAL "🐱" ANSI_NORMAL); } +TEST(ellipsize_ansi_two_byte) { + _cleanup_free_ char *e = NULL, *f = NULL; + + /* Fe escape sequences are just two bytes long (ESC followed by 0x40…0x5F). Make sure they are + * skipped over like the longer CSI sequences are, in particular when one terminates the string. */ + + e = ellipsize("🐱🐱" "\x1bM" "🐱🐱" "\x1bM", 5, 0); + puts(e); + ASSERT_STREQ(e, "…" "\x1bM" "🐱🐱" "\x1bM"); + + f = ellipsize("ab" "\x1bM" "cd" "\x1bM", 4, 90); + puts(f); + ASSERT_STREQ(f, "ab" "\x1bM" "cd" "\x1bM"); +} + TEST(ellipsize_esc_infinite_loop) { /* Make sure we don't infinite loop on an ESC in the first half */ static const char s[] = { 'A', 'B', 0x1B, ' ', 'D', '\0' };