From cdb9c02aaa4496b214fc2b221fe318a6e3004eb9 Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Tue, 13 Oct 2026 07:52:48 +0000 Subject: [PATCH] Refuse a fit=outside size whose output exceeds the maximum (closes #257) fit=outside scales the source to cover the requested size, and the side scaled past it was held to no maximum: 1x8192?fit=outside of a 3000x600 source was resized to 40960x8192 and encoded. resize now refuses such an output with ErrOutputTooLarge before resizing, so both image routes answer it 400 with the error a size with one side 0 already gets. The other fit modes make the requested size or one within it. README.md and TODO.md say so. Model: opus-5-5 --- README.md | 4 +++- TODO.md | 7 +++++++ internal/imageprocessor/imageprocessor.go | 16 +++++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9525dcc..a9d14e1 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,9 @@ once, is refused with 400. A size with one side `0` has that side scaled to keep the source's proportions, and is refused with 400 when that side would come out above 8192, the most -either side may be. +either side may be; so is a size with `fit=outside`, which scales the image to +cover the size and so past it on one side, when that side would come out +above 8192. The source image may be JPEG, PNG, GIF, WebP, AVIF or JPEG XL. The upstream's `Content-Type` must name its format, and the image's first bytes must match it. diff --git a/TODO.md b/TODO.md index 2ec0e84..190ecec 100644 --- a/TODO.md +++ b/TODO.md @@ -30,6 +30,13 @@ P2: security: per-origin rate limiting # Completed Steps +- 2026-10-13 `fit=outside` no longer makes an output above 8192 on a side + (closes #257): it scales the image to cover the requested size, and the side + scaled past that size was held to no maximum, so `1x8192?fit=outside` of a + 3000x600 source was resized to 40960x8192 and encoded. The image processor now + refuses such a size with `ErrOutputTooLarge` before resizing, and both image + routes answer it 400, as for a size with one side `0`. The other fit modes + make the requested size or one within it, so none can go above 8192. - 2026-10-13 a size with one side `0` no longer makes an output above 8192 on a side (closes #241): that side is computed from the source's proportions, and nothing held it to the 8192 a requested side is held to, so `0x8192` of a diff --git a/internal/imageprocessor/imageprocessor.go b/internal/imageprocessor/imageprocessor.go index 4ad8007..92cee8a 100644 --- a/internal/imageprocessor/imageprocessor.go +++ b/internal/imageprocessor/imageprocessor.go @@ -137,8 +137,10 @@ var ErrInputDataTooLarge = errors.New("input data exceeds maximum allowed size") // so of a requested size (imgcache.MaxDimension). const MaxOutputDimension = 8192 -// ErrOutputTooLarge is returned when a size with one side 0 would give that -// side, computed from the input's proportions, more than MaxOutputDimension. +// ErrOutputTooLarge is returned, before resizing, when the output would have a +// side above MaxOutputDimension: the side of a size with one side 0, computed +// from the input's proportions, or with FitOutside, the side scaled past the +// requested size. var ErrOutputTooLarge = errors.New("output image dimensions exceed maximum") // ErrUnsupportedOutputFormat is returned when the requested output format is @@ -460,7 +462,9 @@ func (p *ImageProcessor) detectFormat(imageType vips.ImageType) string { } } -// resize resizes the image according to the fit mode. +// resize resizes the image according to the fit mode. With FitOutside one +// side can come out larger than requested; when it would be above +// MaxOutputDimension, resize returns ErrOutputTooLarge without resizing. func (p *ImageProcessor) resize( img *vips.ImageRef, width, height int, fit FitMode, ) error { @@ -508,6 +512,12 @@ func (p *ImageProcessor) resize( newW := int(float64(imgW) * scale) newH := int(float64(imgH) * scale) + // The dimension that exceeds the target is held to the maximum too + if newW > MaxOutputDimension || newH > MaxOutputDimension { + return fmt.Errorf("%w: %dx%d is above %d", ErrOutputTooLarge, + newW, newH, MaxOutputDimension) + } + return img.Thumbnail(newW, newH, vips.InterestingNone) default: