package avatar import ( "bytes" "mime/multipart" "net/textproto" "os" "path/filepath" "testing" "github.com/google/uuid" ) func TestResolveAbsRejectsTraversal(t *testing.T) { dir := t.TempDir() id := uuid.New() name := id.String() + ".jpg" if err := os.WriteFile(filepath.Join(dir, name), []byte("x"), 0o644); err != nil { t.Fatal(err) } if _, err := ResolveAbs(dir, "../"+name); err == nil { t.Fatal("expected reject ..") } if _, err := ResolveAbs(dir, "not-a-uuid.jpg"); err == nil { t.Fatal("expected reject bad id") } if _, err := ResolveAbs(dir, id.String()+".gif"); err == nil { t.Fatal("expected reject gif") } full, err := ResolveAbs(dir, name) if err != nil { t.Fatal(err) } if filepath.Base(full) != name { t.Fatalf("full=%s", full) } } func TestStoreRejectsBadTypeAndSize(t *testing.T) { dir := t.TempDir() uid := uuid.New() fhExe := mustFormFile(t, "x.exe", "application/octet-stream", []byte("MZ")) if _, err := Store(dir, uid, fhExe); err == nil { t.Fatal("expected bad type") } big := bytes.Repeat([]byte("a"), MaxBytes+10) fhBig := mustFormFile(t, "big.jpg", "image/jpeg", big) if _, err := Store(dir, uid, fhBig); err == nil { t.Fatal("expected too large") } } func TestStoreOKAndOverwrite(t *testing.T) { dir := t.TempDir() uid := uuid.New() fh := mustFormFile(t, "a.png", "image/png", []byte{0x89, 0x50, 0x4e, 0x47}) path, err := Store(dir, uid, fh) if err != nil { t.Fatal(err) } want := "/api/v1/media/avatars/" + uid.String() + ".png" if path != want { t.Fatalf("path=%s want=%s", path, want) } if _, err := os.Stat(filepath.Join(dir, uid.String()+".png")); err != nil { t.Fatal(err) } fh2 := mustFormFile(t, "b.jpg", "image/jpeg", []byte{0xff, 0xd8, 0xff}) path2, err := Store(dir, uid, fh2) if err != nil { t.Fatal(err) } if path2 != "/api/v1/media/avatars/"+uid.String()+".jpg" { t.Fatalf("path2=%s", path2) } if _, err := os.Stat(filepath.Join(dir, uid.String()+".png")); !os.IsNotExist(err) { t.Fatal("old png should be removed") } } func mustFormFile(t *testing.T, filename, contentType string, data []byte) *multipart.FileHeader { t.Helper() var buf bytes.Buffer w := multipart.NewWriter(&buf) h := make(textproto.MIMEHeader) h.Set("Content-Disposition", `form-data; name="file"; filename="`+filename+`"`) h.Set("Content-Type", contentType) part, err := w.CreatePart(h) if err != nil { t.Fatal(err) } if _, err := part.Write(data); err != nil { t.Fatal(err) } if err := w.Close(); err != nil { t.Fatal(err) } r := multipart.NewReader(&buf, w.Boundary()) form, err := r.ReadForm(int64(len(data)) + 1024) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = form.RemoveAll() }) files := form.File["file"] if len(files) == 0 { t.Fatal("no file part") } return files[0] }