Compare commits

..
2 Commits
Author SHA1 Message Date
clawbot 3c40863c2e Refuse a size whose computed side exceeds the maximum (closes #241)
check / check (push) Canceled after 0s
A side given as 0 is computed from the source's proportions, and
nothing held it to 8192: 0x8192 of a 3000x600 source was resized to
40960x8192 and encoded. Process now refuses such a size with
ErrOutputTooLarge once it knows the source's dimensions, before the
resize and the encode, and both image routes answer it 400 with the
JSON error body. It is a processing error, not a failed fetch, so it
is never negative-cached. imgcache.MaxDimension is now
imageprocessor.MaxOutputDimension, so the request limit and the output
limit are one value. README.md says it under Routes and Encrypted URLs.

Model: opus-5-5
2026-10-13 07:08:17 +00:00
clawbot ba1930c5cc Test that a size whose computed side exceeds the maximum is refused (part of #241)
Failing tests first. Through both image routes, 0x8192 of a 3000x600
source and 8192x0 of a 600x3000 source must answer 400 with the JSON
error body; orig, a size with both sides given and a 0 side whose
computed side stays within 8192 must be served as before, after the
refusals. In the image processor, a 0 side computed to exactly 8192 is
kept and one above is refused with ErrOutputTooLarge, which does not
exist yet.

Model: opus-5-5
2026-10-13 07:07:49 +00:00
2 changed files with 35 additions and 25 deletions

No files matched your search

+22 -18
View File
@@ -267,14 +267,10 @@ func (p *ImageProcessor) Process(
return nil, ErrInputTooLarge return nil, ErrInputTooLarge
} }
// Determine target dimensions // Determine target dimensions; too large ones are refused before resizing
targetWidth, targetHeight := targetDimensions(req.Size, origWidth, origHeight) targetWidth, targetHeight, err := targetDimensions(req.Size, origWidth, origHeight)
if err != nil {
// A side given as 0 is computed from the input's proportions, so it can return nil, err
// come out above MaxOutputDimension; refuse it before resizing
if targetWidth > MaxOutputDimension || targetHeight > MaxOutputDimension {
return nil, fmt.Errorf("%w: %dx%d is above %d", ErrOutputTooLarge,
targetWidth, targetHeight, MaxOutputDimension)
} }
// Resize if needed // Resize if needed
@@ -311,21 +307,29 @@ func (p *ImageProcessor) Process(
// targetDimensions calculates the output dimensions for a requested size, // targetDimensions calculates the output dimensions for a requested size,
// scaling proportionally when only one dimension is given and keeping the // scaling proportionally when only one dimension is given and keeping the
// original dimensions when both are zero. // original dimensions when both are zero. A side scaled proportionally can
func targetDimensions(size Size, origWidth, origHeight int) (int, int) { // come out above MaxOutputDimension, which is ErrOutputTooLarge.
func targetDimensions(size Size, origWidth, origHeight int) (int, int, error) {
width, height := size.Width, size.Height
switch { switch {
case size.Width == 0 && size.Height == 0: case width == 0 && height == 0:
// Both are 0: keep original size // Both are 0: keep original size
return origWidth, origHeight return origWidth, origHeight, nil
case size.Width == 0: case width == 0:
// Only height specified: calculate width proportionally // Only height specified: calculate width proportionally
return origWidth * size.Height / origHeight, size.Height width = origWidth * height / origHeight
case size.Height == 0: case height == 0:
// Only width specified: calculate height proportionally // Only width specified: calculate height proportionally
return size.Width, origHeight * size.Width / origWidth height = origHeight * width / origWidth
default:
return size.Width, size.Height
} }
if width > MaxOutputDimension || height > MaxOutputDimension {
return 0, 0, fmt.Errorf("%w: %dx%d is above %d", ErrOutputTooLarge,
width, height, MaxOutputDimension)
}
return width, height, nil
} }
// MIME types for the supported image formats. // MIME types for the supported image formats.
+13 -7
View File
@@ -22,6 +22,12 @@ import (
func TestImageProxyRefusesComputedSideAboveMaximum(t *testing.T) { func TestImageProxyRefusesComputedSideAboveMaximum(t *testing.T) {
t.Parallel() t.Parallel()
// The upstream paths of the two sources
const (
widePath = "/wide.png"
tallPath = "/tall.png"
)
wide := encodeTestPNG(t, 3000, 600) wide := encodeTestPNG(t, 3000, 600)
tall := encodeTestPNG(t, 600, 3000) tall := encodeTestPNG(t, 600, 3000)
@@ -29,7 +35,7 @@ func TestImageProxyRefusesComputedSideAboveMaximum(t *testing.T) {
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/png") w.Header().Set("Content-Type", "image/png")
if r.URL.Path == "/tall.png" { if r.URL.Path == tallPath {
_, _ = w.Write(tall) _, _ = w.Write(tall)
return return
@@ -55,12 +61,12 @@ func TestImageProxyRefusesComputedSideAboveMaximum(t *testing.T) {
// wantWidth and wantHeight are the served image's, 0 for a refusal // wantWidth and wantHeight are the served image's, 0 for a refusal
wantWidth, wantHeight int wantWidth, wantHeight int
}{ }{
{"/wide.png", "0x8192", 0, 8192, 0, 0}, {widePath, "0x8192", 0, 8192, 0, 0},
{"/tall.png", "8192x0", 8192, 0, 0, 0}, {tallPath, "8192x0", 8192, 0, 0, 0},
{"/wide.png", "orig", 0, 0, 3000, 600}, {widePath, "orig", 0, 0, 3000, 600},
{"/wide.png", "300x200", 300, 200, 300, 200}, {widePath, "300x200", 300, 200, 300, 200},
{"/wide.png", "0x300", 0, 300, 1500, 300}, {widePath, "0x300", 0, 300, 1500, 300},
{"/tall.png", "300x0", 300, 0, 300, 1500}, {tallPath, "300x0", 300, 0, 300, 1500},
} }
for _, tt := range tests { for _, tt := range tests {