Test that an SVG served as AVIF is refused, and that each source format loads

An SVG starting with an XML comment holding "ftypavis" passes the
first-bytes check for AVIF; resized as PNG, it is answered 200 with the
SVG rendered. The new server test asks for it through both image routes,
resized and as orig, and wants 415 for each. The new processor test
checks that an image in each of the six source formats loads and is saved
again in its own format.

Model: opus-5-5
This commit is contained in:
2026-10-13 03:20:48 +00:00
parent 06e537c458
commit d67b6d5051
2 changed files with 117 additions and 0 deletions
@@ -0,0 +1,46 @@
package imageprocessor
import (
"bytes"
"io"
"testing"
)
// TestImageProcessor_LoadsEachSourceFormat verifies that an image in each of
// the six source formats, saved by the processor itself, is loaded and saved
// again in its own format (orig).
func TestImageProcessor_LoadsEachSourceFormat(t *testing.T) {
t.Parallel()
proc := New(Params{})
for _, format := range []Format{
FormatJPEG, FormatPNG, FormatGIF, FormatWebP, FormatAVIF, FormatJXL,
} {
saved, err := proc.Process(t.Context(),
bytes.NewReader(createTestPNG(t, 20, 20)), &Request{Format: format})
if err != nil {
t.Fatalf("saving the test image as %s: %v", format, err)
}
source, err := io.ReadAll(saved.Content)
if err != nil {
t.Fatalf("reading the %s image: %v", format, err)
}
result, err := proc.Process(t.Context(), bytes.NewReader(source),
&Request{Format: FormatOriginal})
if err != nil {
t.Errorf("orig of the %s image: %v", format, err)
continue
}
t.Logf("orig of the %s image: %s", format, result.ContentType)
if result.ContentType != FormatToMIME(format) {
t.Errorf("orig of the %s image is %s, want %s",
format, result.ContentType, FormatToMIME(format))
}
}
}
@@ -0,0 +1,71 @@
package server
import (
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"sneak.berlin/go/pixa/internal/encurl"
"sneak.berlin/go/pixa/internal/imgcache"
)
// svgServedAsAVIF is an SVG whose first bytes pass the first-bytes check for
// AVIF: its XML comment puts "ftypavis" at bytes 4 to 11, where an AVIF file
// has its ftyp box and brand. libvips would load it as SVG.
const svgServedAsAVIF = `<!--ftypavis--><svg xmlns="http://www.w3.org/2000/svg" ` +
`width="64" height="48"><rect width="64" height="48"/></svg>`
// TestImageProxyRefusesSVGServedAsAVIF requests svgServedAsAVIF, which the
// upstream answers as image/avif, through both image routes, resized as PNG
// and in its own format (orig). Each request is answered 415, as for an SVG
// source, never with an image.
func TestImageProxyRefusesSVGServedAsAVIF(t *testing.T) {
t.Parallel()
upstream := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "image/avif")
_, _ = io.WriteString(w, svgServedAsAVIF)
}))
t.Cleanup(upstream.Close)
s, _, _ := startImageProxy(t, upstream)
generator, err := encurl.NewGenerator(testSigningKey)
if err != nil {
t.Fatalf("encurl.NewGenerator: %v", err)
}
encryptedURL := func(format imgcache.ImageFormat) string {
token, err := generator.Generate(&encurl.Payload{
SourceHost: upstreamHost, SourcePath: "/drawing.avif",
Width: 32, Height: 24, Format: format,
})
if err != nil {
t.Fatalf("encurl Generate: %v", err)
}
return "/v1/e/" + token + "/drawing." + string(format)
}
for _, target := range []string{
"/v1/image/" + upstreamHost + "/drawing.avif/32x24.png",
"/v1/image/" + upstreamHost + "/drawing.avif/64x48.orig",
encryptedURL(imgcache.FormatPNG),
encryptedURL(imgcache.FormatOriginal),
} {
rec := httptest.NewRecorder()
s.ServeHTTP(rec, httptest.NewRequestWithContext(
t.Context(), http.MethodGet, target, nil))
t.Logf("GET %s: %d, Content-Type %s, body %q",
target, rec.Code, rec.Header().Get("Content-Type"), rec.Body.String())
if rec.Code != http.StatusUnsupportedMediaType ||
!strings.Contains(rec.Body.String(), "unsupported source format") {
t.Errorf("GET %s: %d %q, want %d unsupported source format",
target, rec.Code, rec.Body.String(), http.StatusUnsupportedMediaType)
}
}
}