aboutsummaryrefslogtreecommitdiff
path: root/cmd/krino/hostile_test.go
blob: a2b5d392ca3ff5a3459a9c6605afbdb2f24f7091 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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")
}