Commit Graph
3 Commits
Author SHA1 Message Date
dotCooCoo 5b1f01d036 craft-doc: skip wrong-shaped members without buffering them
- A VectorCraft or PhotoCraft `generator`, and a VectorCraft `preview` or
  `pdf`, are read as they are parsed. A value of the wrong shape is
  skipped element by element with `IgnoredAny` and none of it is kept;
  before, serde's untagged enum buffered the whole value first, with no
  limit on that buffer. The preview and PDF still read as `File::read`
  reads them, the array form of `Image` included.
- A rendition after the JSON of a gzip file is counted against
  `max_total` once, as it is decompressed. A 40 KiB PDF with `max_total`
  at 64 KiB now reads compressed as it does uncompressed. A preview and
  PDF that name the same bytes are counted as two copies.
- A repeated VectorCraft `version` is malformed, not too new, and
  `renditions` refuses a missing or repeated `document`, as `File::read`
  does.
- The README says that a VectorCraft file written as a JSON array is not
  recognized by `sniff`.
2026-11-02 18:33:23 -08:00
dotCooCoo decd8b4794 craft-doc: read envelopes as the format crates and apps do
- renditions checks where the last VectorCraft member after the JSON
  ends against max_total before it inflates anything past the marker,
  and keeps only the members' bytes, not the data before them.
- A ZIP archive whose manifest.json has no format_version gives
  Unrecognized instead of Malformed.
- A PhotoCraft manifest that is a JSON array is refused, as
  read_manifest refuses it.
- renditions and outline refuse a PhotoCraft manifest nested past
  PhotoCraft's limit (manifest_depth(MAX_GROUP_DEPTH), 367 levels).
- The TooNew check reads only the format's own version member, so a
  newer file that also has another format's member is TooNew, not
  Malformed.
- A generator that is not a string reads as None. A repeated VectorCraft
  generator, preview or pdf reads as its last value, as File::read
  reads it.
- The VectorCraft marker must follow the JSON's closing }, ] or ",
  through craft_vectorcraft_format::marker_at.
- sniff reads the names in a whole archive's central directory, which
  info reads too; a head too short to reach it still uses the local
  headers.
2026-11-02 14:19:39 -08:00
.CooCoo fa1a657476 Add craft-doc: read PhotoCraft, VectorCraft and EffectCraft documents without the apps (#404)
* Add craft-doc: read PhotoCraft, VectorCraft and EffectCraft documents without the apps

A new crate, craft-doc, reads the native files of three apps from bytes:
PhotoCraft .pcraft (a ZIP bundle), VectorCraft .vectorcraft (JSON, plain
or gzip, with the version 4 data after the JSON) and EffectCraft .ecproj
(JSON).

- sniff tells which app wrote a file from its first bytes.
- info gives the format version, the writer the file names, and whether
  the version is one the crate reads.
- renditions returns VectorCraft's preview and PDF and PhotoCraft's
  thumbnail and composite, when the file has them.
- outline returns PhotoCraft's canvas and layer tree, VectorCraft's
  artboards and top-level layers, and EffectCraft's compositions with
  their layers.

A file whose version is newer than the crate reads is refused with
Error::TooNew. Limits bound the JSON, each entry, the total bytes
decompressed and the JSON nesting, and each is checked before the bytes
it covers are allocated. ZIP entries are read by a small STORE and
DEFLATE reader in the crate. The crate depends on serde, serde_json,
thiserror and flate2 and builds for wasm32-unknown-unknown.

The tests build every document they read: JSON written by hand, PNG,
ZIP and gzip bytes assembled in the test. doc.yml runs fmt, clippy, the
tests and a wasm32 check.

* ci: do not keep the checkout token in the doc workflow

The checkout step sets persist-credentials: false, as jpeg.yml and media.yml do. No later step in the job uses the token.

* craft-doc: read each format's envelope through its format crate

craft-doc no longer keeps its own copies of the envelope types and
constants. It reads them with craft-pcraft-format (`MANIFEST`,
`FORMAT_VERSION`, `THUMB`, `COMPOSITE`, `check_manifest`),
craft-vectorcraft-format (`Head`, `Image`, `sniff`, `check`, `VERSION`,
`BLOB_MAGIC`, base64) and craft-ecproj-format (`Head::read`,
`SCHEMA_VERSION`). Its own base64 module is removed. A `.pcraft`
manifest is still read in one pass without building a
`serde_json::Value`, with the checks `read_manifest` applies. The
outlines are still read with craft-doc's read-only views of each
document, and the public API (`sniff`, `info`, `renditions`, `outline`,
`Limits`) does not change.

These inputs are now read as the format crates read them:

- A `.pcraft` manifest without `generator`, or with a `thumbnail` other
  than `thumb.png` or a `composite` other than `composite/preview.png`,
  is malformed for `renditions` and `outline`; `info` still reports its
  version. A manifest with more than 4,194,304 JSON items is refused
  with `LimitExceeded`.
- A `.vectorcraft` preview or PDF member without `mime` gives no
  rendition.
- `sniff` takes `format` only from a top-level member, so
  `{"meta":{"format":"vectorcraft"}}` is not a VectorCraft file.

tests/envelopes.rs writes a file of each format with the crates' types
and reads it through every call, and checks the inputs above.
doc.yml also runs when one of the three crates changes.

* craft-doc: sniff reads only top-level members of an EffectCraft project

`sniff` took any JSON object whose first member is `savedBy` for an
EffectCraft project when `"schema"` appeared anywhere in its first
bytes, so `{"savedBy":"1.0","metadata":{"schema":1}}` was claimed. It
now checks the top-level member names only, one at a time, without
keeping them.

* craft-doc: bound list elements with max_items; no rendition without a MIME type

Limits gains max_items (default 1,048,576): EffectCraft project items and
composition layers, VectorCraft artboards and layers, and PhotoCraft layers
at every level are counted before each one is read, and a file that lists
more gives Error::LimitExceeded. A list of empty objects within max_json no
longer expands into gigabytes of layers.

A VectorCraft preview or pdf member with an empty mime gives no rendition, as
one without a mime does; a preview may be PNG or JPEG. An EffectCraft
composition without a duration or a frame rate gets no timing instead of a
duration of 0.
2026-11-02 16:29:51 -05:00