package server import ( "bytes" "encoding/json" "image/jpeg" "net/http" "net/http/httptest" "testing" "sneak.berlin/go/pixa/internal/encurl" "sneak.berlin/go/pixa/internal/imgcache" ) // TestImageProxyRefusesComputedSideAboveMaximum requests sizes of a 3000x600 // and a 600x3000 PNG through both image routes. A size with one side 0, whose // side computed from the source's proportions would be above 8192, is refused // with 400 and the JSON error body. The sizes after those refusals are served // as before, which also shows that a refusal is not cached as a failed fetch of // the source: orig, a size with both sides given, and a size with one side 0 // whose computed side stays within 8192. func TestImageProxyRefusesComputedSideAboveMaximum(t *testing.T) { t.Parallel() wide := encodeTestPNG(t, 3000, 600) tall := encodeTestPNG(t, 600, 3000) upstream := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "image/png") if r.URL.Path == "/tall.png" { _, _ = w.Write(tall) return } _, _ = w.Write(wide) })) t.Cleanup(upstream.Close) s, _, _ := startImageProxy(t, upstream) generator, err := encurl.NewGenerator(testSigningKey) if err != nil { t.Fatalf("encurl.NewGenerator: %v", err) } tests := []struct { path string // size is the size of the /v1/image/ URL, and width and height are // those of the encrypted URL size string width, height int // wantWidth and wantHeight are the served image's, 0 for a refusal wantWidth, wantHeight int }{ {"/wide.png", "0x8192", 0, 8192, 0, 0}, {"/tall.png", "8192x0", 8192, 0, 0, 0}, {"/wide.png", "orig", 0, 0, 3000, 600}, {"/wide.png", "300x200", 300, 200, 300, 200}, {"/wide.png", "0x300", 0, 300, 1500, 300}, {"/tall.png", "300x0", 300, 0, 300, 1500}, } for _, tt := range tests { token, err := generator.Generate(&encurl.Payload{ SourceHost: upstreamHost, SourcePath: tt.path, Width: tt.width, Height: tt.height, Format: imgcache.FormatJPEG, }) if err != nil { t.Fatalf("encurl Generate: %v", err) } for _, target := range []string{ "/v1/image/" + upstreamHost + tt.path + "/" + tt.size + ".jpeg", "/v1/e/" + token + "/img.jpg", } { rec := httptest.NewRecorder() s.ServeHTTP(rec, httptest.NewRequestWithContext( t.Context(), http.MethodGet, target, nil)) t.Logf("GET %s: %d, Content-Type %s", target, rec.Code, rec.Header().Get("Content-Type")) if tt.wantWidth == 0 { checkSizeRefused(t, rec) } else { checkServedSize(t, rec, tt.wantWidth, tt.wantHeight) } } } } // checkSizeRefused checks that rec answers 400 with the JSON error body that // refuses a size whose side computed from the source's proportions is above // 8192. func checkSizeRefused(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() const wantError = "size too large for the source: " + "the side computed from its proportions is above 8192" var body struct { Error string `json:"error"` Status int `json:"status"` } err := json.Unmarshal(rec.Body.Bytes(), &body) if err != nil { t.Errorf("status %d, body is not JSON: %v", rec.Code, err) return } if rec.Code != http.StatusBadRequest || body.Status != http.StatusBadRequest || body.Error != wantError { t.Errorf("status %d, body %+v, want %d and the error %q", rec.Code, body, http.StatusBadRequest, wantError) } } // checkServedSize checks that rec answers 200 with a JPEG of wantWidth by // wantHeight. func checkServedSize( t *testing.T, rec *httptest.ResponseRecorder, wantWidth, wantHeight int, ) { t.Helper() if rec.Code != http.StatusOK { t.Errorf("status %d, want %d; body %s", rec.Code, http.StatusOK, rec.Body.String()) return } served, err := jpeg.DecodeConfig(bytes.NewReader(rec.Body.Bytes())) if err != nil { t.Errorf("decoding the served JPEG: %v", err) return } if served.Width != wantWidth || served.Height != wantHeight { t.Errorf("served %dx%d, want %dx%d", served.Width, served.Height, wantWidth, wantHeight) } }