package ingest import ( "strings" "testing" ) func TestSanitiseFilenameStripsPathTraversal(t *testing.T) { cases := map[string]string{ "../../../etc/passwd": "passwd", "..\\..\\..\\windows\\system32\\cmd.exe": "cmd.exe", "/etc/shadow": "shadow", "foo/../../bar.txt": "bar.txt", "normal.pdf": "normal.pdf", "with spaces.docx": "with spaces.docx", "unicode-ünïcödé.txt": "unicode-ünïcödé.txt", } for in, want := range cases { if got := SanitiseFilename(in); got != want { t.Errorf("SanitiseFilename(%q) = %q, want %q", in, got, want) } } } func TestSanitiseFilenameRemovesDangerousCharacters(t *testing.T) { hostile := []string{ "file\x00.txt", "file\nname.txt", "file\rname.txt", "a:b.txt", "a*b.txt", "a?b.txt", `a"b.txt`, "a.txt", "a|b.txt", } for _, in := range hostile { got := SanitiseFilename(in) for _, bad := range []string{"\x00", "\n", "\r", "/", "\\", ":", "*", "?", `"`, "<", ">", "|"} { if strings.Contains(got, bad) { t.Errorf("SanitiseFilename(%q) = %q, which still contains %q", in, got, bad) } } } } func TestSanitiseFilenameRefusesHiddenAndReservedNames(t *testing.T) { // A leading dot creates a hidden file and can conceal the real extension. for _, in := range []string{".hidden", "...", ". leading space.txt"} { if got := SanitiseFilename(in); strings.HasPrefix(got, ".") { t.Errorf("SanitiseFilename(%q) = %q, still hidden", in, got) } } // Windows device names cause surprising behaviour if the file is ever // copied to such a system. for _, in := range []string{"CON", "PRN", "AUX", "NUL", "COM1", "LPT1", "con.txt", "nul.pdf"} { got := SanitiseFilename(in) if strings.EqualFold(got, in) { t.Errorf("reserved device name %q was passed through unchanged", in) } } } func TestSanitiseFilenameHandlesDegenerateInput(t *testing.T) { for _, in := range []string{"", ".", "..", "/", "//", " ", "...."} { got := SanitiseFilename(in) if strings.ContainsAny(got, "/\\") || got == ".." || got == "." { t.Errorf("SanitiseFilename(%q) = %q, which is not a safe base name", in, got) } } } func TestSanitiseFilenameIsBounded(t *testing.T) { long := strings.Repeat("a", 5000) + ".txt" if got := SanitiseFilename(long); len(got) > 200 { t.Fatalf("a %d-character name produced %d characters", len(long), len(got)) } } func TestSanitiseFilenameIsIdempotent(t *testing.T) { for _, in := range []string{"../../etc/passwd", "CON", ".hidden", "normal.pdf", "a:b*c.txt"} { once := SanitiseFilename(in) if twice := SanitiseFilename(once); once != twice { t.Errorf("not idempotent for %q: %q then %q", in, once, twice) } } } func FuzzSanitiseFilename(f *testing.F) { f.Add("../../etc/passwd") f.Add("normal.pdf") f.Add("CON") f.Add("\x00\xff") f.Add(strings.Repeat("../", 100) + "x") f.Fuzz(func(t *testing.T, name string) { got := SanitiseFilename(name) // Whatever the input, the result must be usable as a bare base name. for _, forbidden := range []string{"/", "\\", "\x00"} { if strings.Contains(got, forbidden) { t.Fatalf("SanitiseFilename(%q) = %q, containing %q", name, got, forbidden) } } if got == "." || got == ".." || strings.HasPrefix(got, ".") { t.Fatalf("SanitiseFilename(%q) = %q, which is a relative or hidden path", name, got) } if len(got) > 200 { t.Fatalf("SanitiseFilename(%q) produced %d characters", name, len(got)) } }) } func TestStripControlRemovesInvisibleCharacters(t *testing.T) { got := stripControl(" hello\x00\x07world\x1b ") if strings.ContainsAny(got, "\x00\x07\x1b") { t.Fatalf("control characters survived: %q", got) } if got != "helloworld" { t.Fatalf("stripControl produced %q", got) } // Newlines and tabs are legitimate in a pasted note and must survive. if stripControl("line one\nline two\tindented") != "line one\nline two\tindented" { t.Fatal("stripControl removed a legitimate newline or tab") } } func TestMetadataKeyPatternRejectsOperatorShapedKeys(t *testing.T) { valid := []string{"project", "department", "a", "a1", "a.b", "a-b", "a_b", strings.Repeat("k", 64)} for _, k := range valid { if !metadataKeyPattern.MatchString(k) { t.Errorf("valid metadata key %q was rejected", k) } } // A key MongoDB would treat specially, or that carries markup, must not // reach storage. invalid := []string{ "", "$ne", "$where", "$gt", ".leading", "-leading", "_leading", "has space", "has\nnewline", "