Commit Graph
2488 Commits
Author SHA1 Message Date
mtlcl410andClaude Opus 5.5 9ebc237ecf M6: skip forms without a Password field quickly, and find ancestors once (#3926)
Review on #3943, point 1. has_password_values ran a full fields() walk
on every save and recovery snapshot, and clear_password_values walked
every field's ancestors again for each ancestor of each target.

has_password_flag now answers first, reading only /Ff, /Kids and /Parent:
the field tree from /Fields (seen set, 32 levels) and, as fields() does,
the widgets on the pages with the fields above them. A form without such
a field returns before any Field is built. clear_password_values finds
each field's ancestors once.

The test puts a page-only widget, a flag inherited from an unlisted
parent and a nested field whose widget is on no page each alone in a
document. Mutants that drop the page scan, the parent chain, the tree
walk or the descent into kids each fail it, as does treating every
ancestor as exclusive. Dropping the seen set or the early-out changes
only speed (equivalent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:29 +08:00
mtlcl410andClaude Opus 5.5 adc8307c0b M6: a saved Password field returns to its default value and is redrawn from it (#3926)
Review on #3943. A black-box test of Acrobat (synthetic form, Save As and
Ctrl+S, every decoded object searched) showed how it saves a Password
field: the typed value is nowhere in the file; a field with /DV is saved
holding /DV, one without is left empty; the asterisks are drawn from the
saved value (5 for a 5-letter default, not 13 for the typed password);
/DV and /AA are kept.

clear_password_values now does the same: it restores the (inherited)
/DV as the value, or leaves the field empty, and redraws the widgets, so
the appearance no longer shows how long the typed password was. A field
already at its default no longer counts as holding a password, so such
a form needs no cleaned copy. The parity note for form.fill-text says so
and lists the two limits Acrobat shares (a script copying the password
into an ordinary field, and an earlier revision kept by an incremental
save).

Mutants, each failing a test: no /DV restore, no redraw, the default
ignored in password_fields, emptiness ignored (an empty string in an
array). Dropping the empty-default filter was equivalent, so the filter
is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:29 +08:00
mtlcl410andClaude Opus 5.5 64c5fd8ef0 M6: signing, timestamping and DSS writes carry no Password-field values (#3926)
CodeRabbit on #3943: sign, sign_with_timestamp, timestamp_document and dss::embed
each write a new incremental file from editor.cos, outside the writable() boundary
saves use, so a typed Password value was signed into the file. Each now writes from
the writable() copy. The new test signs a form with a typed value and checks the
signed file holds the masked appearance but not the value; the three TSA/DSS paths
need a timestamp server or validation data, and use the same one-line change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:29 +08:00
mtlcl410andClaude Opus 5.5 048cf14d31 M6: Extract, Split and Combine write no Password-field values either (#3926)
CodeRabbit on #3943: Extract Pages copies the fields of the pages it takes, and
write_new (Combine, Extract, Split, Create) wrote that copy without the writable()
boundary saves use, so the new file kept a typed Password value. write_unchecked, which
write_new and Split's size measuring both go through, now writes the sanitized copy. The
other write_full/write_incremental calls in the engine build in-memory bytes (display,
previews, the working copy, print rasters), not files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:29 +08:00
mtlcl410andClaude Opus 5.5 509b5d5cf3 M6: clear Password values inherited from an ancestor or held by a widget (#3926)
Review follow-up: the helpers checked only the field dictionary's own /V. A value
inherited through /Parent or carried by a separate widget was missed, so the
document could be written as it was. Detection now uses the resolved value and
the widgets; clearing removes /V from the field, its widgets and every ancestor
only password fields inherit from, and gives the field an empty value of its own
where a shared ancestor value has to stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:28 +08:00
mtlcl410andClaude Opus 5.5 0e883c44a0 M6: values typed into Password fields are written to no file (#3926)
A text field with the Password flag kept its typed value in /V, so Save, a full
rewrite, Reduce File Size and the recovery snapshots all wrote it in plain text.
ISO 32000-2 §12.7.5.3, Table 231 (note to the Password flag): PDF processors
should never store the value in the PDF file. Each of those paths now writes a
copy without the values; the open document keeps them in memory, and the masked
appearance is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:38:16 +08:00
Dylan M. Taylor 0dc77fe4ed Merge pull request #4129 from aiongg/fix/coderabbit-c5-forms-misc
fix(xfdf, sign, preflight, engine, forms, measure, annot): bound repeated work in form exchange, preflight and export, and check what imports and time stamps claim
2026-10-22 22:50:29 -04:00
Dylan M. Taylor 5f7b9e241b Merge pull request #4109 from aiongg/fix/coderabbit-a1-audit
fix(audit): find covers the audit missed, bound its work and honour the copy permission
2026-10-22 22:50:24 -04:00
Dylan M. Taylor 7fa38de186 Merge pull request #4170 from dylanmtaylor/fix/sonar-shell-scripts
fix(packaging): use [[ tests and name positional parameters (SonarCloud shelldre:S7688, S7679, S7682, S131)
2026-10-22 22:48:47 -04:00
Dylan M. Taylor 1d29f0673c Merge pull request #4169 from dylanmtaylor/chore/sonar-scope-vendor-and-spec-hashes
chore(sonar): exclude vendored crates and the spec-mandated MD5/RIPEMD-160 (SonarCloud rust:S4790)
2026-10-22 22:48:42 -04:00
Dylan M. Taylor e93f11e81e fix(packaging): use [[ tests and name positional parameters in the scripts (SonarCloud shelldre:S7688, S7679, S7682, S131)
Every packaging script runs under bash, so [[ ]] is available. Name the positional
parameters of sha256, render and has, end env.sh's functions with an explicit return
(bare, so the exit status is unchanged), and give the build-label case a default arm.
2026-10-22 22:40:05 -04:00
Dylan M. Taylor bd9030b271 chore(sonar): exclude vendored crates and the spec-mandated MD5/RIPEMD-160 (SonarCloud rust:S4790)
vendor/ is third-party code kept close to upstream (vendor/README.md), so SonarCloud's
186 findings there are not ours to restyle. MD5 in crates/crypt is what ISO 32000's
standard security handler mandates, and RIPEMD-160 in crates/sign is a digest existing
signatures can name; both S4790 findings are scoped to those two files.
2026-10-22 22:36:03 -04:00
Dylan M. Taylor 7345e5f5de Merge main into fix/coderabbit-a1-audit
Keep this branch's budgeted audit() and read lines with main's
glyph_text_lines, so /ActualText cannot hide text under a cover.
2026-10-22 22:20:45 -04:00
Dylan M. Taylor 07a9109fed Merge remote-tracking branch 'origin/main' into HEAD 2026-10-22 22:07:20 -04:00
Dylan M. Taylor 93b25f67a6 Merge main into fix/coderabbit-c5-forms-misc
Keep the Ops iterator from this branch and carry main's long-number skip
(MAX_RETRIED_TOKEN) into its operand error path.
2026-10-22 22:07:19 -04:00
Dylan M. Taylor de91632934 Merge pull request #4151 from aiongg/fix/coderabbit-c2-edit-organize
fix(edit,organize,create): bound the work hostile files cause in editing, page organizing and image import
2026-10-22 21:57:13 -04:00
Dylan M. Taylor 684226713a Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	crates/audit/src/tests.rs
2026-10-22 21:42:41 -04:00
Dylan M. Taylor d31a96f46f Merge pull request #4121 from aiongg/fix/coderabbit-c1-js-xfa
fix(js, xfa): bound the text and work that form scripts and XFA data can multiply
2026-10-22 21:39:08 -04:00
Dylan M. Taylor 8ee1ac0c58 Merge pull request #4117 from aiongg/fix/coderabbit-b-caps
fix(limits): bound the decoding and listing work that repeated references and small inputs multiply
2026-10-22 21:38:59 -04:00
Dylan M. Taylor 3cee9f338f Merge pull request #4128 from aiongg/fix/coderabbit-c4-core
fix(cos, content, filters, crypt, model, render, print, a11y): bound the work that repeated keys, streams and page values multiply
2026-10-22 21:38:37 -04:00
Dylan M. Taylor f7a3a52fad Merge pull request #4114 from aiongg/fix/coderabbit-b-shared
fix(cos): one decode budget per page walk, so repeated stream references cannot multiply decoding
2026-10-22 21:38:15 -04:00
Brandon ThomasandClaude Opus 5.5 53199fb4fa Release 0.13.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.13.0
2026-10-22 20:06:13 -04:00
aiongg ba024eb0dd fix(edit): flatten on a copy so a page refused part way changes nothing 2026-10-23 00:02:33 +00:00
aiongg 0d216199b9 fix(redact): apply redactions on a copy so a failure leaves the document as it was 2026-10-22 23:40:55 +00:00
aiongg 2430d53f9e fix(print): read the text after a form the budget refuses 2026-10-22 23:37:07 +00:00
aiongg 5473d354a9 fix(forms): skip a field of an unread kind before it claims shared widgets 2026-10-22 23:36:31 +00:00
aiongg 54152aed87 fix(edit): refuse a mark on top of over-deep content before changing the page 2026-10-22 23:35:40 +00:00
aiongg 03366c3c70 A counted decode bounds all filter stages together and counts output before the predictor 2026-10-22 23:33:37 +00:00
aiongg 4362449fb2 fix(render): find an embedded file by key without the listing's budget
Extraction walked the name tree again with a fresh key budget, so keys
the listing had left out could fill it and a listed embedded file came
back as not found. The lookup compares each key with the one wanted and
keeps none, so it needs no budget; a key object or /Names array that
several places reference is converted once.
2026-10-22 23:32:42 +00:00
aiongg 7a466ff5fc fix(edit): prune a field again when a shorter path reaches it past the depth cutoff 2026-10-22 23:32:11 +00:00
aiongg 8bcf83faef fix(render): spend the raw length of text that converts shorter
A kept web address or attachment text spent only its converted length,
so a long raw value that trimmed short was converted in full for every
entry sharing it. A kept conversion now spends the longer of its raw
and converted lengths.
2026-10-22 23:31:43 +00:00
aiongg 7a73c2ec3a fix(render): keep embedded-file keys within the text budget
The embedded-files name tree copied every key, and an attachment
without a file name copied its key again as the name, so many entries
sharing one long key string multiplied it. Keys, and a key kept again
as the name, now spend the 16 MiB link and attachment text budget; an
embedded file whose key does not fit is not listed.
2026-10-22 22:03:43 +00:00
aiongg a0a66b982b fix(render): read an annotation attachment's Contents only when it names it
An annotation attachment's /Contents was read and charged to the text
budget before the file specification's /UF or /F, which then named the
attachment, so the discarded text could leave a later web address
unlisted. /Contents is now read only when neither name is there.
2026-10-22 22:02:10 +00:00
aiongg 0c09000f7b A decode charges the page's decode budget for every filter stage's output 2026-10-22 22:00:51 +00:00
aiongg c704c1089a fix(create): keep the TIFF total budget in range on 32-bit targets 2026-10-22 21:55:12 +00:00
aiongg aaa4b99eb9 fix(render): spend the text budget on a rejected conversion
A web address or attachment text that converted to more than the budget
left spent nothing, so every link or attachment sharing it converted it
again. A conversion that comes out too long now spends the raw length,
and so does attachment text that trims to nothing.
2026-10-22 21:54:39 +00:00
skylar taylor-barrick adea2c908a M14: recover existing macOS PDF associations after open and save (#3558)
* M14: recover existing macOS PDF associations

* M14: address macOS association recovery review

* M14: reuse upstream save metadata preservation
2026-10-22 15:49:13 -06:00
aiongg 35b5abd5b3 test(engine): show the truncated-page picture exports within a sufficient budget
The fixture's image stream held six bytes under /Length 4, and the test
only checked that the picture was missing at a zero budget, which a
picture that could not be prepared also passes. The stream is four
bytes, and the picture is first shown to export at a 1 MiB budget.
2026-10-22 21:42:02 +00:00
aiongg 82721ed44c Merge #4114 (fix/coderabbit-b-shared): over-budget page content reads as an error 2026-10-22 21:41:53 +00:00
aiongg d67c4b3059 fix(js): an XFA value past the effect cap leaves the field unchanged 2026-10-22 21:41:50 +00:00
aiongg e541151389 fix(js): XFA presence and access change the snapshot only when their effect is kept 2026-10-22 21:41:01 +00:00
aiongg 11d2a576d6 Filters README lists decode_counted 2026-10-22 21:40:10 +00:00
aiongg 0ae289c762 A failed strict decode charges the page's decode budget for what it decoded 2026-10-22 21:37:19 +00:00
aiongg fd2e13f332 M8: XFA instance changes reserve their effect before changing the snapshot 2026-10-22 21:35:52 +00:00
aiongg 426a609795 fix(render): keep link addresses and attachment text within 16 MiB
The link and attachment listings copied a link's web address and an
attachment's name and description for every annotation or name-tree
entry, so many entries sharing one long string multiplied it in
memory. Together they keep at most 16 MiB of that text, and a string
past what is left is not converted. A link past it lists as a URI
action without its address, an attachment under its generic name
without a description.
2026-10-22 21:35:23 +00:00
aiongg 4e1457ed4f Marking a tag as an artifact refuses content past the page's decode budget 2026-10-22 21:33:43 +00:00
aiongg c82dc91abf fix(render): list an annotation reached through a reference chain once
The annotation listing skipped a repeated /Annots entry by its raw
reference, so an indirect object holding only a reference to a link
listed that link a second time. Entries and page arrays are now told
apart by the object their reference chain ends at. The lopdf reader
reported the first object of a chain, because lopdf's get_object
already follows it; it now reports the last, as the lazy reader does.
2026-10-22 21:33:20 +00:00
aiongg e3f86940a3 M7: redaction audit skips covered-text assembly when copying is withheld 2026-10-22 21:32:20 +00:00
aiongg 473b0a4371 Merge fix/coderabbit-b-shared into fix/coderabbit-c4-core 2026-10-22 21:32:09 +00:00
aiongg 4fc56fd303 Reading blocks documents that over-budget page content is an error and truncation covers forms 2026-10-22 21:29:04 +00:00