Detect a fetched image's format from its bytes once (closes #319)
check / check (push) Canceled after 0s

magic.DetectFormat returns an imagerequest.Format. ValidateMagicBytes
returns the format it checked against the upstream Content-Type. The
cache stores that format in the source's row, so a variant made from a
cached source uses it too. The proxy hands it to Process, which still
refuses input libvips would load with another loader, such as an SVG
passing as AVIF. Format.MIMEType declares each MIME string once, and the
fetcher, the cache's variant read and the Accept matching use it.
magic.MIMEType, magic.ImageFormat, MIMEToImageFormat, magic's SVG
detection, detectFormat, formatFromString, FormatToMIME and
Result.InputFormat are gone.

Model: opus-5-5
This commit is contained in:
2026-10-26 19:46:01 +00:00
parent ac850823f4
commit adfb6ab50a
39 changed files with 430 additions and 594 deletions
+24 -171
View File
@@ -1,9 +1,10 @@
package magic
import (
"bytes"
"errors"
"testing"
"sneak.berlin/go/pixa/internal/imagerequest"
)
// Shared test fixture strings.
@@ -12,9 +13,6 @@ const (
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.
@@ -36,37 +34,22 @@ func TestDetectFormat(t *testing.T) {
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 string
data []byte
wantFormat imagerequest.Format
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: "JPEG", data: jpeg, wantFormat: imagerequest.FormatJPEG},
{name: "PNG", data: png, wantFormat: imagerequest.FormatPNG},
{name: "GIF87a", data: gif87a, wantFormat: imagerequest.FormatGIF},
{name: "GIF89a", data: gif89a, wantFormat: imagerequest.FormatGIF},
{name: "WebP", data: webp, wantFormat: imagerequest.FormatWebP},
{name: "AVIF", data: avif, wantFormat: imagerequest.FormatAVIF},
{name: "AVIF sequence", data: avis, wantFormat: imagerequest.FormatAVIF},
{
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: "SVG",
data: []byte(`<svg xmlns="http://www.w3.org/2000/svg"></svg>`),
wantErr: ErrUnknownFormat,
},
{
name: "unknown format",
@@ -88,8 +71,8 @@ func TestDetectFormat(t *testing.T) {
return
}
if got != tt.wantMIME {
t.Errorf("DetectFormat() = %v, want %v", got, tt.wantMIME)
if got != tt.wantFormat {
t.Errorf("DetectFormat() = %v, want %v", got, tt.wantFormat)
}
})
}
@@ -105,24 +88,28 @@ func TestValidateMagicBytes(t *testing.T) {
name string
data []byte
declaredType string
wantFormat imagerequest.Format
wantErr error
}{
{
name: "matching JPEG",
data: jpegData,
declaredType: testMIMEJPEG,
wantFormat: imagerequest.FormatJPEG,
wantErr: nil,
},
{
name: "matching JPEG with params",
data: jpegData,
declaredType: testMIMEJPEGParams,
wantFormat: imagerequest.FormatJPEG,
wantErr: nil,
},
{
name: "matching PNG",
data: pngData,
declaredType: testMIMEPNG,
wantFormat: imagerequest.FormatPNG,
wantErr: nil,
},
{
@@ -143,45 +130,14 @@ func TestValidateMagicBytes(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := ValidateMagicBytes(tt.data, tt.declaredType)
got, 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)
t.Errorf("ValidateMagicBytes() = %q, want %q", got, tt.wantFormat)
}
})
}
@@ -214,109 +170,6 @@ func TestNormalizeMIMEType(t *testing.T) {
}
}
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()