Detect a fetched image's format from its bytes once (closes #319) #336

Merged
clawbot merged 1 commits from issue-319-format-from-bytes-once into next 2026-10-26 21:21:40 +01:00
Collaborator

Implements #319.

A fetched image's format is now detected once, by magic, as an imagerequest.Format.

  • magic.ValidateMagicBytes returns the format it checked against the upstream Content-Type. The fetcher's Content-Type check stays as the filter before downloading.
  • Cache.Source returns a cached source's format, read from its file's first bytes as Cache.Variant reads a variant's. A file in no source format is an error, and the proxy fetches the source again.
  • The proxy hands the format to ImageProcessor.Process.
  • Format.MIMEType declares each MIME string once. The fetcher, the mock fetcher, the cache's read of a variant's type and the auto Accept matching use it.
  • An undetectable source is magic.ErrUnknownFormat, answered 415 as before.

What the diff does not show: the processor still compares govips' DetermineImageType with the loader of the format it is given. govips picks the libvips loader that way, so this check keeps an SVG passing as AVIF away from librsvg. It only refuses.

Disclosures:

  • magic no longer detects SVG. pixa does not read SVG, and SVG data is now ErrUnknownFormat, still answered 415.
  • eviction.go held no MIME literals at this next, so nothing changed there.
  • The 406 message and the load test origin's Content-Type still spell MIME types as text.
  • Tests follow the new signatures. A handler test's fake processor error follows the new wording. The TIFF test passes its input as JPEG.

Model: opus-5-5

Implements https://git.eeqj.de/sneak/pixa/issues/319. A fetched image's format is now detected once, by `magic`, as an `imagerequest.Format`. - `magic.ValidateMagicBytes` returns the format it checked against the upstream `Content-Type`. The fetcher's `Content-Type` check stays as the filter before downloading. - `Cache.Source` returns a cached source's format, read from its file's first bytes as `Cache.Variant` reads a variant's. A file in no source format is an error, and the proxy fetches the source again. - The proxy hands the format to `ImageProcessor.Process`. - `Format.MIMEType` declares each MIME string once. The fetcher, the mock fetcher, the cache's read of a variant's type and the `auto` `Accept` matching use it. - An undetectable source is `magic.ErrUnknownFormat`, answered 415 as before. What the diff does not show: the processor still compares govips' `DetermineImageType` with the loader of the format it is given. govips picks the libvips loader that way, so this check keeps an SVG passing as AVIF away from librsvg. It only refuses. Disclosures: - `magic` no longer detects SVG. pixa does not read SVG, and SVG data is now `ErrUnknownFormat`, still answered 415. - `eviction.go` held no MIME literals at this `next`, so nothing changed there. - The 406 message and the load test origin's `Content-Type` still spell MIME types as text. - Tests follow the new signatures. A handler test's fake processor error follows the new wording. The TIFF test passes its input as JPEG. Model: opus-5-5
clawbot added the needs-review label 2026-10-26 20:46:25 +01:00
clawbot self-assigned this 2026-10-26 20:46:25 +01:00
Author
Collaborator

FAIL: needs-rework

  1. internal/db/migrations/001_schema.sql, internal/imgcache/cache.go (Source, PutSource): the new format column is a second record of what the cached file's first bytes already say. Cache.Variant reads a variant's format from its file with readFormat, and Source can do the same on the file it opens. The two records can disagree. Two variants of one source can fetch it at the same time, and when the upstream changes the image's format between those fetches, one fetch's file can end up under the other's row. Every new variant of that source then fails with 415, and the source is not fetched again while it stays cached. Fixtures that hold bytes in no image format are a test convenience, not a reason for a schema column. Acceptable: no format column and no format argument to PutSource; Source returns the format readFormat reads from the file, and a file in no source format is an error, on which the proxy already fetches again; the README.md sentence, the schema comment and the TODO.md entry follow; the fixtures that Source reads back start with an image's first bytes.

  2. internal/imageprocessor/imageprocessor.go, the check in Process: for a sourceFormat with no loader (orig, auto or empty), vipsImageType returns vips.ImageTypeUnknown, the value govips also gives input it cannot read. Such input passes the check and fails later as a decode error, answered 500, although the doc of ErrUnsupportedInputFormat says input libvips reads as no format is refused. Acceptable: a sourceFormat that is not one of the six source formats is refused with ErrUnsupportedInputFormat before the comparison, with a test.

Judgement call: the 406 message in internal/handlers/format_auto.go keeps its MIME types as text, as a message to the client; not a finding.
Judgement call: the load test origin's Content-Type header stays a literal, as what a stand-in upstream sends; not a finding.

Model: opus-5-5

**FAIL: needs-rework** 1. `internal/db/migrations/001_schema.sql`, `internal/imgcache/cache.go` (`Source`, `PutSource`): the new `format` column is a second record of what the cached file's first bytes already say. `Cache.Variant` reads a variant's format from its file with `readFormat`, and `Source` can do the same on the file it opens. The two records can disagree. Two variants of one source can fetch it at the same time, and when the upstream changes the image's format between those fetches, one fetch's file can end up under the other's row. Every new variant of that source then fails with 415, and the source is not fetched again while it stays cached. Fixtures that hold bytes in no image format are a test convenience, not a reason for a schema column. Acceptable: no `format` column and no format argument to `PutSource`; `Source` returns the format `readFormat` reads from the file, and a file in no source format is an error, on which the proxy already fetches again; the `README.md` sentence, the schema comment and the `TODO.md` entry follow; the fixtures that `Source` reads back start with an image's first bytes. 2. `internal/imageprocessor/imageprocessor.go`, the check in `Process`: for a `sourceFormat` with no loader (`orig`, `auto` or empty), `vipsImageType` returns `vips.ImageTypeUnknown`, the value govips also gives input it cannot read. Such input passes the check and fails later as a decode error, answered 500, although the doc of `ErrUnsupportedInputFormat` says input libvips reads as no format is refused. Acceptable: a `sourceFormat` that is not one of the six source formats is refused with `ErrUnsupportedInputFormat` before the comparison, with a test. Judgement call: the 406 message in `internal/handlers/format_auto.go` keeps its MIME types as text, as a message to the client; not a finding. Judgement call: the load test origin's `Content-Type` header stays a literal, as what a stand-in upstream sends; not a finding. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-26 20:56:13 +01:00
clawbot added 1 commit 2026-10-26 21:09:10 +01:00
magic.DetectFormat returns an imagerequest.Format. ValidateMagicBytes
returns the format it checked against the upstream Content-Type. The
cache reads a cached source's format from its file's first bytes, as it
reads a variant's. The proxy hands the format to Process. Process
refuses a format that is not a source format, and input libvips would
load with another loader, such as an SVG passing as AVIF.
Format.MIMEType declares each MIME string once, and the fetcher, the
cache's variant read and the Accept matching use it. magic.MIMEType,
magic.ImageFormat, MIMEToImageFormat, magic's SVG detection,
detectFormat, formatFromString, FormatToMIME and Result.InputFormat are
gone.

Model: opus-5-5
clawbot force-pushed issue-319-format-from-bytes-once from adfb6ab50a to 4515d09c49 2026-10-26 21:09:10 +01:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-10-26 21:09:22 +01:00
Author
Collaborator

Rework for #336 (comment):

  1. The format column and the format argument of PutSource are gone. Cache.Source returns what readFormat reads from the file and refuses a file in no source format, and the proxy then fetches again. New tests cover both in internal/imgcache and internal/imageproxy. The README.md sentence, the schema comment and the TODO.md entry follow. The fixtures that Source reads back are now real JPEGs, and every assertion is kept. Disclosure: the proxy's own check for an empty cached source is gone, since Source now refuses that file and the proxy logs a warning before fetching again.
  2. Process refuses a source format with no loader with ErrUnsupportedInputFormat before the comparison, which moved into checkInputFormat to keep Process under the complexity limit. TestImageProcessor_NotASourceFormatRefused covers orig, auto and an empty format.

Model: opus-5-5

Rework for https://git.eeqj.de/sneak/pixa/pulls/336#issuecomment-156570: 1. The `format` column and the format argument of `PutSource` are gone. `Cache.Source` returns what `readFormat` reads from the file and refuses a file in no source format, and the proxy then fetches again. New tests cover both in `internal/imgcache` and `internal/imageproxy`. The `README.md` sentence, the schema comment and the `TODO.md` entry follow. The fixtures that `Source` reads back are now real JPEGs, and every assertion is kept. Disclosure: the proxy's own check for an empty cached source is gone, since `Source` now refuses that file and the proxy logs a warning before fetching again. 2. `Process` refuses a source format with no loader with `ErrUnsupportedInputFormat` before the comparison, which moved into `checkInputFormat` to keep `Process` under the complexity limit. `TestImageProcessor_NotASourceFormatRefused` covers `orig`, `auto` and an empty format. Model: opus-5-5
Author
Collaborator

PASS: a fetched or cached image's format now comes from its first bytes once, as an imagerequest.Format, and both findings of #336 (comment) are fixed.

Judgement call: the 406 message in internal/handlers/format_auto.go and the load test origin's Content-Type keep their MIME types as text; not a finding.
Judgement call: the SVG tests removed from internal/magic count as tests of removed code, since SVG data is now ErrUnknownFormat and still answered 415.
Judgement call: an empty cached source is now logged as a warning before it is fetched again, where it was fetched again silently; not a finding.

Model: opus-5-5

**PASS**: a fetched or cached image's format now comes from its first bytes once, as an `imagerequest.Format`, and both findings of https://git.eeqj.de/sneak/pixa/pulls/336#issuecomment-156570 are fixed. Judgement call: the 406 message in `internal/handlers/format_auto.go` and the load test origin's `Content-Type` keep their MIME types as text; not a finding. Judgement call: the SVG tests removed from `internal/magic` count as tests of removed code, since SVG data is now `ErrUnknownFormat` and still answered 415. Judgement call: an empty cached source is now logged as a warning before it is fetched again, where it was fetched again silently; not a finding. Model: opus-5-5
clawbot merged commit b73bbde46e into next 2026-10-26 21:21:40 +01:00
clawbot deleted branch issue-319-format-from-bytes-once 2026-10-26 21:21:40 +01:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/pixa#336