A 250-char multipart filename was accepted and echoed verbatim in Content-Disposition. SanitizeFilename already truncates; lower the cap from 255 to 128 so DB rows and header echoes stay bounded (#248).
114 lines
2.8 KiB
Go
114 lines
2.8 KiB
Go
package store
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func testBlobs(t *testing.T) *FsBlobStore {
|
|
t.Helper()
|
|
b, err := NewFsBlobStore(t.TempDir() + "/files")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func TestBlobPutGetStatDeleteRoundtrip(t *testing.T) {
|
|
b := testBlobs(t)
|
|
data := []byte("hello attachment world")
|
|
sha, size, err := b.Put("abc123/pending", strings.NewReader(string(data)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if size != int64(len(data)) {
|
|
t.Fatalf("size = %d want %d", size, len(data))
|
|
}
|
|
if len(sha) != 64 {
|
|
t.Fatalf("sha256 = %q", sha)
|
|
}
|
|
// canonical key is <paste-id>/<sha256>
|
|
got, err := b.Get("abc123/" + sha)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
buf := make([]byte, len(data)+10)
|
|
n, _ := got.Read(buf)
|
|
got.Close()
|
|
if string(buf[:n]) != string(data) {
|
|
t.Fatalf("roundtrip mismatch: %q", buf[:n])
|
|
}
|
|
sz, err := b.Stat("abc123/" + sha)
|
|
if err != nil || sz != int64(len(data)) {
|
|
t.Fatalf("stat = %d, %v", sz, err)
|
|
}
|
|
if err := b.Delete("abc123/" + sha); err != nil {
|
|
t.Fatalf("delete: %v", err)
|
|
}
|
|
if _, err := b.Get("abc123/" + sha); err != ErrBlobNotFound {
|
|
t.Fatalf("get after delete: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBlobStatMissing(t *testing.T) {
|
|
b := testBlobs(t)
|
|
if _, err := b.Stat("nope/deadbeef"); err != ErrBlobNotFound {
|
|
t.Fatalf("want ErrBlobNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBlobTraversalPrevention(t *testing.T) {
|
|
b := testBlobs(t)
|
|
evil := []string{
|
|
"../../etc/passwd",
|
|
"../escape",
|
|
"..\\windows",
|
|
"/abs/path",
|
|
"a/b/c", // too many segments
|
|
"onlyone", // no slash
|
|
"./relative", // dot segment
|
|
"../..", // bare traversal
|
|
"ok/../traverse", // traversal inside
|
|
}
|
|
for _, key := range evil {
|
|
if _, _, err := b.Put(key, strings.NewReader("x")); err == nil {
|
|
t.Errorf("Put accepted evil key %q", key)
|
|
}
|
|
if _, err := b.Get(key); err == nil {
|
|
t.Errorf("Get accepted evil key %q", key)
|
|
}
|
|
if err := b.Delete(key); err == nil {
|
|
t.Errorf("Delete accepted evil key %q", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSanitizeFilename(t *testing.T) {
|
|
cases := [][2]string{
|
|
{"../../etc/passwd", "passwd"},
|
|
{"C:\\Users\\evil\\file.txt", "file.txt"},
|
|
{"normal.txt", "normal.txt"},
|
|
{"a<b>c", "a<b>c"},
|
|
{"", "file"},
|
|
{"..", "file"},
|
|
{".hidden", ".hidden"},
|
|
{"with\x00null.txt", "withnull.txt"},
|
|
{"new\nline.txt", "newline.txt"},
|
|
}
|
|
for _, c := range cases {
|
|
got := SanitizeFilename(c[0])
|
|
if got != c[1] {
|
|
t.Errorf("SanitizeFilename(%q) = %q want %q", c[0], got, c[1])
|
|
}
|
|
}
|
|
long := strings.Repeat("x", 300)
|
|
if got := SanitizeFilename(long); len(got) != MaxFilenameLen {
|
|
t.Errorf("long name len = %d want %d", len(got), MaxFilenameLen)
|
|
}
|
|
// issue #248: a 250-char multipart filename must truncate to the cap
|
|
repro := strings.Repeat("b", 246) + ".txt"
|
|
if got := SanitizeFilename(repro); len(got) != MaxFilenameLen {
|
|
t.Errorf("repro name len = %d want %d", len(got), MaxFilenameLen)
|
|
}
|
|
}
|