aboutsummaryrefslogtreecommitdiff
path: root/cmd/krino/display_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 20:11:26 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 20:11:26 +0200
commit97b07968a0a239c862309bcdffe31848dfbf128c (patch)
treeb662f247c33ea93c0544dc8be40250dcf2a62fc7 /cmd/krino/display_test.go
parentaa24cfb344b1b3eaef7217d996359023cd72ba28 (diff)
downloadkrino-97b07968a0a239c862309bcdffe31848dfbf128c.tar.gz
krino-97b07968a0a239c862309bcdffe31848dfbf128c.zip
plan 8: escape terminal controls in everything krino prints
Diffstat (limited to 'cmd/krino/display_test.go')
-rw-r--r--cmd/krino/display_test.go86
1 files changed, 86 insertions, 0 deletions
diff --git a/cmd/krino/display_test.go b/cmd/krino/display_test.go
new file mode 100644
index 0000000..4c1cefe
--- /dev/null
+++ b/cmd/krino/display_test.go
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package main
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+ "unicode/utf8"
+)
+
+// firstUnsafe names the first thing in s a terminal could act on - a C0
+// control (a newline too, unless allowNewline), DEL, a C1 control, a
+// bidirectional control, or invalid UTF-8 - or returns "" when there is
+// none.
+func firstUnsafe(s string, allowNewline bool) string {
+ if !utf8.ValidString(s) {
+ return "invalid UTF-8"
+ }
+ for _, r := range s {
+ if r == '\n' && allowNewline {
+ continue
+ }
+ if r < 0x20 || r == 0x7f || (r >= 0x80 && r <= 0x9f) || (r >= 0x202a && r <= 0x202e) || (r >= 0x2066 && r <= 0x2069) {
+ return fmt.Sprintf("%U", r)
+ }
+ }
+ return ""
+}
+
+// TestDisplayEscapesTerminalControls: display shows every character a
+// terminal would act on as an escape, and leaves ordinary text - Polish
+// letters and a backslash included - exactly as it is.
+func TestDisplayEscapesTerminalControls(t *testing.T) {
+ for in, want := range map[string]string{
+ "plain name.pdf": "plain name.pdf",
+ "zażółć gęślą jaźń.pdf": "zażółć gęślą jaźń.pdf",
+ "esc\x1b[2Kx.pdf": `esc\x1b[2Kx.pdf`,
+ "bell\a.pdf": `bell\x07.pdf`,
+ "cr\rline.pdf": `cr\x0dline.pdf`,
+ "new\nline.pdf": `new\x0aline.pdf`,
+ "tab\t.pdf": `tab\x09.pdf`,
+ "del\x7f.pdf": `del\x7f.pdf`,
+ "c1\u009bcsi.pdf": `c1\u009bcsi.pdf`,
+ "bidi\u202egnp.pdf": `bidi\u202egnp.pdf`,
+ "isolate\u2066x\u2069.pdf": `isolate\u2066x\u2069.pdf`,
+ "bad\xffbyte.pdf": `bad\xffbyte.pdf`,
+ `back\slash.pdf`: `back\slash.pdf`,
+ "replacement\ufffd.pdf": "replacement\ufffd.pdf",
+ } {
+ if got := display(in); got != want {
+ t.Errorf("display(%q) = %q, want %q", in, got, want)
+ }
+ }
+}
+
+// FuzzDisplay: whatever a name holds, display's result holds nothing a
+// terminal could act on.
+func FuzzDisplay(f *testing.F) {
+ f.Add("esc\x1b[2K\u202e\xff\x00")
+ f.Add("plain")
+ f.Fuzz(func(t *testing.T, s string) {
+ if bad := firstUnsafe(display(s), false); bad != "" {
+ t.Fatalf("display(%q) = %q still holds %s", s, display(s), bad)
+ }
+ })
+}
+
+// TestReviewEscapesHostileNames: the per-file header and the delete
+// confirmation show a hostile name escaped, in review and in undo's review.
+func TestReviewEscapesHostileNames(t *testing.T) {
+ out := new(strings.Builder)
+ if _, _, _, err := reviewChains(strings.NewReader("cdnn"), out, chains("esc\x1b[2Kx.pdf"), "", palette{}); err != nil {
+ t.Fatal(err)
+ }
+ if bad := firstUnsafe(out.String(), true); bad != "" {
+ t.Errorf("review printed %s:\n%q", bad, out)
+ }
+ undoOut := new(strings.Builder)
+ if _, _, err := reviewUndoFiles(strings.NewReader("cn"), undoOut, undoFiles("esc\x1b[2Kx.pdf"), palette{}); err != nil {
+ t.Fatal(err)
+ }
+ if bad := firstUnsafe(undoOut.String(), true); bad != "" {
+ t.Errorf("undo review printed %s:\n%q", bad, undoOut)
+ }
+}