Commit Graph
3113 Commits
Author SHA1 Message Date
aiongg 73f11b2a29 fix(engine): fail an action whose information-removal step fails
A step that applies an edit logged any error and carried on, which is right
for a flatten with nothing to flatten but not for removing hidden
information or sanitizing: on a signed file the removal was refused, and the
unchanged file was written out as the action's successful result. Those
steps now fail the action unless there was nothing to remove.
2026-10-22 13:18:26 -04:00
aiongg 28ab616eb6 fix(engine): print as low-resolution pictures when only that is allowed
Printing checked the print permission but not the separate high-quality one
(Table 22, bit 12), so a document limited to low-resolution printing came out
as its original vector pages, text included. Such a document now always
prints as pictures of at most 150 dpi, whatever the settings ask for.
2026-10-22 13:18:26 -04:00
aiongg 30300ae71b fix(engine): compare only documents that allow copying their text
Compare read both documents' words out with no check of the copying
permission, and its result, report and difference comments carry that text,
so a copy-restricted document's text could be taken out through a comparison.
Both documents must now allow copying.
2026-10-22 13:18:26 -04:00
aiongg 5dc64face0 fix(automation): keep page text from tools when copying is not allowed
text_lines returned a copy-restricted document's lines, ocr_recognize returned
the words it recognised, and text_edit on a paragraph echoed the paragraph's
own text, all without checking the copying permission that the other text
tools check. text_lines is refused like them; recognising and restyling still
work with permission to change the document, and leave the text out.
2026-10-22 13:18:26 -04:00
aiongg a525cf1d42 fix(js): refuse code compiled from strings that skipped the script checks
eval and the Function constructors compiled code built at run time, which never
passed the size and nesting checks a script passes before it runs, so a short
form script could hand boa's recursive parser a deeply nested string and
overflow the stack. Form scripts now refuse to compile code from strings. XFA
scripts, which the runner itself starts through eval, hold such code to the
same checks as the script.
2026-10-22 13:18:26 -04:00
MarekSpodBiedry db0689e381 Save keeps the open text box or paragraph the document refused, and still applies the other (#3496) 2026-10-22 19:18:09 +02:00
aiongg 7eaa20799c fix(fonts): let auto-deref pass the candidate objects to the program match
Clippy's explicit_auto_deref rejects the explicit derefs.
2026-10-22 17:12:57 +00:00
PerchAnton d323e86706 M7 PR #3000: retain batched styled fallbacks with current main 2026-10-22 20:11:21 +03:00
aiongg 306627ee77 fix(xfa): read a packet stream the XFA array lists again only once
The packet reader decoded the stream of every /XFA array entry and
charged only the decoded output to its 64 MB budget, so one stream that
decodes to almost nothing (Flate over hex whitespace), listed many
times, repeated a large decode for free. A stream reference already
read is skipped.
2026-10-22 17:08:55 +00:00
aiongg 2e1c029fa8 fix(xfa): every flowed subform instance spends the layout budget
Nested repeating subforms multiplied their instances (up to 50 per level
over 64 levels), and an empty instance neither measured nor placed
anything, so it spent none of the measurement or item budgets. Each
instance now counts as a measurement, and layout stops with TooLarge
once the budget is spent.
2026-10-22 17:08:54 +00:00
aiongg 77ee1c8a26 fix(sign): read DSS revocation evidence one stream at a time within caps
Revocation checking decoded every /CRLs and /OCSPs entry under the
general 1 GiB stream limit and kept them all before parsing any, so one
compressed stream referenced many times multiplied decoding and memory.
Evidence now decodes lazily, each stream once and within the 2 MiB
evidence cap, with at most 1024 entries and 64 MiB per array.
2026-10-22 17:08:54 +00:00
aiongg dfde90787f fix(render): list a repeated annotation once and cap what the listing keeps
Inspection built an annotation record, with owned copies of its text,
for every /Annots entry on every page, so one comment referenced many
times, a page-shared /Annots array, or many small comments sharing one
long /Contents string multiplied that text in memory. Pages are read in
order, an annotation or /Annots array already listed is skipped, and
the listing stops once the kept text and geometry pass 64 MiB.
2026-10-22 17:08:54 +00:00
aiongg 853a9ae1e3 fix(preflight): decode an output-intent profile within 16 MiB
Reading the colour space from an ICC header when /N is missing decoded
the whole profile under the general 1 GiB stream limit to look at four
bytes. The profile now decodes within 16 MiB; a larger one leaves the
component count unknown.
2026-10-22 17:08:54 +00:00
aiongg a34697a8eb fix(measure): a failed content decode uses up the snap byte budget
Snap geometry charged decoded bytes only on success, so a /Contents
array repeating one stream that inflates past the 16 MiB limit ran that
inflation for every entry without spending any budget. A tolerant decode
fails only on an unsupported filter or after producing the remaining
budget, so a failure now spends the rest of the budget and later parts
are skipped as truncated.
2026-10-22 17:08:54 +00:00
aiongg a16661ac10 fix(measure): list a repeated measurement once and cap what a listing keeps
The listing read a measurement for every /Annots entry, so one annotation
referenced many times, or many small annotations sharing one indirect
2048-point vertex array, multiplied its points and text in memory. The
listing skips annotation references it has already read and stops,
marked truncated, once the kept points and text pass 32 MiB.
2026-10-22 17:08:54 +00:00
aiongg 8da1164f7d fix(fonts): decode Encoding and ToUnicode CMaps within 16 MiB
Both CMap streams decoded under the general 1 GiB stream limit, so a
small Flate stream of whitespace inflated to a gigabyte that was then
lexed on every font load. They now decode within 16 MiB, like the
font programs beside them, and a larger CMap is not read.
2026-10-22 17:08:54 +00:00
aiongg 05df6ee4b7 fix(fonts): decode each candidate font program once, within its size
Looking for an already embedded full program decoded every candidate up
to 17 MiB, once per resource naming it, so a page whose PCFull resources
all alias one compressed stream, or many marked streams, made one text
edit inflate gigabytes. Candidates now decode within the program's own
length, each once per embedding, and at most 16 in all; the resource
check compares its CMap streams within their expected lengths too.
2026-10-22 17:08:54 +00:00
aiongg 83d8eb0856 fix(create): decode an indexed lookup table within a small cap
Image export decoded a stream lookup table under the general 1 GiB
stream limit although a table holds at most 1024 bytes, and every image
sharing the palette decoded it again. The table now decodes within
64 KiB and a larger one is refused for that image.
2026-10-22 17:08:54 +00:00
aiongg 1695106f98 fix(annot): list a repeated link once and cap its web-address text
The link listing copied the action's URI into a new String for every
/Annots entry, so one annotation referenced many times, or many small
links sharing one long URI string, multiplied that string in memory.
The listing skips annotation references it has already listed and keeps
at most 16 MiB of web-address text; a link past that lists as an
uneditable action, which leaves it unchanged in the file.
2026-10-22 17:08:54 +00:00
aiongg e07942c829 fix(annot): cap the bumps of a cloudy outline
The cloud border clamped bumps per edge only, so a polygon with many long
edges asked for 10,000 cubic segments on each one. The bump radius now
grows so the whole outline draws at most 20,000 bumps plus one per edge.
2026-10-22 17:08:54 +00:00
aiongg 986a64bc77 fix(cos): cap the objects one parsed object may build
The parser pushed every array element and dictionary value with only a
nesting limit, so a small compressed object stream decoding to a long run
of '0 ' became tens of millions of Object values. Each top-level parse now
counts the objects it builds and fails past 4M, including inside
tolerated junk.
2026-10-22 17:08:54 +00:00
aiongg 37b6c530d8 refactor(edit): images read page content through content_streams
The image module kept its own copy of the page-content reader; the one
in text carries the page decode budget.
2026-10-22 16:57:55 +00:00
aiongg c88831381e fix(edit): editor font enumeration shares one decode budget
text_editor_fonts counted only the programs it kept against its 32 MiB
budget, so up to 128 aliases of one unusable program were each inflated
in full. Every decode now spends a shared 64 MiB allowance.
2026-10-22 16:57:55 +00:00
aiongg 7a33a85d8b fix(fonts): embedded sfnt reads can share one decode budget
embedded_sfnt decoded each program with a fresh 16 MiB allowance and
charged nothing when the program turned out unusable, so a page whose
font resources all named one bad program inflated it once per alias.
embedded_sfnt_within charges every decode to a caller's DecodeBudget.
2026-10-22 16:57:55 +00:00
aiongg 735c80c6c5 fix(cos): DecodeBudget decodes within a per-stream bound too
Callers with a tighter limit for one stream (an embedded font program)
need it alongside the shared allowance. A decode that runs past that
bound charges the bound, the work it took, so repeated failures still
spend the allowance.
2026-10-22 16:57:55 +00:00
aiongg a37a908f7b fix(edit): page content, forms and image records share per-page limits
The editor's page readers decoded every /Contents entry and every form
entry with a fresh 1 GiB allowance, and the reading-order image
inventory added a record per image draw in every form entered, so a few
shared forms drawing each other produced millions of records. Content
streams and form entries are decoded against one per-page DecodeBudget
(content past it is an error, so no edit rewrites a page from part of
its content; forms past it are reported truncated), and an inventory
past 100,000 image draws is refused.
2026-10-22 16:57:55 +00:00
aiongg b5cddc884d fix(audit): page content and forms share one decode budget per page
The cover walk decoded every /Contents entry and every form draw with
a fresh 1 GiB allowance; the operator bound does not count whitespace,
so a large blank form drawn many times meant hours of decoding. The
walk charges a per-page DecodeBudget with the strict decoder, and
spending it fails the audit.
2026-10-22 16:57:55 +00:00
aiongg ea4939cd08 fix(cos): a strict decode charged to a DecodeBudget
The redaction audit treats any damaged stream as a failure, so it needs
the strict decoder under the same shared allowance.
2026-10-22 16:57:55 +00:00
aiongg d2e7ce72f0 fix(redact): form entries and page content share one budget per page
The interpreter entered a form again at every Do with only a nesting
limit, so a few shared forms drawing each other produced exponential
work in apply, sanitize and verify. Scope carries a per-page
DecodeBudget for form entries and form bytes; past it, apply refuses
the page and verification counts the form as possible residue. Page
content streams are decoded against a page budget too.
2026-10-22 16:57:55 +00:00
aiongg bb8ea65ffd fix(forms): script streams share one decode budget per reading
Each field action and document-script entry decoded its /JS stream
with a fresh 1 GiB allowance and kept its own copy, so many fields or
name-tree pairs naming one compressed stream retained gigabytes. A
reading of the fields, one field's actions, and the document scripts
each share a 64 MiB DecodeBudget for their script streams.
2026-10-22 16:57:55 +00:00
aiongg 92ab0e932d fix(cos): a spent decode budget reports its whole allowance
The error named the bytes left at the failing decode (often 0), which
says nothing about why the operation stopped.
2026-10-22 16:57:55 +00:00
aiongg 9c9bbe2ad6 fix(organize): page content is joined within one decode budget
Pruning and page_as_form joined every /Contents entry with a fresh
1 GiB allowance each, so a page naming one compressed stream many
times became gigabytes. Both share one helper that decodes against a
per-page DecodeBudget and fails when it runs out.
2026-10-22 16:57:54 +00:00
aiongg 8c8f56a2e2 fix(print): page content and label forms share one decode budget per page
Printing and the label text-direction check decoded every /Contents
entry with a fresh 1 GiB allowance, and the direction check re-entered
forms at every Do with only a depth limit, so a few hundred bytes of
nested repeated draws meant hundreds of thousands of form walks. Both
charge a per-page DecodeBudget: printing refuses a page whose content
runs past it, and the direction check stops reading there.
2026-10-22 16:57:54 +00:00
aiongg 0b6adb70da fix(optimize): page content and forms share one decode budget per page
Image measurement and fragment merging decoded every /Contents entry
and every form occurrence with a fresh 1 GiB allowance, so repeated
references to one compressed stream multiplied memory. Both charge a
per-page DecodeBudget. A page whose content does not decode within it
is left unmerged, and its images are not resampled, since a use may be
missed; fragment merging no longer rewrites a page from content it
could not fully decode.
2026-10-22 16:57:54 +00:00
aiongg 13d9c9bf8c fix(a11y): page content and forms share one decode budget per page
The checker decoded every /Contents entry and every form a page drew
with a fresh 1 GiB allowance each, so a page repeating one compressed
stream reference produced gigabytes. The two copies of the page-content
helper are one now, and it and the form walk charge a per-page
DecodeBudget.
2026-10-22 16:57:54 +00:00
aiongg 733e38bfa7 fix(cos): a decode budget shared by every stream one walk reads
MAX_DECODED bounds one decode, so a page whose /Contents repeats a
reference to one compressed stream, or a form drawn many times, gets a
fresh 1 GiB allowance per occurrence. DecodeBudget is one cumulative
decoded-byte allowance plus a form-entry count and a seen-set of stream
references, shared by a whole page walk. A decode that runs past the
remaining allowance spends all of it.
2026-10-22 16:57:54 +00:00
aiongg 693eae1d56 docs(parity): cite the audit's new tests and record its bounds and copy restriction 2026-10-22 16:49:00 +00:00
aiongg 3fce90e94a fix(audit): withhold covered text when copying is not permitted
The audit returned the text under each cover whatever the document's
permissions, and the CLI and the automation tool printed it. A user
whose password withholds copying could read through the audit the text
that text extraction refuses them. The audit now reports every finding
for such a document with empty covered text; the owner password lifts
the restriction as it does elsewhere.
2026-10-22 16:44:38 +00:00
aiongg 65ee18d470 fix(audit): bound cover-line comparisons and covered text
Every cover was compared with every text line of its page, and each
finding kept its own copy of the text it covered. Both grow with the
square of the content: a few megabytes of covers over one long line
meant hours of comparisons and gigabytes of report. The audit now
counts comparisons per page and covered-text bytes across the document
and fails with an error past either bound, never with a partial
report.
2026-10-22 16:44:38 +00:00
aiongg 3e306c22ef fix(audit): compare a cover with the start of a merged text line
A text line joins compatible runs on one baseline, and the audit took
the line's last painting operator as its paint position. Text drawn
after a cover on the same baseline joined the covered words and moved
the line past the cover, so the covered words went unreported. The
audit now compares against the line's first painting operator: any
run painted before the cover is checked, and runs painted after it
in the same low-contrast color are unreadable on it too.
2026-10-22 16:44:38 +00:00
aiongg 4c7ba85beb fix(audit): resolve an indirect /Subtype before skipping an object
The annotation scan and the form XObject walk read /Subtype only as a
direct name. A /Subtype stored as a reference to the name read as
none, so a /Redact annotation or a covering form named that way was
skipped as an unrelated object and the page reported clean. Both now
follow the reference, and a reference that cannot be loaded fails the
audit.
2026-10-22 16:44:38 +00:00
aiongg cc1086a862 fix(audit): keep the pending path across Q
The cover walk cleared the current path when it restored the graphics
state, but the current path is not part of the graphics state. A
rectangle built before Q and filled after it paints a box over the
text, and the audit missed it and reported the page clean.
2026-10-22 16:44:37 +00:00
Janiz-zzzandClaude Opus 5.5 667bc697b2 i18n(fi): translate the 64 labels left in English after #2210
Replaces the English placeholders added during the #2210 integration
(calculation order, text formatting, print, thumbnails, layers, comment
reshape and signature checks, plus the two signature wordings from
3e180944) with Finnish, and updates the fi.tsv sha256 and the entry
count in docs/localization.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:38:44 +03:00
Oliver HagmannandClaude Opus 5.5 9a76f90f07 M5: the arrow keys move the selected comment
With a comment selected, the arrow keys scrolled the view (↑ ↓) or turned the page (← →), so
a text box, rectangle or line could only be placed with the mouse. Now an arrow key moves the
selected comment a screen pixel, and ⌘/Ctrl or Shift and an arrow ten, as Edit text nudges
objects; up is up on screen whatever the zoom, the page's rotation and the view's. Each press
is one undo step (Edit::MoveAnnotation). The keys are taken before the view's navigation,
which has them back once nothing is selected. Text markup stays with its text, and a locked
comment where it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:33:32 +02:00
HiteshandClaude Opus 5.5 7d6af8a508 fix: Delete removes the selected paragraph or objects in Edit text
A paragraph selected by its frame (or objects taken by an area selection) answered the
arrow keys and Escape but not Delete, so the only way to remove text was one typed
character at a time in the inline editor. Delete or Backspace now removes the whole
selection as one undo step: paragraphs and images last-first in their inventories, added
content after them, under the existing "Delete content" history label.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:56:38 +05:30
Hamza 19f40e6554 The two file tests ask which file, not how the OS says it is missing
Review of #3849: both tests asserted `stderr.contains("No such file")`, which
is the Unix wording. On Windows the same error reads "The system cannot find
the file specified. (os error 2)", so `test (windows-latest)` failed on them.

Neither test is about the wording. What they are about:

- `a_flag_that_takes_no_value_does_not_swallow_the_file`: the error is about
  `missing.pdf`, and is not "check-one: missing file" -- the file reached the
  command instead of being eaten as `--edit`'s operand.
- `a_file_whose_name_starts_with_dashes_is_still_reachable`: the error names the
  file the command could not open (`missing.pdf`, or `./--dash.pdf` for the
  `info` case), and is not a usage error about a value or an unknown option.

Both now assert exactly that, plus the absence of our own usage phrases. Every
string they match is one this CLI prints, so the OS has no say in it.
2026-10-22 21:21:00 +05:00
alehel efe2518385 Merge remote-tracking branch 'origin/main' into feat/all-tools-expand-view
# Conflicts:
#	ATTRIBUTION.toml
2026-10-22 18:18:48 +02:00
aiongg d5047aa4ac test(cos): build the private temp directory without an unused mut on Windows
The only mutable use of the DirBuilder is the Unix mode call, so the binding
is mutable only where that call exists.
2026-10-22 12:13:16 -04:00
aiongg 7cdde6c91c chore(assets): record the changed translation catalogues in ATTRIBUTION.toml 2026-10-22 12:13:16 -04:00
aiongg 7c4731234f style(sign): rustfmt the new tests 2026-10-22 12:13:16 -04:00