diff --git a/src/basic/string-util.c b/src/basic/string-util.c index b06b50397e..4c7ab3c4d3 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -627,25 +627,58 @@ char *cellescape(char *buf, size_t len, const char *s) { * very end. */ - size_t i; - const char *t = s; + size_t i = 0, last_char_width[4] = {}, k = 0, j; - assert(len > 4 + 4 + 1); /* two chars and the terminator */ + assert(len > 0); /* at least a terminating NUL */ - for (i = 0; i < len - 9; t++) { - if (!*t) + for (;;) { + char four[4]; + int w; + + if (*s == 0) /* terminating NUL detected? then we are done! */ goto done; - i += cescape_char(*t, buf + i); + + w = cescape_char(*s, four); + if (i + w + 1 > len) /* This character doesn't fit into the buffer anymore? In that case let's + * ellipsize at the previous location */ + break; + + /* OK, there was space, let's add this escaped character to the buffer */ + memcpy(buf + i, four, w); + i += w; + + /* And remember its width in the ring buffer */ + last_char_width[k] = w; + k = (k + 1) % 4; + + s++; } - /* We have space for one more char and terminating nul at this point */ - if (*t) { - if (*(t+1)) - i += write_ellipsis(buf + i, false); - else - i += cescape_char(*t, buf + i); + /* Ellipsation is necessary. This means we might need to truncate the string again to make space for 4 + * characters ideally, but the buffer is shorter than that in the first place take what we can get */ + for (j = 0; j < ELEMENTSOF(last_char_width); j++) { + + if (i + 4 <= len) /* nice, we reached our space goal */ + break; + + k = k == 0 ? 3 : k - 1; + if (last_char_width[k] == 0) /* bummer, we reached the beginning of the strings */ + break; + + assert(i >= last_char_width[k]); + i -= last_char_width[k]; } + if (i + 4 <= len) /* yay, enough space */ + i += write_ellipsis(buf + i, false); + else if (i + 3 <= len) { /* only space for ".." */ + buf[i++] = '.'; + buf[i++] = '.'; + } else if (i + 2 <= len) /* only space for a single "." */ + buf[i++] = '.'; + else + assert(i + 1 <= len); + done: buf[i] = '\0'; return buf; diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 413adfda7d..8b176781de 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -81,21 +81,79 @@ static void test_ascii_strcasecmp_nn(void) { static void test_cellescape(void) { char buf[40]; - assert_se(streq(cellescape(buf, 10, "1"), "1")); - assert_se(streq(cellescape(buf, 10, "12"), "12")); - assert_se(streq(cellescape(buf, 10, "123"), is_locale_utf8() ? "1…" : "1...")); + assert_se(streq(cellescape(buf, 1, ""), "")); + assert_se(streq(cellescape(buf, 1, "1"), "")); + assert_se(streq(cellescape(buf, 1, "12"), "")); - assert_se(streq(cellescape(buf, 10, "1\011"), "1\\t")); - assert_se(streq(cellescape(buf, 10, "1\020"), "1\\020")); - assert_se(streq(cellescape(buf, 10, "1\020x"), is_locale_utf8() ? "1…" : "1...")); + assert_se(streq(cellescape(buf, 2, ""), "")); + assert_se(streq(cellescape(buf, 2, "1"), "1")); + assert_se(streq(cellescape(buf, 2, "12"), ".")); + assert_se(streq(cellescape(buf, 2, "123"), ".")); + + assert_se(streq(cellescape(buf, 3, ""), "")); + assert_se(streq(cellescape(buf, 3, "1"), "1")); + assert_se(streq(cellescape(buf, 3, "12"), "12")); + assert_se(streq(cellescape(buf, 3, "123"), "..")); + assert_se(streq(cellescape(buf, 3, "1234"), "..")); + + assert_se(streq(cellescape(buf, 4, ""), "")); + assert_se(streq(cellescape(buf, 4, "1"), "1")); + assert_se(streq(cellescape(buf, 4, "12"), "12")); + assert_se(streq(cellescape(buf, 4, "123"), "123")); + assert_se(streq(cellescape(buf, 4, "1234"), is_locale_utf8() ? "…" : "...")); + assert_se(streq(cellescape(buf, 4, "12345"), is_locale_utf8() ? "…" : "...")); + + assert_se(streq(cellescape(buf, 5, ""), "")); + assert_se(streq(cellescape(buf, 5, "1"), "1")); + assert_se(streq(cellescape(buf, 5, "12"), "12")); + assert_se(streq(cellescape(buf, 5, "123"), "123")); + assert_se(streq(cellescape(buf, 5, "1234"), "1234")); + assert_se(streq(cellescape(buf, 5, "12345"), is_locale_utf8() ? "1…" : "1...")); + assert_se(streq(cellescape(buf, 5, "123456"), is_locale_utf8() ? "1…" : "1...")); + + assert_se(streq(cellescape(buf, 1, "\020"), "")); + assert_se(streq(cellescape(buf, 2, "\020"), ".")); + assert_se(streq(cellescape(buf, 3, "\020"), "..")); + assert_se(streq(cellescape(buf, 4, "\020"), "…")); + assert_se(streq(cellescape(buf, 5, "\020"), "\\020")); + + assert_se(streq(cellescape(buf, 5, "1234\020"), "1…")); + assert_se(streq(cellescape(buf, 6, "1234\020"), "12…")); + assert_se(streq(cellescape(buf, 7, "1234\020"), "123…")); + assert_se(streq(cellescape(buf, 8, "1234\020"), "1234…")); + assert_se(streq(cellescape(buf, 9, "1234\020"), "1234\\020")); + + assert_se(streq(cellescape(buf, 1, "\t\n"), "")); + assert_se(streq(cellescape(buf, 2, "\t\n"), ".")); + assert_se(streq(cellescape(buf, 3, "\t\n"), "..")); + assert_se(streq(cellescape(buf, 4, "\t\n"), "…")); + assert_se(streq(cellescape(buf, 5, "\t\n"), "\\t\\n")); + + assert_se(streq(cellescape(buf, 5, "1234\t\n"), "1…")); + assert_se(streq(cellescape(buf, 6, "1234\t\n"), "12…")); + assert_se(streq(cellescape(buf, 7, "1234\t\n"), "123…")); + assert_se(streq(cellescape(buf, 8, "1234\t\n"), "1234…")); + assert_se(streq(cellescape(buf, 9, "1234\t\n"), "1234\\t\\n")); + + assert_se(streq(cellescape(buf, 4, "x\t\020\n"), "…")); + assert_se(streq(cellescape(buf, 5, "x\t\020\n"), "x…")); + assert_se(streq(cellescape(buf, 6, "x\t\020\n"), "x…")); + assert_se(streq(cellescape(buf, 7, "x\t\020\n"), "x\\t…")); + assert_se(streq(cellescape(buf, 8, "x\t\020\n"), "x\\t…")); + assert_se(streq(cellescape(buf, 9, "x\t\020\n"), "x\\t…")); + assert_se(streq(cellescape(buf, 10, "x\t\020\n"), "x\\t\\020\\n")); + + assert_se(streq(cellescape(buf, 6, "1\011"), "1\\t")); + assert_se(streq(cellescape(buf, 6, "1\020"), "1\\020")); + assert_se(streq(cellescape(buf, 6, "1\020x"), is_locale_utf8() ? "1…" : "1...")); assert_se(streq(cellescape(buf, 40, "1\020"), "1\\020")); assert_se(streq(cellescape(buf, 40, "1\020x"), "1\\020x")); assert_se(streq(cellescape(buf, 40, "\a\b\f\n\r\t\v\\\"'"), "\\a\\b\\f\\n\\r\\t\\v\\\\\\\"\\'")); - assert_se(streq(cellescape(buf, 10, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a...")); - assert_se(streq(cellescape(buf, 11, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a...")); - assert_se(streq(cellescape(buf, 12, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a\\b…" : "\\a\\b...")); + assert_se(streq(cellescape(buf, 6, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a...")); + assert_se(streq(cellescape(buf, 7, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a...")); + assert_se(streq(cellescape(buf, 8, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a\\b…" : "\\a\\b...")); assert_se(streq(cellescape(buf, sizeof buf, "1\020"), "1\\020")); assert_se(streq(cellescape(buf, sizeof buf, "1\020x"), "1\\020x"));