check / check (push) Waiting to run
Service.Warm, the ImageCache interface, both GenerateSignedURL methods, signature.ParseParams, imagerequest.ParseImagePath, four symbols of internal/magic, the image processor's lists of supported formats, IsEmpty and Count of the allowlist, ssrfSafeDialer, CacheStale, ErrCacheMiss, ErrNegativeCache, ImageResponse.LastModified and the Load methods of ContentStorage and VariantStorage had no caller outside tests. They are gone, with the tests that tested only them. The handlers tests sign their image URLs with generateSignedURL, a test helper that builds the URL as the removed generator did. The SSRF dialer tests call dialSSRFSafe with no operator blocklist, which is all ssrfSafeDialer did. The tests of ParseImagePath and ContentStorage.Load call ParseImageURL and LoadWithSize instead. MIMEToImageFormat, ContentStorage.Store and CleanExpired stay, as pixa calls them. Model: opus-5-5
351 lines
8.6 KiB
Go
351 lines
8.6 KiB
Go
package magic
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
// Shared test fixture strings.
|
|
const (
|
|
testNameEmpty = "empty"
|
|
testMIMEJPEG = "image/jpeg"
|
|
testMIMEJPEGParams = "image/jpeg; charset=utf-8"
|
|
testMIMEPNG = "image/png"
|
|
testMIMEWebP = "image/webp"
|
|
testMIMEGIF = "image/gif"
|
|
testMIMEAVIF = "image/avif"
|
|
)
|
|
|
|
// pad appends zero bytes so data is comfortably above MinMagicBytes.
|
|
func pad(b ...byte) []byte {
|
|
return append(b, make([]byte, 100)...)
|
|
}
|
|
|
|
func TestDetectFormat(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
jpeg := pad(0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01)
|
|
png := pad(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D)
|
|
gif87a := pad(0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0, 0, 0, 0, 0, 0)
|
|
gif89a := pad(0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0, 0, 0, 0, 0, 0)
|
|
// RIFF + size placeholder + WEBP
|
|
webp := pad(0x52, 0x49, 0x46, 0x46, 0, 0, 0, 0, 0x57, 0x45, 0x42, 0x50)
|
|
// box size + ftyp + brand
|
|
avif := pad(0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66)
|
|
avis := pad(0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x73)
|
|
|
|
tests := []struct {
|
|
name string
|
|
data []byte
|
|
wantMIME MIMEType
|
|
wantErr error
|
|
}{
|
|
{name: "JPEG", data: jpeg, wantMIME: MIMETypeJPEG},
|
|
{name: "PNG", data: png, wantMIME: MIMETypePNG},
|
|
{name: "GIF87a", data: gif87a, wantMIME: MIMETypeGIF},
|
|
{name: "GIF89a", data: gif89a, wantMIME: MIMETypeGIF},
|
|
{name: "WebP", data: webp, wantMIME: MIMETypeWebP},
|
|
{name: "AVIF", data: avif, wantMIME: MIMETypeAVIF},
|
|
{name: "AVIF sequence", data: avis, wantMIME: MIMETypeAVIF},
|
|
{
|
|
name: "SVG with XML declaration",
|
|
data: []byte(`<?xml version="1.0"?><svg></svg>`),
|
|
wantMIME: MIMETypeSVG,
|
|
},
|
|
{
|
|
name: "SVG without declaration",
|
|
data: []byte(`<svg xmlns="http://www.w3.org/2000/svg"></svg>`),
|
|
wantMIME: MIMETypeSVG,
|
|
},
|
|
{
|
|
name: "SVG with whitespace",
|
|
data: []byte(` <?xml version="1.0"?><svg></svg>`),
|
|
wantMIME: MIMETypeSVG,
|
|
},
|
|
{
|
|
name: "SVG with BOM",
|
|
data: append([]byte{0xEF, 0xBB, 0xBF}, []byte(`<svg></svg>`)...),
|
|
wantMIME: MIMETypeSVG,
|
|
},
|
|
{
|
|
name: "unknown format",
|
|
data: make([]byte, MinMagicBytes),
|
|
wantErr: ErrUnknownFormat,
|
|
},
|
|
{name: "too short", data: []byte{0xFF, 0xD8}, wantErr: ErrNotEnoughData},
|
|
{name: testNameEmpty, data: []byte{}, wantErr: ErrNotEnoughData},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := DetectFormat(tt.data)
|
|
if !errors.Is(err, tt.wantErr) {
|
|
t.Errorf("DetectFormat() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
return
|
|
}
|
|
|
|
if got != tt.wantMIME {
|
|
t.Errorf("DetectFormat() = %v, want %v", got, tt.wantMIME)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateMagicBytes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
jpegData := pad(0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01)
|
|
pngData := pad(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D)
|
|
|
|
tests := []struct {
|
|
name string
|
|
data []byte
|
|
declaredType string
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "matching JPEG",
|
|
data: jpegData,
|
|
declaredType: testMIMEJPEG,
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "matching JPEG with params",
|
|
data: jpegData,
|
|
declaredType: testMIMEJPEGParams,
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "matching PNG",
|
|
data: pngData,
|
|
declaredType: testMIMEPNG,
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "mismatched type",
|
|
data: jpegData,
|
|
declaredType: testMIMEPNG,
|
|
wantErr: ErrMagicByteMismatch,
|
|
},
|
|
{
|
|
name: "unknown data",
|
|
data: make([]byte, MinMagicBytes),
|
|
declaredType: testMIMEJPEG,
|
|
wantErr: ErrUnknownFormat,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
err := ValidateMagicBytes(tt.data, tt.declaredType)
|
|
|
|
if !errors.Is(err, tt.wantErr) {
|
|
t.Errorf("ValidateMagicBytes() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMIMEToImageFormat(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
mimeType string
|
|
wantFormat ImageFormat
|
|
wantOk bool
|
|
}{
|
|
{testMIMEJPEG, FormatJPEG, true},
|
|
{testMIMEPNG, FormatPNG, true},
|
|
{testMIMEWebP, FormatWebP, true},
|
|
{testMIMEGIF, FormatGIF, true},
|
|
{testMIMEAVIF, FormatAVIF, true},
|
|
{"image/svg+xml", "", false}, // SVG doesn't convert to ImageFormat
|
|
{"image/tiff", "", false},
|
|
{"text/plain", "", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.mimeType, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, ok := MIMEToImageFormat(tt.mimeType)
|
|
|
|
if ok != tt.wantOk {
|
|
t.Errorf("MIMEToImageFormat(%q) ok = %v, want %v", tt.mimeType, ok, tt.wantOk)
|
|
}
|
|
|
|
if got != tt.wantFormat {
|
|
t.Errorf("MIMEToImageFormat(%q) = %v, want %v", tt.mimeType, got, tt.wantFormat)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNormalizeMIMEType(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
input string
|
|
want string
|
|
}{
|
|
{testMIMEJPEG, testMIMEJPEG},
|
|
{"IMAGE/JPEG", testMIMEJPEG},
|
|
{testMIMEJPEGParams, testMIMEJPEG},
|
|
{" image/jpeg ", testMIMEJPEG},
|
|
{"image/jpeg; boundary=something", testMIMEJPEG},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.input, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := normalizeMIMEType(tt.input)
|
|
|
|
if got != tt.want {
|
|
t.Errorf("normalizeMIMEType(%q) = %q, want %q", tt.input, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDetectSVG(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
data string
|
|
want bool
|
|
}{
|
|
{"xml declaration", `<?xml version="1.0"?><svg></svg>`, true},
|
|
{"svg element", `<svg xmlns="http://www.w3.org/2000/svg"></svg>`, true},
|
|
{
|
|
"doctype",
|
|
`<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ` +
|
|
`"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">`,
|
|
true,
|
|
},
|
|
{"with whitespace", `
|
|
<?xml version="1.0"?><svg></svg>`, true},
|
|
{"uppercase", `<SVG></SVG>`, true},
|
|
{"not svg", `<html></html>`, false},
|
|
{"random text", `hello world`, false},
|
|
{testNameEmpty, ``, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := detectSVG([]byte(tt.data))
|
|
|
|
if got != tt.want {
|
|
t.Errorf("detectSVG(%q) = %v, want %v", tt.data, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSkipBOM(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
data []byte
|
|
want []byte
|
|
}{
|
|
{"with BOM", []byte{0xEF, 0xBB, 0xBF, 'h', 'e', 'l', 'l', 'o'}, []byte("hello")},
|
|
{"without BOM", []byte("hello"), []byte("hello")},
|
|
{testNameEmpty, []byte{}, []byte{}},
|
|
{"only BOM", []byte{0xEF, 0xBB, 0xBF}, []byte{}},
|
|
{"partial BOM", []byte{0xEF, 0xBB}, []byte{0xEF, 0xBB}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := skipBOM(tt.data)
|
|
|
|
if !bytes.Equal(got, tt.want) {
|
|
t.Errorf("skipBOM() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRealWorldSVGPatterns(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Test various real-world SVG patterns
|
|
svgPatterns := []string{
|
|
`<?xml version="1.0" encoding="UTF-8"?>
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
<circle cx="50" cy="50" r="40"/>
|
|
</svg>`,
|
|
`<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
</svg>`,
|
|
`<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ` +
|
|
`"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">` + `
|
|
<svg xmlns="http://www.w3.org/2000/svg">
|
|
</svg>`,
|
|
}
|
|
|
|
for i, pattern := range svgPatterns {
|
|
data := []byte(pattern)
|
|
if len(data) < MinMagicBytes {
|
|
// Pad short SVGs for detection
|
|
data = append(data, make([]byte, MinMagicBytes-len(data))...)
|
|
}
|
|
|
|
got, err := DetectFormat(data)
|
|
if err != nil {
|
|
t.Errorf("Pattern %d: DetectFormat() error = %v", i, err)
|
|
|
|
continue
|
|
}
|
|
|
|
if got != MIMETypeSVG {
|
|
t.Errorf("Pattern %d: DetectFormat() = %v, want %v", i, got, MIMETypeSVG)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDetectFormatRIFFNotWebP(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// RIFF container but not WebP (e.g., WAV file)
|
|
wavData := []byte{
|
|
0x52, 0x49, 0x46, 0x46, // RIFF
|
|
0x00, 0x00, 0x00, 0x00, // file size
|
|
0x57, 0x41, 0x56, 0x45, // WAVE (not WEBP)
|
|
}
|
|
|
|
_, err := DetectFormat(wavData)
|
|
if !errors.Is(err, ErrUnknownFormat) {
|
|
t.Errorf("DetectFormat(WAV) error = %v, want %v", err, ErrUnknownFormat)
|
|
}
|
|
}
|
|
|
|
func TestDetectFormatFtypNotAVIF(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// ftyp container but not AVIF (e.g., MP4)
|
|
mp4Data := []byte{
|
|
0x00, 0x00, 0x00, 0x1C, // box size
|
|
0x66, 0x74, 0x79, 0x70, // ftyp
|
|
0x69, 0x73, 0x6F, 0x6D, // isom brand (not avif)
|
|
}
|
|
|
|
_, err := DetectFormat(mp4Data)
|
|
if !errors.Is(err, ErrUnknownFormat) {
|
|
t.Errorf("DetectFormat(MP4) error = %v, want %v", err, ErrUnknownFormat)
|
|
}
|
|
}
|