The fetcher accepts image/svg+xml and internal/magic detects SVG. orig of an SVG at the orig size is the SVG unchanged, cached as a variant; at another size it is ErrSVGNotOriginalSize (400). Any other format renders it with libvips' thumbnail at the least size covering the output, at most 8192 on a side, so it stays sharp. An SVG's own size is not limited; one with a side over 8192 times the other is ErrSVGTooThin (415). The cache reads up to 1024 first bytes to find a file's format. Tests that pinned the SVG refusal now expect SVG to be read. Render time and memory are unbounded: P1 in TODO.md until the owner decides on #68. Model: opus-5-5
This commit is contained in:
1 parent
7f87aa17ca
commit
d96899eca6
20 files changed
+858
-87
No files matched your search
@@ -4,38 +4,114 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"sneak.berlin/go/pixa/internal/imageproxy"
|
||||
"sneak.berlin/go/pixa/internal/imagerequest"
|
||||
"sneak.berlin/go/pixa/internal/magic"
|
||||
)
|
||||
|
||||
// TestImageProxyRefusesSVG requests an image the upstream answers as SVG,
|
||||
// in its own format (orig) and resized as PNG, through pixa's router,
|
||||
// upstream fetcher and image processor. pixa does not accept SVG as a
|
||||
// source, so each request is answered 415, never with an image.
|
||||
func TestImageProxyRefusesSVG(t *testing.T) {
|
||||
t.Parallel()
|
||||
// testSVG is the SVG the upstream serves in the SVG tests below.
|
||||
const testSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="48">` +
|
||||
`<rect width="64" height="48"/></svg>`
|
||||
|
||||
// startSVGUpstream starts an upstream that answers every request with
|
||||
// testSVG as image/svg+xml, and returns it with its count of requests.
|
||||
func startSVGUpstream(t *testing.T) (*httptest.Server, *atomic.Int64) {
|
||||
t.Helper()
|
||||
|
||||
var requests atomic.Int64
|
||||
|
||||
upstream := httptest.NewServer(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
requests.Add(1)
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
_, _ = io.WriteString(w, `<svg xmlns="http://www.w3.org/2000/svg" `+
|
||||
`width="64" height="48"><rect width="64" height="48"/></svg>`)
|
||||
_, _ = io.WriteString(w, testSVG)
|
||||
}))
|
||||
t.Cleanup(upstream.Close)
|
||||
|
||||
return upstream, &requests
|
||||
}
|
||||
|
||||
// TestImageProxyServesSVGAsItIs requests an SVG source in its own format
|
||||
// (orig) at the orig size through pixa's router, upstream fetcher, image
|
||||
// processor and cache, twice. Both answers are the SVG itself, never another
|
||||
// format, the second from the cache; the upstream is asked once. Scripts in
|
||||
// the SVG cannot run, as the Content-Security-Policy allows no inline
|
||||
// script. orig at another size is refused with 400.
|
||||
func TestImageProxyServesSVGAsItIs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
upstream, requests := startSVGUpstream(t)
|
||||
s, _, _ := startImageProxy(t, upstream)
|
||||
target := "/v1/image/" + upstreamHost + "/drawing.svg/orig.orig"
|
||||
|
||||
for _, wantCache := range []imageproxy.CacheStatus{
|
||||
imageproxy.CacheMiss, imageproxy.CacheHit,
|
||||
} {
|
||||
rec := getImage(t, s, target)
|
||||
|
||||
if rec.Header().Get("Content-Type") != "image/svg+xml" ||
|
||||
rec.Body.String() != testSVG {
|
||||
t.Errorf("GET %s: %s %q, want image/svg+xml %q", target,
|
||||
rec.Header().Get("Content-Type"), rec.Body.String(), testSVG)
|
||||
}
|
||||
|
||||
if got := rec.Header().Get("X-Pixa-Cache"); got != string(wantCache) {
|
||||
t.Errorf("GET %s: X-Pixa-Cache = %q, want %q", target, got, wantCache)
|
||||
}
|
||||
|
||||
policy := rec.Header().Get("Content-Security-Policy")
|
||||
if !strings.Contains(policy, "script-src 'self'") ||
|
||||
strings.Contains(policy, "unsafe-inline") {
|
||||
t.Errorf("GET %s: Content-Security-Policy = %q, want script-src "+
|
||||
"'self' and no unsafe-inline", target, policy)
|
||||
}
|
||||
}
|
||||
|
||||
if requests.Load() != 1 {
|
||||
t.Errorf("upstream requests = %d, want 1", requests.Load())
|
||||
}
|
||||
|
||||
resized := "/v1/image/" + upstreamHost + "/drawing.svg/32x24.orig"
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
s.ServeHTTP(rec, httptest.NewRequestWithContext(
|
||||
t.Context(), http.MethodGet, resized, nil))
|
||||
t.Logf("GET %s: %d %s", resized, rec.Code, rec.Body.String())
|
||||
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Errorf("GET %s: %d %s, want 400", resized, rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestImageProxyRendersSVG requests an SVG source as each raster format
|
||||
// through pixa's router, upstream fetcher and image processor. Each answer
|
||||
// is an image in that format.
|
||||
func TestImageProxyRendersSVG(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
upstream, _ := startSVGUpstream(t)
|
||||
s, _, _ := startImageProxy(t, upstream)
|
||||
|
||||
for _, sizeFormat := range []string{"64x48.orig", "32x24.png"} {
|
||||
target := "/v1/image/" + upstreamHost + "/drawing.svg/" + sizeFormat
|
||||
for _, format := range []imagerequest.Format{
|
||||
imagerequest.FormatJPEG, imagerequest.FormatPNG, imagerequest.FormatGIF,
|
||||
imagerequest.FormatWebP, imagerequest.FormatAVIF, imagerequest.FormatJXL,
|
||||
} {
|
||||
target := "/v1/image/" + upstreamHost + "/drawing.svg/32x24." +
|
||||
string(format)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
s.ServeHTTP(rec, httptest.NewRequestWithContext(
|
||||
t.Context(), http.MethodGet, target, nil))
|
||||
t.Logf("GET %s: %d, Content-Type %s, body %s",
|
||||
target, rec.Code, rec.Header().Get("Content-Type"), rec.Body.String())
|
||||
rec := getImage(t, s, target)
|
||||
detected, err := magic.DetectFormat(rec.Body.Bytes())
|
||||
t.Logf("GET %s: %s, body in %q (%v)", target,
|
||||
rec.Header().Get("Content-Type"), detected, err)
|
||||
|
||||
if rec.Code != http.StatusUnsupportedMediaType {
|
||||
t.Errorf("GET %s status = %d, want %d",
|
||||
target, rec.Code, http.StatusUnsupportedMediaType)
|
||||
if rec.Header().Get("Content-Type") != format.MIMEType() ||
|
||||
err != nil || detected != format {
|
||||
t.Errorf("GET %s: %s, body in %q (%v); want %s", target,
|
||||
rec.Header().Get("Content-Type"), detected, err, format.MIMEType())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user