12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 | package tui
import (
"strings"
"testing"
"unicode/utf8"
)
var sanitizeSeeds = []string{
"plain\ntext",
"visible\x1b]0;x rest-of-output-hidden\nline2\nline3",
"\x1b[31mred\x1b[0m \x1b[2J\x1b[H",
"\x1b]8;;https://e.example\x1b\\link\x1b]8;;\x1b\\",
"\x1bP+q\x1b\\ \x1b_apc\x07 \x1b^pm\x1b\\",
"esc\x1b\nnext",
"c1\u009b31m \u009d0;t\u009c",
"\x9b\xff invalid",
"bidi\u202eevil\u2066x\u200f",
"tag\U000e0041\U000e0042",
"a\r\nb\rc\t",
}
// Sanitized text holds no control characters but newlines, no escape
// sequences, no bidi controls, and is valid UTF-8.
func checkSanitized(t *testing.T, in, out string) {
t.Helper()
if !utf8.ValidString(out) {
t.Fatalf("Sanitize(%q) = %q: invalid UTF-8", in, out)
}
for _, r := range out {
if r != '\n' && (r < 0x20 || r == 0x7f || r >= 0x80 && r < 0xa0) ||
r >= '\u202a' && r <= '\u202e' || r >= '\u2066' && r <= '\u2069' || r == '\u200e' || r == '\u200f' ||
r >= '\U000e0000' && r <= '\U000e007f' {
t.Fatalf("Sanitize(%q) = %q: holds %U", in, out, r)
}
}
}
// Sanitizing is line by line: no sequence, finished or not, reaches past
// the end of its line, so nothing can take later lines out of sight.
func FuzzSanitize(f *testing.F) {
for _, s := range sanitizeSeeds {
f.Add(s)
}
f.Fuzz(func(t *testing.T, s string) {
out := Sanitize(s)
checkSanitized(t, s, out)
lines := strings.Split(s, "\n")
for i, l := range lines {
lines[i] = Sanitize(l)
}
if want := strings.Join(lines, "\n"); out != want {
t.Fatalf("Sanitize(%q) = %q, but line by line %q", s, out, want)
}
})
}
// SanitizeColors keeps only color and erase-line sequences; the rest is
// as Sanitize leaves it.
func FuzzSanitizeColors(f *testing.F) {
for _, s := range append(sanitizeSeeds, "\x1b]8;;\x1b[31m\ax", "\x1b[38;2;1;2;3mrgb\x1b[K\x1b[0K\x1b[1K") {
f.Add(s)
}
f.Fuzz(func(t *testing.T, s string) {
out := SanitizeColors(s)
var text strings.Builder
for rest := out; ; {
i := strings.IndexByte(rest, 0x1b)
if i < 0 {
text.WriteString(rest)
break
}
text.WriteString(rest[:i])
j := i + 2
if j > len(rest) || rest[i+1] != '[' {
t.Fatalf("SanitizeColors(%q) = %q: a bare ESC", s, out)
}
for j < len(rest) && (rest[j] >= '0' && rest[j] <= '9' || rest[j] == ';' || rest[j] == ':') {
j++
}
if j >= len(rest) || rest[j] != 'm' && !(rest[j] == 'K' && (j == i+2 || rest[i+2:j] == "0")) {
t.Fatalf("SanitizeColors(%q) = %q: keeps a sequence that is not a color", s, out)
}
rest = rest[j+1:]
}
checkSanitized(t, s, text.String())
})
}
|