85 lines
3.0 KiB
Go
85 lines
3.0 KiB
Go
|
|
package handlers
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"strings"
|
|||
|
|
"testing"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// TestSniffImage — белый список форматов по сигнатуре: только фото.
|
|||
|
|
func TestSniffImage(t *testing.T) {
|
|||
|
|
jpeg := []byte{0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10}
|
|||
|
|
png := []byte{0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A, 0x00}
|
|||
|
|
webp := []byte("RIFF\x24\x00\x00\x00WEBPVP8 ")
|
|||
|
|
|
|||
|
|
cases := []struct {
|
|||
|
|
name string
|
|||
|
|
data []byte
|
|||
|
|
contentType string
|
|||
|
|
ext string
|
|||
|
|
}{
|
|||
|
|
{"jpeg", jpeg, "image/jpeg", "jpg"},
|
|||
|
|
{"png", png, "image/png", "png"},
|
|||
|
|
{"webp", webp, "image/webp", "webp"},
|
|||
|
|
{"gif is not photo", []byte("GIF89a"), "", ""},
|
|||
|
|
{"bmp is not photo", []byte("BM\x00\x00"), "", ""},
|
|||
|
|
{"svg text", []byte(`<svg xmlns="http://www.w3.org/2000/svg"></svg>`), "", ""},
|
|||
|
|
{"empty", nil, "", ""},
|
|||
|
|
{"truncated jpeg", []byte{0xFF, 0xD8}, "", ""},
|
|||
|
|
{"riff but not webp", []byte("RIFF\x00\x00\x00\x00WAVEfmt "), "", ""},
|
|||
|
|
}
|
|||
|
|
for _, tc := range cases {
|
|||
|
|
t.Run(tc.name, func(t *testing.T) {
|
|||
|
|
contentType, ext, ok := sniffImage(tc.data)
|
|||
|
|
if tc.contentType == "" {
|
|||
|
|
if ok {
|
|||
|
|
t.Errorf("sniff = (%q, %q, true), want reject", contentType, ext)
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if !ok || contentType != tc.contentType || ext != tc.ext {
|
|||
|
|
t.Errorf("sniff = (%q, %q, %v), want (%q, %q, true)",
|
|||
|
|
contentType, ext, ok, tc.contentType, tc.ext)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestPhotoName — имя файла из multipart: без путей, управляющих
|
|||
|
|
// символов и без бесконечных строк (оно уходит в журнал событий).
|
|||
|
|
func TestPhotoName(t *testing.T) {
|
|||
|
|
cases := []struct {
|
|||
|
|
in string
|
|||
|
|
want string
|
|||
|
|
}{
|
|||
|
|
{"photo.jpg", "photo.jpg"},
|
|||
|
|
{`C:\Users\me\photo.jpg`, "photo.jpg"}, // имя с Windows-путём
|
|||
|
|
{"../../etc/passwd", "passwd"}, // path traversal вырезается
|
|||
|
|
{"a\x00b\x1fc.png", "abc.png"}, // управляющие символы
|
|||
|
|
{"", ""}, // имя не передали
|
|||
|
|
{".", ""}, // path.Base(".") = "."
|
|||
|
|
{strings.Repeat("я", 150) + ".jpg", strings.Repeat("я", 100)}, // обрезка по рунам
|
|||
|
|
}
|
|||
|
|
for _, tc := range cases {
|
|||
|
|
if got := photoName(tc.in); got != tc.want {
|
|||
|
|
t.Errorf("photoName(%q) = %q, want %q", tc.in, got, tc.want)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestNewPhotoHandlerDefaults — лимиты из env могли быть нулём
|
|||
|
|
// (не заданы): хендлер обязан подставить дефолты, а не считать с нулём.
|
|||
|
|
func TestNewPhotoHandlerDefaults(t *testing.T) {
|
|||
|
|
h := NewPhotoHandler(nil, nil, nil, nil, 0, -1)
|
|||
|
|
if h.maxBytes != photoDefaultMaxBytes {
|
|||
|
|
t.Errorf("maxBytes = %d, want %d", h.maxBytes, photoDefaultMaxBytes)
|
|||
|
|
}
|
|||
|
|
if h.maxPer != photoDefaultMaxPer {
|
|||
|
|
t.Errorf("maxPer = %d, want %d", h.maxPer, photoDefaultMaxPer)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
custom := NewPhotoHandler(nil, nil, nil, nil, 4096, 3)
|
|||
|
|
if custom.maxBytes != 4096 || custom.maxPer != 3 {
|
|||
|
|
t.Errorf("custom limits = %d/%d, want 4096/3", custom.maxBytes, custom.maxPer)
|
|||
|
|
}
|
|||
|
|
}
|