Refuse a fit=outside size whose output exceeds the maximum (closes #257)
check / check (push) Waiting to run

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
This commit is contained in:
2026-10-13 08:02:05 +00:00
parent 4c82948e19
commit cdb9c02aaa
3 changed files with 23 additions and 4 deletions
+3 -1
View File
@@ -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, 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 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 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. `Content-Type` must name its format, and the image's first bytes must match it.
+7
View File
@@ -30,6 +30,13 @@ P2: security: per-origin rate limiting
# Completed Steps # 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 - 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 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 nothing held it to the 8192 a requested side is held to, so `0x8192` of a
+13 -3
View File
@@ -137,8 +137,10 @@ var ErrInputDataTooLarge = errors.New("input data exceeds maximum allowed size")
// so of a requested size (imgcache.MaxDimension). // so of a requested size (imgcache.MaxDimension).
const MaxOutputDimension = 8192 const MaxOutputDimension = 8192
// ErrOutputTooLarge is returned when a size with one side 0 would give that // ErrOutputTooLarge is returned, before resizing, when the output would have a
// side, computed from the input's proportions, more than MaxOutputDimension. // 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") var ErrOutputTooLarge = errors.New("output image dimensions exceed maximum")
// ErrUnsupportedOutputFormat is returned when the requested output format is // 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( func (p *ImageProcessor) resize(
img *vips.ImageRef, width, height int, fit FitMode, img *vips.ImageRef, width, height int, fit FitMode,
) error { ) error {
@@ -508,6 +512,12 @@ func (p *ImageProcessor) resize(
newW := int(float64(imgW) * scale) newW := int(float64(imgW) * scale)
newH := int(float64(imgH) * 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) return img.Thumbnail(newW, newH, vips.InterestingNone)
default: default: