From 7bf08a22a5c4b73922026535f81a60854b98fba5 Mon Sep 17 00:00:00 2001 From: m-holger Date: Wed, 3 Dec 2025 12:50:51 +0000 Subject: [PATCH] Improve handling of root object `/Type` entries --- libqpdf/QPDF.cc | 14 +++++++++----- libqpdf/qpdf/QPDF_private.hh | 14 ++++++++++++++ manual/release-notes.rst | 25 ++++++++++++++++--------- qpdf/qtest/qpdf/bad-direct-root.out | 1 + qpdf/qtest/qpdf/catalgg.out | 2 +- qpdf/qtest/qpdf/fuzz-16214.out | 1 + qpdf/qtest/qpdf/issue-119.out | 1 + qpdf/qtest/qpdf/issue-120.out | 1 + qpdf/qtest/qpdf/issue-143.out | 1 + qpdf/qtest/qpdf/issue-51.out | 1 + 10 files changed, 46 insertions(+), 15 deletions(-) diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 1b3e034..6fc608d 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -670,11 +670,15 @@ QPDF::getRoot() if (!Root) { throw m->c.damagedPDF("", -1, "unable to find /Root dictionary"); } - // Check_mode is an interim solution to request #810 pending a more comprehensive review of the - // approach to more extensive checks and warning levels. - if (m->cf.check_mode() && Name(Root["/Type"]) != "/Catalog") { - warn(m->c.damagedPDF("", -1, "catalog /Type entry missing or invalid")); - Root.replace("/Type", Name("/Catalog")); + if (!m->objects.root_checked()) { + m->objects.root_checked(true); + if (Name(Root["/Type"]) != "/Catalog") { + warn(m->c.damagedPDF( + "", -1, "Catalog: setting missing or invalid /Type entry to /Catalog")); + if (!global::Options::inspection_mode()) { + Root.replace("/Type", Name("/Catalog")); + } + } } return Root.oh(); } diff --git a/libqpdf/qpdf/QPDF_private.hh b/libqpdf/qpdf/QPDF_private.hh index 2eb58da..58ac61e 100644 --- a/libqpdf/qpdf/QPDF_private.hh +++ b/libqpdf/qpdf/QPDF_private.hh @@ -1022,6 +1022,19 @@ class QPDF::Doc::Objects: Common { return uncompressed_after_compressed_; } + + bool + root_checked() const + { + return root_checked_; + } + + void + root_checked(bool val) + { + root_checked_ = val; + } + void parse(char const* password); std::shared_ptr const& resolve(QPDFObjGen og); void inParse(bool); @@ -1106,6 +1119,7 @@ class QPDF::Doc::Objects: Common // Linearization data qpdf_offset_t first_xref_item_offset_{0}; // actual value from file bool uncompressed_after_compressed_{false}; + bool root_checked_{false}; }; // class QPDF::Doc::Objects // This class is used to represent a PDF Pages tree. diff --git a/manual/release-notes.rst b/manual/release-notes.rst index f566a20..7f66eb3 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -1,6 +1,7 @@ .. _ticket: https://issues.qpdf.org .. _shared null: https://wiki.qpdf.org/PDF-null-objects-vs-qpdf-null-objects + .. _release-notes: Release Notes @@ -17,7 +18,7 @@ more detail. - Release changes - Starting with version 12.3.0, we use - `cosign__`, rather than GPG, + `cosign `__, rather than GPG, to sign releases. See the top-level README.md for instructions. We will continue to use GPG for the 12.x series. Starting with qpdf version 13, only cosign will be used. @@ -122,14 +123,8 @@ more detail. ``damaged_pdf`` error with message "unable to find /Root dictionary" rather than an internal error. -.. _r12-3-0-deprecate: - - - The following are believed to be not in use and have been deprecated. - If you are relying on them please open a ticket_. - - - QPDF::compute_encryption_key - - All QPDF::EncryptionData methods. These methods are not exported in the - shared library and are only useable in statically linked programs. + - Invalid root object `/Type` entries are now unconditionally repaired [#inspect]_. + Previously they were only repaired if the :qpdf:ref:`--check` option was used. - Setting :qpdf:ref:`--compress-streams` to ``n`` or ``QPDFWriter::setCompressStreams(false)`` no longer automatically @@ -141,6 +136,18 @@ more detail. registered by calling ``QPDF::registerStreamFilter``. If you are providing your own stream filters please open a ticket_. +.. _r12-3-0-deprecate: + + - The following are believed to be not in use and have been deprecated. + If you are relying on them please open a ticket_. + + - QPDF::compute_encryption_key + + - All QPDF::EncryptionData methods. These methods are not exported in the + shared library and are only useable in statically linked programs. + +.. [#inspect] not in :ref:`inspection-mode` + 12.2.0: May 4, 2025 - Upcoming C++ Version Change diff --git a/qpdf/qtest/qpdf/bad-direct-root.out b/qpdf/qtest/qpdf/bad-direct-root.out index f07450f..b72df65 100644 --- a/qpdf/qtest/qpdf/bad-direct-root.out +++ b/qpdf/qtest/qpdf/bad-direct-root.out @@ -5,6 +5,7 @@ WARNING: bad-direct-root.pdf: Attempting to reconstruct cross-reference table WARNING: bad-direct-root.pdf (trailer, offset 249): unknown token while reading object; treating as null WARNING: bad-direct-root.pdf (trailer, offset 261): unknown token while reading object; treating as null WARNING: bad-direct-root.pdf (trailer, offset 186): expected dictionary keys but found non-name objects; ignoring +WARNING: bad-direct-root.pdf: Catalog: setting missing or invalid /Type entry to /Catalog WARNING: bad-direct-root.pdf (object 1 0, offset 65): expected endobj WARNING: bad-direct-root.pdf (object 2 0, offset 114): unknown token while reading object; treating as null WARNING: bad-direct-root.pdf (object 2 0, offset 122): invalid character (/) in hexstring diff --git a/qpdf/qtest/qpdf/catalgg.out b/qpdf/qtest/qpdf/catalgg.out index 3c3b806..c885cd4 100644 --- a/qpdf/qtest/qpdf/catalgg.out +++ b/qpdf/qtest/qpdf/catalgg.out @@ -1,4 +1,4 @@ -WARNING: catalgg.pdf: catalog /Type entry missing or invalid +WARNING: catalgg.pdf: Catalog: setting missing or invalid /Type entry to /Catalog checking catalgg.pdf PDF Version: 1.3 File is not encrypted diff --git a/qpdf/qtest/qpdf/fuzz-16214.out b/qpdf/qtest/qpdf/fuzz-16214.out index 28ddb92..b70ba08 100644 --- a/qpdf/qtest/qpdf/fuzz-16214.out +++ b/qpdf/qtest/qpdf/fuzz-16214.out @@ -6,6 +6,7 @@ WARNING: fuzz-16214.pdf (xref stream, offset 116): Cross-reference stream data h WARNING: fuzz-16214.pdf: reported number of objects (6) is not one plus the highest object number (35) WARNING: fuzz-16214.pdf (object 14 0, offset 652): expected dictionary key but found non-name object; inserting key /QPDFFake1 WARNING: fuzz-16214.pdf (object 14 0, offset 734): expected endobj +WARNING: fuzz-16214.pdf: Catalog: setting missing or invalid /Type entry to /Catalog WARNING: fuzz-16214.pdf: file is damaged WARNING: fuzz-16214.pdf (object 1 0, offset 7189): expected n n obj WARNING: fuzz-16214.pdf: Attempting to reconstruct cross-reference table diff --git a/qpdf/qtest/qpdf/issue-119.out b/qpdf/qtest/qpdf/issue-119.out index 6571b3f..ecb6f4c 100644 --- a/qpdf/qtest/qpdf/issue-119.out +++ b/qpdf/qtest/qpdf/issue-119.out @@ -1 +1,2 @@ +WARNING: issue-119.pdf: Catalog: setting missing or invalid /Type entry to /Catalog qpdf: issue-119.pdf: unable to find page tree diff --git a/qpdf/qtest/qpdf/issue-120.out b/qpdf/qtest/qpdf/issue-120.out index dbef34d..f71c580 100644 --- a/qpdf/qtest/qpdf/issue-120.out +++ b/qpdf/qtest/qpdf/issue-120.out @@ -1,2 +1,3 @@ WARNING: issue-120.pdf (xref stream, offset 712): self-referential object stream 3 +WARNING: issue-120.pdf: Catalog: setting missing or invalid /Type entry to /Catalog qpdf: issue-120.pdf: unable to find page tree diff --git a/qpdf/qtest/qpdf/issue-143.out b/qpdf/qtest/qpdf/issue-143.out index d9a5275..987c98a 100644 --- a/qpdf/qtest/qpdf/issue-143.out +++ b/qpdf/qtest/qpdf/issue-143.out @@ -19,4 +19,5 @@ WARNING: issue-143.pdf object stream 1 (object 0 0, offset 11): object id is inv WARNING: issue-143.pdf object stream 1 (object 6 0, offset 21): offset 0 is invalid (must be larger than previous offset 0) WARNING: issue-143.pdf object stream 1 (object 0 0, offset 23): object id is invalid WARNING: issue-143.pdf object stream 1 (object 2 0, offset 33): expected dictionary key but found non-name object; inserting key /QPDFFake1 +WARNING: issue-143.pdf: Catalog: setting missing or invalid /Type entry to /Catalog qpdf: issue-143.pdf: unable to find page tree diff --git a/qpdf/qtest/qpdf/issue-51.out b/qpdf/qtest/qpdf/issue-51.out index eecb96f..70d0d76 100644 --- a/qpdf/qtest/qpdf/issue-51.out +++ b/qpdf/qtest/qpdf/issue-51.out @@ -3,4 +3,5 @@ WARNING: issue-51.pdf: reported number of objects (0) is not one plus the highes WARNING: issue-51.pdf (object 7 0, offset 500): treating bad indirect reference (0 0 R) as null WARNING: issue-51.pdf (object 7 0, offset 476): dictionary has duplicated key /0000; last occurrence overrides earlier ones WARNING: issue-51.pdf (object 7 0, offset 553): expected endobj +WARNING: issue-51.pdf: Catalog: setting missing or invalid /Type entry to /Catalog issue-51.pdf: unable to find page tree -- libgit2 0.21.4