diff options
Diffstat (limited to 'cmd/krino/hostile_test.go')
| -rw-r--r-- | cmd/krino/hostile_test.go | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/cmd/krino/hostile_test.go b/cmd/krino/hostile_test.go new file mode 100644 index 0000000..a2b5d39 --- /dev/null +++ b/cmd/krino/hostile_test.go @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +// hostileNames are file names a download can carry that try to take over +// the terminal krino prints them to, or to fake what it shows. +var hostileNames = []string{ + "esc\x1b[2K\x1b[1Ahidden.pdf", + "bell\a.pdf", + "cr\rspoof.pdf", + "c1\u009b31mred.pdf", + "bidi\u202egnp.pdf", + "new\nline.pdf", + "-rf.pdf", +} + +// TestHostileNamesNeverReachTheTerminal: with files named to attack the +// terminal, and an extraction tool whose error message carries an escape +// sequence, nothing krino prints - plan, verbose lists, warnings, explain, +// apply, log, undo plan - holds a raw control character. +func TestHostileNamesNeverReachTheTerminal(t *testing.T) { + h := home(t) + dl := filepath.Join(h, "dl") + if err := os.MkdirAll(dl, 0o755); err != nil { + t.Fatal(err) + } + old := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) + for _, n := range hostileNames { + p := filepath.Join(dl, n) + if err := os.WriteFile(p, []byte("content of a hostile file"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.Chtimes(p, old, old); err != nil { + t.Fatal(err) + } + } + bin := filepath.Join(h, "bin") + if err := os.Mkdir(bin, 0o755); err != nil { + t.Fatal(err) + } + tool := "#!/bin/sh\nprintf 'bad \\033[2Jpdf\\n' >&2\nexit 1\n" + if err := os.WriteFile(filepath.Join(bin, "pdftotext"), []byte(tool), 0o755); err != nil { + t.Fatal(err) + } + t.Setenv("PATH", bin) + if code, _, errOut := runCLI(t, "init"); code != 0 { + t.Fatal(errOut) + } + if code, _, errOut := runCLI(t, "new", "dl", dl); code != 0 { + t.Fatal(errOut) + } + rules := "(path \"~/dl\")\n(rule \"read\" (when (content \"acme\")) (stop))\n(rule \"all\" (move \"Out\"))\n" + if err := os.WriteFile(filepath.Join(h, ".config", "krino", "dirs", "dl.conf"), []byte(rules), 0o644); err != nil { + t.Fatal(err) + } + + check := func(args ...string) string { + t.Helper() + code, out, errOut := runCLI(t, args...) + if code != 0 { + t.Fatalf("krino %q: exit %d\n%s\n%s", args, code, out, errOut) + } + for stream, text := range map[string]string{"stdout": out, "stderr": errOut} { + if bad := firstUnsafe(text, true); bad != "" { + t.Errorf("krino %q: %s holds %s:\n%q", args, stream, bad, text) + } + } + return out + } + out := check("-n", "-v") + if !strings.Contains(out, `esc\x1b[2K\x1b[1Ahidden.pdf`) || !strings.Contains(out, `bad \x1b[2Jpdf`) { + t.Errorf("names and the tool's message should be shown escaped:\n%s", out) + } + check("explain", filepath.Join(dl, hostileNames[0])) + check("-y") + check("log") + check("undo", "-n") +} |
