diff --git a/README.md b/README.md index d1ab548..49c74bb 100644 --- a/README.md +++ b/README.md @@ -192,9 +192,10 @@ path under `/v1/` answers 200, in maintenance mode too. address in a blocked network (see `blocked_networks`); 429 over `image_requests_per_minute` (below); 415 when the upstream answers with a `Content-Type` that is not one of the source formats (below), or with an image - libvips would not load as one of them; 502 when the upstream answered with an - error status, and for 5 minutes after that for the same source URL; 503 when - pixa is busy or in maintenance mode; 500 for any other failure. + whose first bytes libvips reads as another format; 502 when the upstream + answered with an error status, and for 5 minutes after that for the same + source URL; 503 when pixa is busy or in maintenance mode; 500 for any other + failure. - `GET` or `HEAD` `/v1/e//` — an image through an encrypted URL (see Encrypted URLs). Needs: nothing but the URL. Answers: 200; 304 when `If-None-Match` matches the image's `ETag`; 400 for a token that does not @@ -265,8 +266,8 @@ once, is refused with 400. 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. An upstream answering with any other `Content-Type`, SVG included, is refused -with 415, and so is an image that libvips would not load as one of these -formats, such as an SVG whose first bytes pass the check for AVIF. +with 415, and so is an image whose first bytes libvips reads as another format, +such as an SVG whose first bytes pass the check for AVIF. With the format `auto`, pixa chooses the format for each request from its `Accept` header, in this order: diff --git a/TODO.md b/TODO.md index 6cd9a0e..f239092 100644 --- a/TODO.md +++ b/TODO.md @@ -36,14 +36,16 @@ P2: security: per-origin rate limiting an upstream `Content-Type` that is not one of the source formats, SVG included, where they answered 500. Before decoding, the image processor refuses with `ErrUnsupportedInputFormat`, also answered 415, any input that - govips' `DetermineImageType`, which picks the libvips loader, does not name as - a source format. That catches an SVG whose first bytes pass the first-bytes - check for another type, as an XML comment can make them do for AVIF; an SVG - whose first bytes do not pass that check answers 500, as before. `orig` is - always the source's own format, where a source the processor could not name - was saved as JPEG. `README.md` no longer lists SVG. The SVG detection in - `internal/magic` stays: the request path does not call it, and it goes with - the unused code of https://git.eeqj.de/sneak/pixa/issues/168. + govips' `DetermineImageType`, which picks the libvips loader from the first + bytes, does not name as a source format. That catches an SVG whose first bytes + pass the first-bytes check for another type, as an XML comment can make them + do for AVIF; an SVG whose first bytes do not pass that check answers 500, as + before, and so does a damaged image in a source format, whose first bytes + `DetermineImageType` names. `orig` is always the source's own format, where a + source the processor could not name was saved as JPEG. `README.md` no longer + lists SVG. The SVG detection in `internal/magic` stays: the request path does + not call it, and it goes with the unused code of + https://git.eeqj.de/sneak/pixa/issues/168. - 2026-10-12 per-client limit on the image routes (closes #236): the new setting `image_requests_per_minute` (`PIXA_IMAGE_REQUESTS_PER_MINUTE`), default 600, is how many `GET` and `HEAD` requests one client may send to `/v1/image/` and diff --git a/internal/imageprocessor/imageprocessor.go b/internal/imageprocessor/imageprocessor.go index 1898ce7..9f08387 100644 --- a/internal/imageprocessor/imageprocessor.go +++ b/internal/imageprocessor/imageprocessor.go @@ -138,7 +138,9 @@ var ErrInputDataTooLarge = errors.New("input data exceeds maximum allowed size") var ErrUnsupportedOutputFormat = errors.New("unsupported output format") // ErrUnsupportedInputFormat is returned, before the input is decoded, when -// libvips would not load it as one of the source formats. +// libvips reads the input's first bytes as another format than the source +// formats, or as none: for example an SVG whose first bytes pass the +// first-bytes check for AVIF. var ErrUnsupportedInputFormat = errors.New("unsupported input format") // ErrTooManyImages is returned when MaxConcurrentProcessing images are being @@ -223,9 +225,10 @@ func (p *ImageProcessor) Process( return nil, ErrInputDataTooLarge } - // govips picks the libvips loader with DetermineImageType. Input it - // would not load as a source format, such as SVG, which librsvg would - // render, is refused before libvips reads it. + // govips picks the libvips loader from the input's first bytes with + // DetermineImageType. Input whose first bytes it reads as no source + // format, such as SVG, which librsvg would render, is refused before + // libvips reads it. inputFormat := p.detectFormat(vips.DetermineImageType(data)) sourceFormat, err := p.formatFromString(inputFormat) diff --git a/internal/imageprocessor/orig_format_internal_test.go b/internal/imageprocessor/orig_format_internal_test.go index f788b7c..453d176 100644 --- a/internal/imageprocessor/orig_format_internal_test.go +++ b/internal/imageprocessor/orig_format_internal_test.go @@ -8,9 +8,9 @@ import ( "github.com/davidbyttow/govips/v2/vips" ) -// TestImageProcessor_OrigOfUnnamedFormatRefused verifies that orig of an -// input in a format the processor decodes but cannot name, here TIFF, is -// an error, and not the image saved as JPEG. +// TestImageProcessor_OrigOfUnnamedFormatRefused verifies that orig of a TIFF +// input, which is not a source format, is refused with +// ErrUnsupportedInputFormat before it is decoded, and not saved as JPEG. func TestImageProcessor_OrigOfUnnamedFormatRefused(t *testing.T) { t.Parallel()