Commit Graph
3759 Commits
Author SHA1 Message Date
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
magdale76andClaude Opus 5.5 76f28a41e5 M3: an empty XMP alternative or list reads as no value, not as its markup (#2373)
`get_property` returned the raw child markup when a property had no
`rdf:li` items, so `<dc:title><rdf:Alt/></dc:title>` with no /Info title
showed "<rdf:Alt/>" in Document Properties. Character data that still
holds a `<` is markup, never a value: an empty Alt/Seq/Bag, or an item
holding a nested structure, now reads as none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 23:33:29 +02:00
magdale76andClaude Opus 5.5 6996aac44a M3: Document Properties shows a title and author stated only in XMP (#2373)
Description read Title, Author, Subject and Keywords only from /Info. A file
that states them only in its XMP packet (/Info is deprecated in PDF 2.0, and
some writers leave it out) showed empty fields, while Acrobat shows them.
organize::info now falls back to the packet's dc:title, dc:creator,
dc:description, pdf:Keywords, xmp:CreatorTool and pdf:Producer: a language
alternative gives its x-default item, and several authors are joined with
"; ". The engine fills the inspection's missing fields the same way, so the
read-only Description rows and doc_info agree with the editable fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 23:33:26 +02: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
aiongg 6384381127 fix(forms): read each widget once when enumerating fields
A /Kids array listing one widget many times, or shared by several fields,
built a Widget for every listing, so a small file expanded into fields times
listings widgets. A widget now belongs to the first field that lists it, and a
page widget already owned is not adopted again.
2026-10-22 21:20:10 +00:00
aiongg a52e39a5e2 Merge #4114 (fix/coderabbit-b-shared): shared DecodeBudget
The tests each branch appends to the audit, edit and organize test files are
kept side by side; prune.rs merges cleanly, with the form memo on top of the
budgeted page content.
2026-10-22 21:17:55 +00:00
aiongg 9188cf59eb fix(create): don't decode a JPEG carrying thousands of Extended XMP chunks
The JPEG decoder re-sorts and rescans every pending Extended XMP chunk after
each marker, so a file of many small chunks cost time quadratic in their
number before any pixel limit applied. Turning a photo upright now counts
the chunks first and leaves a JPEG with more than 1024 as it is.
2026-10-22 21:17:15 +00:00
aiongg cad0fe4526 fix(create): refuse a GIF screen past 4096x4096 and convert its canvas in place
A one-pixel GIF on a huge logical screen decodes to a canvas that size,
which the conversion then copied before splitting it into colour and alpha,
so a few bytes could hold over a gigabyte. Screens past 16 Mpx are refused
before decoding, and the decoded canvas is converted without a copy.
2026-10-22 21:17:15 +00:00
aiongg 7e9f5a4435 fix(create): bound a TIFF's decoded pages together, and read one when one is wanted
A TIFF's pages were each capped at 2 GiB but all kept in memory until
embedded, so many small directories sharing one compressed strip could ask
for many times that; embedding one image also decoded every page. The pages
of one TIFF are now refused past 4 GiB together, and image_xobject reads
only the first page.
2026-10-22 21:17:15 +00:00
aiongg b979d4d9c2 fix(audit): audit the glyphs on each page held, not their ActualText
The redaction audit read each page's text through text_lines, which walked
the whole page tree again for every page (quadratic in the page count) and
let /ActualText replace the glyphs, so an empty or substitute ActualText hid
covered text from the audit. Edit gains glyph_text_lines, which reads the
page dictionary it is given and ignores ActualText; the audit uses it.
2026-10-22 21:17:15 +00:00
aiongg fdcfe16b7d fix(edit): bound the text one page's extraction can expand into
Text extraction copies a ToUnicode destination for every glyph showing its
code, and the page's total text was not bounded, so a small page could expand
into gigabytes. One page's extraction (its forms included) stops with an error
past 16 MiB of text.
2026-10-22 21:17:15 +00:00
aiongg c9090b00ef fix(edit): refuse to put content on top when marked-content depth is unknown
Past the marked-content depth budget the number of open sequences is unknown,
and the mark on top was appended with none of them closed, so the page's own
optional-content scopes (possibly hidden) could hide a redaction box, a
header or a watermark. Placing on top of such a page is now refused before
anything changes; behind it is still possible.
2026-10-22 21:16:27 +00:00
aiongg dc7a195dda fix(edit): prune each form-field node once after flattening
Removing flattened widgets from the field tree recursed into every /Kids
entry with only a depth limit, so a node listing its child twice per level
(or itself) made the walk exponential. Each node's answer is now remembered;
a node met again while it is still being visited survives, as it did when
the depth limit stopped a cycle.
2026-10-22 21:16:21 +00:00
aiongg f1a89a4842 fix(edit): wrap added text once per write, not once per line
Writing an added text item re-wrapped the whole text inside the loop over its
lines to learn the line count, so multiline text cost time and temporary
memory quadratic in its length. The wrapped lines are computed once.
2026-10-22 21:16:21 +00:00
aiongg cb1ddc899b fix(organize): find XMP attributes in one pass over the packet
Each candidate attribute match searched the whole packet before it for the
last < and >, so a long text node repeating the attribute name cost time
quadratic in the packet size (up to the 16 MiB decode cap). The scan now
keeps the last < and > as it moves forward.
2026-10-22 21:16:21 +00:00
aiongg 1f6ce5ecc7 fix(organize): analyse each form once when pruning unused XObjects
The pruner decoded and walked a form XObject again on every Do that drew
it, so a form drawing itself a few times per level was analysed
exponentially often within the depth limit. Forms are now remembered with
the resources owner they were drawn against, and a repeat records nothing
new.
2026-10-22 21:16:20 +00:00
aiongg cab1dc3eb5 fix(organize): read each name-tree node once when importing
Looking up a named destination and listing attachments in a source's name
trees followed every /Kids entry with only a depth limit, so a node listing
the same child twice per level made the walk (and the attachment list)
exponential in the depth. Both walks now skip a node already read.
2026-10-22 21:16:20 +00:00
aiongg 59dbdee428 fix(organize): copy annotation actions with one shared reference map
Fixing up copied annotations cloned the whole source-to-destination map for
every non-GoTo action and dropped the copies it made, so many annotations
cost quadratic memory and time and an object shared by their actions was
copied once per action. The copier now lends its map to the action copy and
takes it back with the new entries.
2026-10-22 21:16:20 +00:00
aiongg da288517e6 fix(organize): resume the field-rename suffix search where it stopped
Renaming a colliding field tried Name-1, Name-2, ... from 1 every time, so
many fields of one name cost a quadratic number of attempts during import,
combine and their conflict previews. Taken names only grow, so the search
now resumes after the last suffix handed out for that name; the result is
still the first free variant.
2026-10-22 21:16:20 +00:00
aiongg d39b55dd6d fix(organize): register imported form fields in linear time
Each imported top-level field was checked against every field already in
/AcroForm /Fields before it was appended, so a page with many distinct
widget fields cost time quadratic in their number. Membership is now kept
in a set beside the list.

Adds a test-only work counter to the crate, so tests can bound the work a
hostile document causes without timing it.
2026-10-22 21:16:20 +00:00
aiongg 62b54fce09 fix(preflight): check the actions in an indirect /Next array 2026-10-22 20:56:33 +00:00
aiongg aa1c13f71c fix(engine): list a page truncated for both forms and pictures once 2026-10-22 20:56:02 +00:00
aiongg 6394779b81 fix(annot): check a whole link batch before creating any link object 2026-10-22 20:51:55 +00:00
aiongg 923e499597 Merge upstream/main into fix/coderabbit-c5-forms-misc
Conflict in crates/content/src/lib.rs: main ends inline images where the
renderer's tokenizer ends them and counts the images whose end is a guess
(Parsed::ambiguous_images); this branch reads operators through the
streaming Ops iterator that parse collects. The iterator's BI branch takes
main's inline_image result and keeps the ambiguous-image count, which parse
copies into Parsed and Ops::ambiguous_images reports.
2026-10-22 20:51:14 +00:00
aiongg f63f509647 fix(cos): compare each section's table with a repeated hybrid stream
A section naming an /XRefStm that a newer section had already read
skipped the stream entirely, so an entry in its own table that
disagreed with the stream was no longer recorded in xref_oddities.
Signature validation reads those records to refuse an appended section
another reader could follow. The reader keeps each hybrid stream's
entries (up to MAX_XREF_OBJECTS in all) and compares every later
naming section's table with them, without decoding the stream again.
2026-10-22 20:37:04 +00:00
aiongg 0e643d45a0 Merge #4114 (fix/coderabbit-b-shared) into fix/coderabbit-c4-core
#4114 moves the bodies of Stream::decoded_within and decoded_strict into
decoded_within_typed and decoded_strict_within_typed, which return
FilterError so DecodeBudget can tell a spent limit from corrupt data.
This branch checks /Filter with readable_filters there. Kept #4114's
typed functions and made readable_filters return
FilterError::Unsupported, so a /Filter that is not a name fails both
typed decodes without charging the budget; decoded_first_filter_strict
maps it to CosError as before.
2026-10-22 20:30:25 +00:00
aiongg 60132bbfbf Merge upstream/main into fix/coderabbit-c4-core 2026-10-22 20:29:34 +00:00
Lombardi2003andClaude Opus 5.5 cf730534f0 fix(ui): "Don't show again" only on the bar that shows the security notice (#4035)
From review of #4096: when a secured form's scripts were turned off, the
bar shows that message instead of the security one, but still offered
"Don't show again", which would turn off a notice the user had not seen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:28:22 +02:00
Lombardi2003andClaude Opus 5.5 31828efd6e feat(ui): the secured-document notice can be turned off for good (#4035)
The notice that a document is secured came back on every open; Dismiss
lasted only for that view. Its new "Don't show again" turns it off in
every document, kept in the settings as show_secured_notice and turned
back on in Preferences (or --secured-notice). Only the notice changes:
the restrictions, the encryption and Document Properties > Security stay
as they are. The two labels are translated in all 33 catalogues, whose
ATTRIBUTION hashes and provenance notes are refreshed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:28:22 +02:00
aiongg 1e2ac1d85a fix(xfa): copy embedded field values out only as far as the text budget
The text layout cut each embedded value to the bytes its block had left, but
the resolver cloned the whole value first. With a byte or two left and a
value starting with a multibyte character, every later run cloned the full
value and kept none of it. The resolver is given the bytes left and returns
only a prefix that fits.
2026-10-22 20:21:21 +00:00
aiongg 2ece498ffc fix(js): charge both copies of a written XFA value before keeping either
set_value charged the snapshot's copy, changed the field, then charged the
effect's copy in push. When only the first fit, the effect was dropped while
later reads in the script saw the new value. Both copies are charged at once
before the field changes.
2026-10-22 20:21:21 +00:00
aiongg 2019726eae Merge upstream/main into fix/coderabbit-c1-js-xfa 2026-10-22 20:16:55 +00:00
Oliver HagmannandClaude Opus 5.5 60ce45f9ba Flatten comments from the page's right-click menu and the Comments panel's menu
Reviewers flatten their comments and Fill & Sign signature once done, so
nothing on the signed or reviewed pages moves any more. The command was
only in All tools and the palette; it now also ends the page's
right-click menu (with or without a comment selected, while the document
has comments) and the Comments panel's More options menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:08:35 +02:00
aiongg 668d8f777e fix(xfa): every drawn subform instance spends the layout budget
A positioned root draws its children instead of flowing them, so nested
repeating subforms under it multiplied their instances without spending
the measurement budget that flowed instances spend. Drawing a subform
instance spends it too.
2026-10-22 20:08:33 +00:00
aiongg a887405ea1 fix(sign): don't report valid when a DSS cap hides revocation evidence
The evidence caps counted repeated references to one stream, so a /DSS
array could list one benign stream 1024 times ahead of a revoking CRL,
and the check never reached it. Repeats are skipped before the entry
cap counts anything, and when a cap still stops an array being read in
full without a revocation found, the signature's status is unknown
rather than valid, with the reason in its details.
2026-10-22 20:07:41 +00:00
aiongg 071130d498 fix(render): list links and attachments after the comment budget fills
Reaching the 64 MiB comment budget ended the whole annotation scan, so
links and file attachments listed after it were missing, and a later
JavaScript link left the document unmarked. Past the budget, comments
are no longer built, and the scan goes on for links and attachments.
2026-10-22 20:05:05 +00:00
aiongg 02f0f7396f fix(annot): count web-address text after its UTF-8 conversion
An address of invalid UTF-8 grows up to three times when converted, so
the listing's 16 MiB cap counted fewer bytes than it kept. The cap now
counts the converted text; the raw length is still checked first, so an
address already over the cap is never converted.
2026-10-22 20:04:04 +00:00
ffishmanandClaude Opus 5.5 c829a1ad44 fix(sign): a removed annotation is not hidden behind an added signature
Review follow-up: an indirect /Annots array that drops a signed
annotation while gaining a signature widget was judged by the widget
alone, so a form-fill certification let a signed comment be deleted.
A removed comment is now a comment change, and a removed widget is a
change no permission covers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 17:03:37 -03:00
aiongg d818e4a20a Merge upstream/main into fix/coderabbit-b-caps 2026-10-22 20:03:27 +00:00
aiongg f02b998122 Format the accessibility tests 2026-10-22 19:54:10 +00:00
aiongg 9d5bcc0745 Accessibility check asks for a manual check where page content ran past the decode budget 2026-10-22 19:51:47 +00:00
aiongg 089882f372 Marking a figure decorative refuses content past the page's decode budget 2026-10-22 19:50:42 +00:00