Files
pdfcraft/crates/cos/src
wstockandBrandon Thomas 72dac33f8e Issue #2367: an object stream whose entry cannot place it is found by its header (#2398)
* Issue #2367: an object stream whose entry cannot place it is found by its header

qpdf's xref-compressed-in-compressed.pdf marks object stream 69 as itself
compressed inside stream 81, which the format forbids and which does not
exist. objstm() loaded the container through its cross-reference entry only,
so every object inside (the outlines, the page labels) read as null and a
full save wrote `/Outlines null /PageLabels null`, while an incremental save
(which copies the original bytes) kept them, and MuPDF recovers them.

The container is now found by its own `N G obj` header when its entry cannot
load it (scan_for, the same last resort a plain object with a wrong offset has).

Regression test: document::tests::an_object_stream_marked_as_compressed_is_found_by_its_header.
Verified on the fixture: objects 70 and 82 load (Count 6; 12 page-label
ranges) and a full save keeps both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011K1peyrh5MdZ6ttHkVq8Gh

The fallback is for file entries only: once the stream is freed or replaced in the overlay,
its objects must not come back from the file's copy (sign's
object_stream_changes_cannot_reuse_old_candidate_membership caught this after the rebase);
a cos test covers both cases.

* cos: a container the saved table marks free is not found by its header either

Freeing an object stream, saving incrementally and reopening left the
overlay empty and the table saying the stream is free; the header
fallback then rescanned the file and the stream's objects came back on
the next full save. The fallback now also stops at an on-disk free
entry.

* M1: put the #2367 test's description on the test, not its fixture

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:48:23 -04:00
..