Commit 7bf08a22a5c4b73922026535f81a60854b98fba5

Authored by m-holger
1 parent 0d42bc0b

Improve handling of root object `/Type` entries

- Automatically repair missing or invalid `/Type` entries by setting them to `/Catalog`, unless in inspection mode.
- Update related documentation and warnings to reflect this behavior.
libqpdf/QPDF.cc
@@ -670,11 +670,15 @@ QPDF::getRoot() @@ -670,11 +670,15 @@ QPDF::getRoot()
670 if (!Root) { 670 if (!Root) {
671 throw m->c.damagedPDF("", -1, "unable to find /Root dictionary"); 671 throw m->c.damagedPDF("", -1, "unable to find /Root dictionary");
672 } 672 }
673 - // Check_mode is an interim solution to request #810 pending a more comprehensive review of the  
674 - // approach to more extensive checks and warning levels.  
675 - if (m->cf.check_mode() && Name(Root["/Type"]) != "/Catalog") {  
676 - warn(m->c.damagedPDF("", -1, "catalog /Type entry missing or invalid"));  
677 - Root.replace("/Type", Name("/Catalog")); 673 + if (!m->objects.root_checked()) {
  674 + m->objects.root_checked(true);
  675 + if (Name(Root["/Type"]) != "/Catalog") {
  676 + warn(m->c.damagedPDF(
  677 + "", -1, "Catalog: setting missing or invalid /Type entry to /Catalog"));
  678 + if (!global::Options::inspection_mode()) {
  679 + Root.replace("/Type", Name("/Catalog"));
  680 + }
  681 + }
678 } 682 }
679 return Root.oh(); 683 return Root.oh();
680 } 684 }
libqpdf/qpdf/QPDF_private.hh
@@ -1022,6 +1022,19 @@ class QPDF::Doc::Objects: Common @@ -1022,6 +1022,19 @@ class QPDF::Doc::Objects: Common
1022 { 1022 {
1023 return uncompressed_after_compressed_; 1023 return uncompressed_after_compressed_;
1024 } 1024 }
  1025 +
  1026 + bool
  1027 + root_checked() const
  1028 + {
  1029 + return root_checked_;
  1030 + }
  1031 +
  1032 + void
  1033 + root_checked(bool val)
  1034 + {
  1035 + root_checked_ = val;
  1036 + }
  1037 +
1025 void parse(char const* password); 1038 void parse(char const* password);
1026 std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og); 1039 std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og);
1027 void inParse(bool); 1040 void inParse(bool);
@@ -1106,6 +1119,7 @@ class QPDF::Doc::Objects: Common @@ -1106,6 +1119,7 @@ class QPDF::Doc::Objects: Common
1106 // Linearization data 1119 // Linearization data
1107 qpdf_offset_t first_xref_item_offset_{0}; // actual value from file 1120 qpdf_offset_t first_xref_item_offset_{0}; // actual value from file
1108 bool uncompressed_after_compressed_{false}; 1121 bool uncompressed_after_compressed_{false};
  1122 + bool root_checked_{false};
1109 }; // class QPDF::Doc::Objects 1123 }; // class QPDF::Doc::Objects
1110 1124
1111 // This class is used to represent a PDF Pages tree. 1125 // This class is used to represent a PDF Pages tree.
manual/release-notes.rst
1 .. _ticket: https://issues.qpdf.org 1 .. _ticket: https://issues.qpdf.org
2 .. _shared null: https://wiki.qpdf.org/PDF-null-objects-vs-qpdf-null-objects 2 .. _shared null: https://wiki.qpdf.org/PDF-null-objects-vs-qpdf-null-objects
3 3
  4 +
4 .. _release-notes: 5 .. _release-notes:
5 6
6 Release Notes 7 Release Notes
@@ -17,7 +18,7 @@ more detail. @@ -17,7 +18,7 @@ more detail.
17 - Release changes 18 - Release changes
18 19
19 - Starting with version 12.3.0, we use 20 - Starting with version 12.3.0, we use
20 - `cosign<https://docs.sigstore.dev/cosign/>__`, rather than GPG, 21 + `cosign <https://docs.sigstore.dev/cosign/>`__, rather than GPG,
21 to sign releases. See the top-level README.md for instructions. 22 to sign releases. See the top-level README.md for instructions.
22 We will continue to use GPG for the 12.x series. Starting with 23 We will continue to use GPG for the 12.x series. Starting with
23 qpdf version 13, only cosign will be used. 24 qpdf version 13, only cosign will be used.
@@ -122,14 +123,8 @@ more detail. @@ -122,14 +123,8 @@ more detail.
122 ``damaged_pdf`` error with message "unable to find /Root dictionary" 123 ``damaged_pdf`` error with message "unable to find /Root dictionary"
123 rather than an internal error. 124 rather than an internal error.
124 125
125 -.. _r12-3-0-deprecate:  
126 -  
127 - - The following are believed to be not in use and have been deprecated.  
128 - If you are relying on them please open a ticket_.  
129 -  
130 - - QPDF::compute_encryption_key  
131 - - All QPDF::EncryptionData methods. These methods are not exported in the  
132 - shared library and are only useable in statically linked programs. 126 + - Invalid root object `/Type` entries are now unconditionally repaired [#inspect]_.
  127 + Previously they were only repaired if the :qpdf:ref:`--check` option was used.
133 128
134 - Setting :qpdf:ref:`--compress-streams` to ``n`` or 129 - Setting :qpdf:ref:`--compress-streams` to ``n`` or
135 ``QPDFWriter::setCompressStreams(false)`` no longer automatically 130 ``QPDFWriter::setCompressStreams(false)`` no longer automatically
@@ -141,6 +136,18 @@ more detail. @@ -141,6 +136,18 @@ more detail.
141 registered by calling ``QPDF::registerStreamFilter``. If you are 136 registered by calling ``QPDF::registerStreamFilter``. If you are
142 providing your own stream filters please open a ticket_. 137 providing your own stream filters please open a ticket_.
143 138
  139 +.. _r12-3-0-deprecate:
  140 +
  141 + - The following are believed to be not in use and have been deprecated.
  142 + If you are relying on them please open a ticket_.
  143 +
  144 + - QPDF::compute_encryption_key
  145 +
  146 + - All QPDF::EncryptionData methods. These methods are not exported in the
  147 + shared library and are only useable in statically linked programs.
  148 +
  149 +.. [#inspect] not in :ref:`inspection-mode`
  150 +
144 12.2.0: May 4, 2025 151 12.2.0: May 4, 2025
145 - Upcoming C++ Version Change 152 - Upcoming C++ Version Change
146 153
qpdf/qtest/qpdf/bad-direct-root.out
@@ -5,6 +5,7 @@ WARNING: bad-direct-root.pdf: Attempting to reconstruct cross-reference table @@ -5,6 +5,7 @@ WARNING: bad-direct-root.pdf: Attempting to reconstruct cross-reference table
5 WARNING: bad-direct-root.pdf (trailer, offset 249): unknown token while reading object; treating as null 5 WARNING: bad-direct-root.pdf (trailer, offset 249): unknown token while reading object; treating as null
6 WARNING: bad-direct-root.pdf (trailer, offset 261): unknown token while reading object; treating as null 6 WARNING: bad-direct-root.pdf (trailer, offset 261): unknown token while reading object; treating as null
7 WARNING: bad-direct-root.pdf (trailer, offset 186): expected dictionary keys but found non-name objects; ignoring 7 WARNING: bad-direct-root.pdf (trailer, offset 186): expected dictionary keys but found non-name objects; ignoring
  8 +WARNING: bad-direct-root.pdf: Catalog: setting missing or invalid /Type entry to /Catalog
8 WARNING: bad-direct-root.pdf (object 1 0, offset 65): expected endobj 9 WARNING: bad-direct-root.pdf (object 1 0, offset 65): expected endobj
9 WARNING: bad-direct-root.pdf (object 2 0, offset 114): unknown token while reading object; treating as null 10 WARNING: bad-direct-root.pdf (object 2 0, offset 114): unknown token while reading object; treating as null
10 WARNING: bad-direct-root.pdf (object 2 0, offset 122): invalid character (/) in hexstring 11 WARNING: bad-direct-root.pdf (object 2 0, offset 122): invalid character (/) in hexstring
qpdf/qtest/qpdf/catalgg.out
1 -WARNING: catalgg.pdf: catalog /Type entry missing or invalid 1 +WARNING: catalgg.pdf: Catalog: setting missing or invalid /Type entry to /Catalog
2 checking catalgg.pdf 2 checking catalgg.pdf
3 PDF Version: 1.3 3 PDF Version: 1.3
4 File is not encrypted 4 File is not encrypted
qpdf/qtest/qpdf/fuzz-16214.out
@@ -6,6 +6,7 @@ WARNING: fuzz-16214.pdf (xref stream, offset 116): Cross-reference stream data h @@ -6,6 +6,7 @@ WARNING: fuzz-16214.pdf (xref stream, offset 116): Cross-reference stream data h
6 WARNING: fuzz-16214.pdf: reported number of objects (6) is not one plus the highest object number (35) 6 WARNING: fuzz-16214.pdf: reported number of objects (6) is not one plus the highest object number (35)
7 WARNING: fuzz-16214.pdf (object 14 0, offset 652): expected dictionary key but found non-name object; inserting key /QPDFFake1 7 WARNING: fuzz-16214.pdf (object 14 0, offset 652): expected dictionary key but found non-name object; inserting key /QPDFFake1
8 WARNING: fuzz-16214.pdf (object 14 0, offset 734): expected endobj 8 WARNING: fuzz-16214.pdf (object 14 0, offset 734): expected endobj
  9 +WARNING: fuzz-16214.pdf: Catalog: setting missing or invalid /Type entry to /Catalog
9 WARNING: fuzz-16214.pdf: file is damaged 10 WARNING: fuzz-16214.pdf: file is damaged
10 WARNING: fuzz-16214.pdf (object 1 0, offset 7189): expected n n obj 11 WARNING: fuzz-16214.pdf (object 1 0, offset 7189): expected n n obj
11 WARNING: fuzz-16214.pdf: Attempting to reconstruct cross-reference table 12 WARNING: fuzz-16214.pdf: Attempting to reconstruct cross-reference table
qpdf/qtest/qpdf/issue-119.out
  1 +WARNING: issue-119.pdf: Catalog: setting missing or invalid /Type entry to /Catalog
1 qpdf: issue-119.pdf: unable to find page tree 2 qpdf: issue-119.pdf: unable to find page tree
qpdf/qtest/qpdf/issue-120.out
1 WARNING: issue-120.pdf (xref stream, offset 712): self-referential object stream 3 1 WARNING: issue-120.pdf (xref stream, offset 712): self-referential object stream 3
  2 +WARNING: issue-120.pdf: Catalog: setting missing or invalid /Type entry to /Catalog
2 qpdf: issue-120.pdf: unable to find page tree 3 qpdf: issue-120.pdf: unable to find page tree
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 @@ -19,4 +19,5 @@ WARNING: issue-143.pdf object stream 1 (object 0 0, offset 11): object id is inv
19 WARNING: issue-143.pdf object stream 1 (object 6 0, offset 21): offset 0 is invalid (must be larger than previous offset 0) 19 WARNING: issue-143.pdf object stream 1 (object 6 0, offset 21): offset 0 is invalid (must be larger than previous offset 0)
20 WARNING: issue-143.pdf object stream 1 (object 0 0, offset 23): object id is invalid 20 WARNING: issue-143.pdf object stream 1 (object 0 0, offset 23): object id is invalid
21 WARNING: issue-143.pdf object stream 1 (object 2 0, offset 33): expected dictionary key but found non-name object; inserting key /QPDFFake1 21 WARNING: issue-143.pdf object stream 1 (object 2 0, offset 33): expected dictionary key but found non-name object; inserting key /QPDFFake1
  22 +WARNING: issue-143.pdf: Catalog: setting missing or invalid /Type entry to /Catalog
22 qpdf: issue-143.pdf: unable to find page tree 23 qpdf: issue-143.pdf: unable to find page tree
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 @@ -3,4 +3,5 @@ WARNING: issue-51.pdf: reported number of objects (0) is not one plus the highes
3 WARNING: issue-51.pdf (object 7 0, offset 500): treating bad indirect reference (0 0 R) as null 3 WARNING: issue-51.pdf (object 7 0, offset 500): treating bad indirect reference (0 0 R) as null
4 WARNING: issue-51.pdf (object 7 0, offset 476): dictionary has duplicated key /0000; last occurrence overrides earlier ones 4 WARNING: issue-51.pdf (object 7 0, offset 476): dictionary has duplicated key /0000; last occurrence overrides earlier ones
5 WARNING: issue-51.pdf (object 7 0, offset 553): expected endobj 5 WARNING: issue-51.pdf (object 7 0, offset 553): expected endobj
  6 +WARNING: issue-51.pdf: Catalog: setting missing or invalid /Type entry to /Catalog
6 issue-51.pdf: unable to find page tree 7 issue-51.pdf: unable to find page tree