]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
string-util: don't miss ANSI sequence at the very end in previous_ansi_sequence()
authorArmaan Sandhu <armaan.sandhu0504@gmail.com>
Fri, 31 Jul 2026 13:14:35 +0000 (18:44 +0530)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 31 Jul 2026 15:12:40 +0000 (00:12 +0900)
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.

src/basic/string-util.c
src/test/test-ellipsize.c

index 9de61aafe88b5cf3b2fb61eb1ae96c6e875cb7aa..efd690b076218aa246834729e029c94d0a743e00 100644 (file)
@@ -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;
index 3d47cfebe2610889eb5ca72901959e9326532ccc..806d4f54ca45b55e643569066aa5d72833065f39 100644 (file)
@@ -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' };