diff --git a/include/qpdf/QPDFEmbeddedFileDocumentHelper.hh b/include/qpdf/QPDFEmbeddedFileDocumentHelper.hh index f63a491..1a0f606 100644 --- a/include/qpdf/QPDFEmbeddedFileDocumentHelper.hh +++ b/include/qpdf/QPDFEmbeddedFileDocumentHelper.hh @@ -67,19 +67,7 @@ class QPDFEmbeddedFileDocumentHelper: public QPDFDocumentHelper private: void initEmbeddedFiles(); - class Members - { - friend class QPDFEmbeddedFileDocumentHelper; - - public: - ~Members() = default; - - private: - Members() = default; - Members(Members const&) = delete; - - std::shared_ptr embedded_files; - }; + class Members; std::shared_ptr m; }; diff --git a/include/qpdf/QPDFNameTreeObjectHelper.hh b/include/qpdf/QPDFNameTreeObjectHelper.hh index 9405942..1db1241 100644 --- a/include/qpdf/QPDFNameTreeObjectHelper.hh +++ b/include/qpdf/QPDFNameTreeObjectHelper.hh @@ -45,6 +45,19 @@ class QPDF_DLL_CLASS QPDFNameTreeObjectHelper: public QPDFObjectHelper QPDF_DLL QPDFNameTreeObjectHelper(QPDFObjectHandle, QPDF&, bool auto_repair = true); + QPDF_DLL + QPDFNameTreeObjectHelper( + QPDFObjectHandle, + QPDF&, + std::function value_validator, + bool auto_repair); + + // Validate the name tree. Returns true if the tree is valid. + // + // If the tree is not valid and auto_repair is true, attempt to repair the tree. + QPDF_DLL + bool validate(bool repair = true); + // Create an empty name tree QPDF_DLL static QPDFNameTreeObjectHelper newEmpty(QPDF&, bool auto_repair = true); @@ -171,7 +184,11 @@ class QPDF_DLL_CLASS QPDFNameTreeObjectHelper: public QPDFObjectHelper ~Members() = default; private: - Members(QPDFObjectHandle& oh, QPDF&, bool auto_repair); + Members( + QPDFObjectHandle& oh, + QPDF&, + std::function value_validator, + bool auto_repair); Members(Members const&) = delete; std::shared_ptr impl; diff --git a/include/qpdf/QPDFNumberTreeObjectHelper.hh b/include/qpdf/QPDFNumberTreeObjectHelper.hh index 3cc7a89..5d6c669 100644 --- a/include/qpdf/QPDFNumberTreeObjectHelper.hh +++ b/include/qpdf/QPDFNumberTreeObjectHelper.hh @@ -44,6 +44,13 @@ class QPDF_DLL_CLASS QPDFNumberTreeObjectHelper: public QPDFObjectHelper QPDFNumberTreeObjectHelper(QPDFObjectHandle, QPDF&, bool auto_repair = true); QPDF_DLL + QPDFNumberTreeObjectHelper( + QPDFObjectHandle, + QPDF&, + std::function value_validator, + bool auto_repair); + + QPDF_DLL ~QPDFNumberTreeObjectHelper() override; // Create an empty number tree @@ -52,6 +59,12 @@ class QPDF_DLL_CLASS QPDFNumberTreeObjectHelper: public QPDFObjectHelper typedef long long int numtree_number; + // Validate the name tree. Returns true if the tree is valid. + // + // If the tree is not valid and auto_repair is true, attempt to repair the tree. + QPDF_DLL + bool validate(bool repair = true); + // Return overall minimum and maximum indices QPDF_DLL numtree_number getMin(); @@ -188,7 +201,11 @@ class QPDF_DLL_CLASS QPDFNumberTreeObjectHelper: public QPDFObjectHelper ~Members() = default; private: - Members(QPDFObjectHandle& oh, QPDF&, bool auto_repair); + Members( + QPDFObjectHandle& oh, + QPDF&, + std::function value_validator, + bool auto_repair); Members(Members const&) = delete; std::shared_ptr impl; diff --git a/include/qpdf/QPDFOutlineDocumentHelper.hh b/include/qpdf/QPDFOutlineDocumentHelper.hh index a36b4d5..5dc639a 100644 --- a/include/qpdf/QPDFOutlineDocumentHelper.hh +++ b/include/qpdf/QPDFOutlineDocumentHelper.hh @@ -63,33 +63,13 @@ class QPDFOutlineDocumentHelper: public QPDFDocumentHelper { friend class QPDFOutlineObjectHelper; - static bool - checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen og) - { - return !dh.m->seen.add(og); - } + static bool checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen og); }; private: void initializeByPage(); - class Members - { - friend class QPDFOutlineDocumentHelper; - - public: - ~Members() = default; - - private: - Members() = default; - Members(Members const&) = delete; - - std::vector outlines; - QPDFObjGen::set seen; - QPDFObjectHandle dest_dict; - std::shared_ptr names_dest; - std::map> by_page; - }; + class Members; std::shared_ptr m; }; diff --git a/include/qpdf/QPDFPageLabelDocumentHelper.hh b/include/qpdf/QPDFPageLabelDocumentHelper.hh index 9c63172..c5a9c3f 100644 --- a/include/qpdf/QPDFPageLabelDocumentHelper.hh +++ b/include/qpdf/QPDFPageLabelDocumentHelper.hh @@ -77,19 +77,7 @@ class QPDFPageLabelDocumentHelper: public QPDFDocumentHelper std::vector& new_labels); private: - class Members - { - friend class QPDFPageLabelDocumentHelper; - - public: - ~Members() = default; - - private: - Members() = default; - Members(Members const&) = delete; - - std::shared_ptr labels; - }; + class Members; std::shared_ptr m; }; diff --git a/libqpdf/NNTree.cc b/libqpdf/NNTree.cc index 751bd72..d3ff3b6 100644 --- a/libqpdf/NNTree.cc +++ b/libqpdf/NNTree.cc @@ -141,8 +141,8 @@ NNTreeIterator::increment(bool backward) impl.warn(node, "items array doesn't have enough elements"); } else if (!impl.details.keyValid(items[item_number])) { impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type")); - } else if (!items[item_number + 1]) { - impl.warn(node, "item " + std::to_string(item_number) + " is null"); + } else if (!impl.value_valid(items[item_number + 1])) { + impl.warn(node, "item " + std::to_string(item_number + 1) + " is invalid"); } else { return; } @@ -386,6 +386,9 @@ NNTreeIterator::insertAfter(QPDFObjectHandle const& key, QPDFObjectHandle const& if (!(key && value)) { impl.error(node, "insert: key or value is null"); } + if (!impl.value_valid(value)) { + impl.error(node, "insert: value is invalid"); + } items.insert(item_number + 2, key); items.insert(item_number + 3, value); resetLimits(node, lastPathElement()); @@ -621,10 +624,15 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) } NNTreeImpl::NNTreeImpl( - NNTreeDetails const& details, QPDF& qpdf, QPDFObjectHandle& oh, bool auto_repair) : + NNTreeDetails const& details, + QPDF& qpdf, + QPDFObjectHandle& oh, + std::function value_validator, + bool auto_repair) : details(details), qpdf(qpdf), oh(oh), + value_valid(value_validator), auto_repair(auto_repair) { } @@ -740,7 +748,7 @@ NNTreeImpl::repair() { auto new_node = QPDFObjectHandle::newDictionary(); new_node.replaceKey(details.itemsKey(), Array()); - NNTreeImpl repl(details, qpdf, new_node, false); + NNTreeImpl repl(details, qpdf, new_node, value_valid, false); for (auto const& [key, value]: *this) { if (key && value) { repl.insert(key, value); @@ -774,10 +782,17 @@ NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_fo if (first_item == end()) { return end(); } - if (first_item.valid() && details.keyValid(first_item->first) && - details.compareKeys(key, first_item->first) < 0) { - // Before the first key - return end(); + if (first_item.valid()) { + if (!details.keyValid(first_item->first)) { + error(oh, "encountered invalid key in find"); + } + if (!value_valid(first_item->second)) { + error(oh, "encountered invalid value in find"); + } + if (details.compareKeys(key, first_item->first) < 0) { + // Before the first key + return end(); + } } qpdf_assert_debug(!last_item.valid()); @@ -797,6 +812,12 @@ NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_fo key, items, nitems / 2, return_prev_if_not_found, &NNTreeImpl::compareKeyItem); if (idx >= 0) { result.setItemNumber(node, 2 * idx); + if (!result.impl.details.keyValid(result.ivalue.first)) { + error(node, "encountered invalid key in find"); + } + if (!result.impl.value_valid(result.ivalue.second)) { + error(oh, "encountered invalid value in find"); + } } return result; } @@ -830,6 +851,9 @@ NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& val if (!(key && value)) { error(oh, "unable to insert null key or value"); } + if (!value_valid(value)) { + error(oh, "attempting to insert an invalid value"); + } items.insert(0, key); items.insert(1, value); iter.setItemNumber(iter.node, 0); @@ -867,3 +891,33 @@ NNTreeImpl::remove(QPDFObjectHandle const& key, QPDFObjectHandle* value) iter.remove(); return true; } + +bool +NNTreeImpl::validate(bool a_repair) +{ + bool first = true; + QPDFObjectHandle last_key; + try { + for (auto const& [key, value]: *this) { + if (!details.keyValid(key)) { + error(oh, "invalid key in validate"); + } + if (!value_valid(value)) { + error(oh, "invalid value in validate"); + } + if (first) { + first = false; + } else if (last_key && details.compareKeys(last_key, key) != -1) { + error(oh, "keys are not sorted in validate"); + } + last_key = key; + } + } catch (QPDFExc& e) { + if (a_repair) { + warn(oh, std::string("attempting to repair after error: ") + e.what()); + repair(); + } + return false; + } + return true; +} diff --git a/libqpdf/QPDFEmbeddedFileDocumentHelper.cc b/libqpdf/QPDFEmbeddedFileDocumentHelper.cc index 6d1653d..552a389 100644 --- a/libqpdf/QPDFEmbeddedFileDocumentHelper.cc +++ b/libqpdf/QPDFEmbeddedFileDocumentHelper.cc @@ -30,16 +30,30 @@ // >> // >> +class QPDFEmbeddedFileDocumentHelper::Members +{ + public: + Members() = default; + Members(Members const&) = delete; + ~Members() = default; + + std::unique_ptr embedded_files; +}; + QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF& qpdf) : QPDFDocumentHelper(qpdf), - m(new Members()) + m(std::make_shared()) { - auto root = qpdf.getRoot(); - auto names = root.getKey("/Names"); + auto names = qpdf.getRoot().getKey("/Names"); if (names.isDictionary()) { auto embedded_files = names.getKey("/EmbeddedFiles"); if (embedded_files.isDictionary()) { - m->embedded_files = std::make_shared(embedded_files, qpdf); + m->embedded_files = std::make_unique( + embedded_files, + qpdf, + [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, + true); + m->embedded_files->validate(); } } } @@ -65,7 +79,8 @@ QPDFEmbeddedFileDocumentHelper::initEmbeddedFiles() if (!embedded_files.isDictionary()) { auto nth = QPDFNameTreeObjectHelper::newEmpty(qpdf); names.replaceKey("/EmbeddedFiles", nth.getObjectHandle()); - m->embedded_files = std::make_shared(nth); + m->embedded_files = std::make_unique( + nth, qpdf, [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, true); } } diff --git a/libqpdf/QPDFNameTreeObjectHelper.cc b/libqpdf/QPDFNameTreeObjectHelper.cc index 16c0822..d3bccf3 100644 --- a/libqpdf/QPDFNameTreeObjectHelper.cc +++ b/libqpdf/QPDFNameTreeObjectHelper.cc @@ -40,14 +40,29 @@ QPDFNameTreeObjectHelper::~QPDFNameTreeObjectHelper() // NOLINT (modernize-use-e // class, see github issue #745. } -QPDFNameTreeObjectHelper::Members::Members(QPDFObjectHandle& oh, QPDF& q, bool auto_repair) : - impl(std::make_shared(name_tree_details, q, oh, auto_repair)) +QPDFNameTreeObjectHelper::Members::Members( + QPDFObjectHandle& oh, + QPDF& q, + std::function value_validator, + bool auto_repair) : + impl(std::make_shared(name_tree_details, q, oh, value_validator, auto_repair)) { } QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper(QPDFObjectHandle oh, QPDF& q, bool auto_repair) : QPDFObjectHelper(oh), - m(new Members(oh, q, auto_repair)) + m(new Members( + oh, q, [](QPDFObjectHandle const& o) -> bool { return static_cast(o); }, auto_repair)) +{ +} + +QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper( + QPDFObjectHandle oh, + QPDF& q, + std::function value_validator, + bool auto_repair) : + QPDFObjectHelper(oh), + m(new Members(oh, q, value_validator, auto_repair)) { } @@ -200,3 +215,9 @@ QPDFNameTreeObjectHelper::getAsMap() const result.insert(begin(), end()); return result; } + +bool +QPDFNameTreeObjectHelper::validate(bool repair) +{ + return m->impl->validate(repair); +} diff --git a/libqpdf/QPDFNumberTreeObjectHelper.cc b/libqpdf/QPDFNumberTreeObjectHelper.cc index adf3ddd..eae8251 100644 --- a/libqpdf/QPDFNumberTreeObjectHelper.cc +++ b/libqpdf/QPDFNumberTreeObjectHelper.cc @@ -41,15 +41,30 @@ QPDFNumberTreeObjectHelper::~QPDFNumberTreeObjectHelper() // NOLINT (modernize-u // class, see github issue #745. } -QPDFNumberTreeObjectHelper::Members::Members(QPDFObjectHandle& oh, QPDF& q, bool auto_repair) : - impl(std::make_shared(number_tree_details, q, oh, auto_repair)) +QPDFNumberTreeObjectHelper::Members::Members( + QPDFObjectHandle& oh, + QPDF& q, + std::function value_validator, + bool auto_repair) : + impl(std::make_shared(number_tree_details, q, oh, value_validator, auto_repair)) { } QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper( QPDFObjectHandle oh, QPDF& q, bool auto_repair) : QPDFObjectHelper(oh), - m(new Members(oh, q, auto_repair)) + m(new Members( + oh, q, [](QPDFObjectHandle const& o) -> bool { return static_cast(o); }, auto_repair)) +{ +} + +QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper( + QPDFObjectHandle oh, + QPDF& q, + std::function value_validator, + bool auto_repair) : + QPDFObjectHelper(oh), + m(new Members(oh, q, value_validator, auto_repair)) { } @@ -236,3 +251,9 @@ QPDFNumberTreeObjectHelper::getAsMap() const result.insert(begin(), end()); return result; } + +bool +QPDFNumberTreeObjectHelper::validate(bool repair) +{ + return m->impl->validate(repair); +} diff --git a/libqpdf/QPDFOutlineDocumentHelper.cc b/libqpdf/QPDFOutlineDocumentHelper.cc index 13107c3..f1774ff 100644 --- a/libqpdf/QPDFOutlineDocumentHelper.cc +++ b/libqpdf/QPDFOutlineDocumentHelper.cc @@ -2,22 +2,42 @@ #include +class QPDFOutlineDocumentHelper::Members +{ + public: + Members() = default; + Members(Members const&) = delete; + ~Members() = default; + + std::vector outlines; + QPDFObjGen::set seen; + QPDFObjectHandle dest_dict; + std::unique_ptr names_dest; + std::map> by_page; +}; + +bool +QPDFOutlineDocumentHelper::Accessor::checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen og) +{ + return !dh.m->seen.add(og); +} + QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) : QPDFDocumentHelper(qpdf), - m(new Members()) + m(std::make_shared()) { QPDFObjectHandle root = qpdf.getRoot(); if (!root.hasKey("/Outlines")) { return; } - QPDFObjectHandle outlines = root.getKey("/Outlines"); + auto outlines = root.getKey("/Outlines"); if (!(outlines.isDictionary() && outlines.hasKey("/First"))) { return; } QPDFObjectHandle cur = outlines.getKey("/First"); QPDFObjGen::set seen; while (!cur.isNull() && seen.add(cur)) { - m->outlines.push_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1)); + m->outlines.emplace_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1)); cur = cur.getKey("/Next"); } } @@ -55,11 +75,10 @@ QPDFOutlineDocumentHelper::getOutlinesForPage(QPDFObjGen og) if (m->by_page.empty()) { initializeByPage(); } - std::vector result; if (m->by_page.contains(og)) { - result = m->by_page[og]; + return m->by_page[og]; } - return result; + return {}; } QPDFObjectHandle @@ -70,13 +89,19 @@ QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name) if (!m->dest_dict) { m->dest_dict = qpdf.getRoot().getKey("/Dests"); } - QTC::TC("qpdf", "QPDFOutlineDocumentHelper name named dest"); result = m->dest_dict.getKeyIfDict(name.getName()); } else if (name.isString()) { if (!m->names_dest) { auto dests = qpdf.getRoot().getKey("/Names").getKeyIfDict("/Dests"); if (dests.isDictionary()) { - m->names_dest = std::make_shared(dests, qpdf); + m->names_dest = std::make_unique( + dests, + qpdf, + [](QPDFObjectHandle const& o) -> bool { + return o.isArray() || o.isDictionary(); + }, + true); + m->names_dest->validate(); } } if (m->names_dest) { @@ -89,7 +114,6 @@ QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name) return QPDFObjectHandle::newNull(); } if (result.isDictionary()) { - QTC::TC("qpdf", "QPDFOutlineDocumentHelper named dest dictionary"); return result.getKey("/D"); } return result; diff --git a/libqpdf/QPDFPageLabelDocumentHelper.cc b/libqpdf/QPDFPageLabelDocumentHelper.cc index eab70b9..54f8238 100644 --- a/libqpdf/QPDFPageLabelDocumentHelper.cc +++ b/libqpdf/QPDFPageLabelDocumentHelper.cc @@ -2,37 +2,47 @@ #include +class QPDFPageLabelDocumentHelper::Members +{ + public: + Members() = default; + Members(Members const&) = delete; + ~Members() = default; + + std::unique_ptr labels; +}; + QPDFPageLabelDocumentHelper::QPDFPageLabelDocumentHelper(QPDF& qpdf) : QPDFDocumentHelper(qpdf), - m(new Members()) + m(std::make_shared()) { QPDFObjectHandle root = qpdf.getRoot(); if (root.hasKey("/PageLabels")) { - m->labels = - std::make_shared(root.getKey("/PageLabels"), this->qpdf); + m->labels = std::make_unique( + root.getKey("/PageLabels"), + this->qpdf, + [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, + true); + m->labels->validate(); } } bool QPDFPageLabelDocumentHelper::hasPageLabels() { - return nullptr != m->labels; + return m->labels != nullptr; } QPDFObjectHandle QPDFPageLabelDocumentHelper::getLabelForPage(long long page_idx) { - QPDFObjectHandle result(QPDFObjectHandle::newNull()); if (!hasPageLabels()) { - return result; + return QPDFObjectHandle::newNull(); } QPDFNumberTreeObjectHelper::numtree_number offset = 0; QPDFObjectHandle label; if (!m->labels->findObjectAtOrBelow(page_idx, label, offset)) { - return result; - } - if (!label.isDictionary()) { - return result; + return QPDFObjectHandle::newNull(); } QPDFObjectHandle S = label.getKey("/S"); // type (D, R, r, A, a) QPDFObjectHandle P = label.getKey("/P"); // prefix @@ -43,7 +53,7 @@ QPDFPageLabelDocumentHelper::getLabelForPage(long long page_idx) } QIntC::range_check(start, offset); start += offset; - result = QPDFObjectHandle::newDictionary(); + auto result = QPDFObjectHandle::newDictionary(); result.replaceKey("/S", S); result.replaceKey("/P", P); result.replaceKey("/St", QPDFObjectHandle::newInteger(start)); @@ -81,21 +91,20 @@ QPDFPageLabelDocumentHelper::getLabelsForPageRange( label.getKey("/St").getIntValue() - last.getKey("/St").getIntValue(); long long int idx_delta = new_start_idx - last_idx.getIntValue(); if (st_delta == idx_delta) { - QTC::TC("qpdf", "QPDFPageLabelDocumentHelper skip first"); skip_first = true; } } } if (!skip_first) { - new_labels.push_back(QPDFObjectHandle::newInteger(new_start_idx)); - new_labels.push_back(label); + new_labels.emplace_back(QPDFObjectHandle::newInteger(new_start_idx)); + new_labels.emplace_back(label); } long long int idx_offset = new_start_idx - start_idx; for (long long i = start_idx + 1; i <= end_idx; ++i) { if (m->labels->hasIndex(i) && (label = getLabelForPage(i)).isDictionary()) { - new_labels.push_back(QPDFObjectHandle::newInteger(i + idx_offset)); - new_labels.push_back(label); + new_labels.emplace_back(QPDFObjectHandle::newInteger(i + idx_offset)); + new_labels.emplace_back(label); } } } diff --git a/libqpdf/qpdf/NNTree.hh b/libqpdf/qpdf/NNTree.hh index 96d954e..fcaf401 100644 --- a/libqpdf/qpdf/NNTree.hh +++ b/libqpdf/qpdf/NNTree.hh @@ -95,7 +95,12 @@ class NNTreeImpl public: typedef NNTreeIterator iterator; - NNTreeImpl(NNTreeDetails const&, QPDF&, QPDFObjectHandle&, bool auto_repair = true); + NNTreeImpl( + NNTreeDetails const&, + QPDF&, + QPDFObjectHandle&, + std::function value_validator, + bool auto_repair = true); iterator begin(); iterator end(); iterator last(); @@ -104,6 +109,8 @@ class NNTreeImpl iterator insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value); bool remove(QPDFObjectHandle const& key, QPDFObjectHandle* value = nullptr); + bool validate(bool repair = true); + // Change the split threshold for easier testing. There's no real reason to expose this to // downstream tree helpers, but it has to be public so we can call it from the test suite. void setSplitThreshold(int split_threshold); @@ -127,6 +134,7 @@ class NNTreeImpl QPDF& qpdf; int split_threshold{32}; QPDFObjectHandle oh; + const std::function value_valid; bool auto_repair{true}; size_t error_count{0}; }; diff --git a/manual/release-notes.rst b/manual/release-notes.rst index b687099..82c43be 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -23,6 +23,16 @@ more detail. not work on some older Linux distributions. If you need support for an older distribution, please use version 12.2.0 or below. + - Library Enhancements + + - Add ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` + constructor overloads that allow a function to ne passed to + validate the values in the tree. + + - Add new ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` + ``validate`` method to validate and optionally repair the name/number + tree. + - CLI Enhancements - Disallow option :qpdf:ref:`--deterministic-id` to be used together diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 5e9e65f..1ea1757 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -338,13 +338,10 @@ QPDFAcroFormDocumentHelper annotation found 1 QPDFJob keep files open n 0 QPDFJob keep files open y 0 QPDFJob automatically set keep files open 1 -QPDFPageLabelDocumentHelper skip first 0 QPDFOutlineObjectHelper direct dest 0 QPDFOutlineObjectHelper action dest 0 QPDFOutlineObjectHelper named dest 0 -QPDFOutlineDocumentHelper name named dest 0 QPDFOutlineDocumentHelper string named dest 0 -QPDFOutlineDocumentHelper named dest dictionary 0 QPDFOutlineObjectHelper loop 0 QPDFObjectHandle merge top type mismatch 0 QPDFObjectHandle merge shallow copy 0 diff --git a/qpdf/qtest/page-labels.test b/qpdf/qtest/page-labels.test index b90acd6..37f2a61 100644 --- a/qpdf/qtest/page-labels.test +++ b/qpdf/qtest/page-labels.test @@ -14,7 +14,7 @@ cleanup(); my $td = new TestDriver('page-labels'); -my $n_tests = 3; +my $n_tests = 4; $td->runtest("complex page labels", {$td->COMMAND => "test_driver 47 page-labels-num-tree.pdf"}, @@ -29,6 +29,15 @@ $td->runtest("no page labels", {$td->FILE => "no-page-labels.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); +# page-labels-num-tree-damaged.pdf has the following errors: +# - entry for page 29 preceedes entry for page 20 +# - entry for page 3 follows entry for page 6 +# - entry for page 12 is an integer rather than a dictionary +$td->runtest("damaged page labels", + {$td->COMMAND => "test_driver 47 page-labels-num-tree-damaged.pdf"}, + {$td->FILE => "page-labels-num-tree-damaged.out", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + # --set-page-labels my @errors = ( ["quack", ".*page label spec must be.*"], diff --git a/qpdf/qtest/qpdf/page-labels-num-tree-damaged.out b/qpdf/qtest/qpdf/page-labels-num-tree-damaged.out new file mode 100644 index 0000000..9dfbc6e --- /dev/null +++ b/qpdf/qtest/qpdf/page-labels-num-tree-damaged.out @@ -0,0 +1,16 @@ +WARNING: page-labels-num-tree-damaged.pdf (Name/Number tree node (object 2)): attempting to repair after error: page-labels-num-tree-damaged.pdf (Name/Number tree node (object 2)): keys are not sorted in validate +WARNING: page-labels-num-tree-damaged.pdf (Name/Number tree node (object 37)): item 1 is invalid +1 << /S /r /St 1 >> +3 << /P (blank) /St 1 >> +4 << /P (X-) /S /A /St 17 >> +6 << /P () /St 1 >> +7 << /S /R /St 3 >> +10 << /S /D /St 1 >> +13 << /S /a /St 3 >> +16 << /P (q.) /S /D /St 6 >> +20 << /P (www) /St 1 >> +21 << /S /D /St 12 >> +23 << /S /D /St 16059 >> +24 << /S /R /St 50 >> +30 << /S /r /St 54 >> +test 47 done diff --git a/qpdf/qtest/qpdf/page-labels-num-tree-damaged.pdf b/qpdf/qtest/qpdf/page-labels-num-tree-damaged.pdf new file mode 100644 index 0000000..9c32003 --- /dev/null +++ b/qpdf/qtest/qpdf/page-labels-num-tree-damaged.pdf @@ -0,0 +1,1417 @@ +%PDF-1.3 +%¿÷¢þ +%QDF-1.0 + +1 0 obj +<< + /PageLabels 2 0 R + /Pages 3 0 R + /Type /Catalog +>> +endobj + +2 0 obj +<< + /Kids [ + 4 0 R + 5 0 R + ] +>> +endobj + +3 0 obj +<< + /Count 30 + /Kids [ + 6 0 R + 7 0 R + 8 0 R + 9 0 R + 10 0 R + 11 0 R + 12 0 R + 13 0 R + 14 0 R + 15 0 R + 16 0 R + 17 0 R + 18 0 R + 19 0 R + 20 0 R + 21 0 R + 22 0 R + 23 0 R + 24 0 R + 25 0 R + 26 0 R + 27 0 R + 28 0 R + 29 0 R + 30 0 R + 31 0 R + 32 0 R + 33 0 R + 34 0 R + 35 0 R + ] + /Type /Pages +>> +endobj + +4 0 obj +<< + /Kids [ + 36 0 R + 37 0 R + ] + /Limits [ + 0 + 19 + ] +>> +endobj + +5 0 obj +<< + /Limits [ + 20 + 29 + ] + /Nums [ + 29 << /S /r /St 54 >> + 20 << /S /D /St 12 >> + 22 << /S /D /St 16059 >> + 23 << /S /R /St 50 >> + ] +>> +endobj + +%% Page 1 +6 0 obj +<< + /Contents 38 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 2 +7 0 obj +<< + /Contents 42 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 3 +8 0 obj +<< + /Contents 44 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 4 +9 0 obj +<< + /Contents 46 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 5 +10 0 obj +<< + /Contents 48 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 6 +11 0 obj +<< + /Contents 50 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 7 +12 0 obj +<< + /Contents 52 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 8 +13 0 obj +<< + /Contents 54 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 9 +14 0 obj +<< + /Contents 56 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 10 +15 0 obj +<< + /Contents 58 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 11 +16 0 obj +<< + /Contents 60 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 12 +17 0 obj +<< + /Contents 62 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 13 +18 0 obj +<< + /Contents 64 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 14 +19 0 obj +<< + /Contents 66 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 15 +20 0 obj +<< + /Contents 68 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 16 +21 0 obj +<< + /Contents 70 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 17 +22 0 obj +<< + /Contents 72 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 18 +23 0 obj +<< + /Contents 74 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 19 +24 0 obj +<< + /Contents 76 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 20 +25 0 obj +<< + /Contents 78 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 21 +26 0 obj +<< + /Contents 80 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 22 +27 0 obj +<< + /Contents 82 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 23 +28 0 obj +<< + /Contents 84 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 24 +29 0 obj +<< + /Contents 86 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 25 +30 0 obj +<< + /Contents 88 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 26 +31 0 obj +<< + /Contents 90 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 27 +32 0 obj +<< + /Contents 92 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 28 +33 0 obj +<< + /Contents 94 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 29 +34 0 obj +<< + /Contents 96 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +%% Page 30 +35 0 obj +<< + /Contents 98 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 40 0 R + >> + /ProcSet 41 0 R + >> + /Type /Page +>> +endobj + +36 0 obj +<< + /Limits [ + 0 + 9 + ] + /Nums [ + 0 << /S /r >> + 2 << /P (blank) >> + 5 << /P () >> + 6 << /S /R /St 3 >> + 3 << /P (X-) /S /A /St 17 >> + 9 << /S /D >> + ] +>> +endobj + +37 0 obj +<< + /Limits [ + 11 + 19 + ] + /Nums [ + 11 42 + 12 << /S /a /St 3 >> + 15 << /P (q.) /S /D /St 6 >> + 19 << /P (www) >> + ] +>> +endobj + +%% Contents for page 1 +38 0 obj +<< + /Length 39 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 0) Tj +ET +endstream +endobj + +39 0 obj +46 +endobj + +40 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +41 0 obj +[ + /PDF + /Text +] +endobj + +%% Contents for page 2 +42 0 obj +<< + /Length 43 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 1) Tj +ET +endstream +endobj + +43 0 obj +46 +endobj + +%% Contents for page 3 +44 0 obj +<< + /Length 45 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 2) Tj +ET +endstream +endobj + +45 0 obj +46 +endobj + +%% Contents for page 4 +46 0 obj +<< + /Length 47 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 3) Tj +ET +endstream +endobj + +47 0 obj +46 +endobj + +%% Contents for page 5 +48 0 obj +<< + /Length 49 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 4) Tj +ET +endstream +endobj + +49 0 obj +46 +endobj + +%% Contents for page 6 +50 0 obj +<< + /Length 51 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 5) Tj +ET +endstream +endobj + +51 0 obj +46 +endobj + +%% Contents for page 7 +52 0 obj +<< + /Length 53 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 6) Tj +ET +endstream +endobj + +53 0 obj +46 +endobj + +%% Contents for page 8 +54 0 obj +<< + /Length 55 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 7) Tj +ET +endstream +endobj + +55 0 obj +46 +endobj + +%% Contents for page 9 +56 0 obj +<< + /Length 57 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 8) Tj +ET +endstream +endobj + +57 0 obj +46 +endobj + +%% Contents for page 10 +58 0 obj +<< + /Length 59 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 9) Tj +ET +endstream +endobj + +59 0 obj +46 +endobj + +%% Contents for page 11 +60 0 obj +<< + /Length 61 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 10) Tj +ET +endstream +endobj + +61 0 obj +47 +endobj + +%% Contents for page 12 +62 0 obj +<< + /Length 63 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 11) Tj +ET +endstream +endobj + +63 0 obj +47 +endobj + +%% Contents for page 13 +64 0 obj +<< + /Length 65 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 12) Tj +ET +endstream +endobj + +65 0 obj +47 +endobj + +%% Contents for page 14 +66 0 obj +<< + /Length 67 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 13) Tj +ET +endstream +endobj + +67 0 obj +47 +endobj + +%% Contents for page 15 +68 0 obj +<< + /Length 69 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 14) Tj +ET +endstream +endobj + +69 0 obj +47 +endobj + +%% Contents for page 16 +70 0 obj +<< + /Length 71 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 15) Tj +ET +endstream +endobj + +71 0 obj +47 +endobj + +%% Contents for page 17 +72 0 obj +<< + /Length 73 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 16) Tj +ET +endstream +endobj + +73 0 obj +47 +endobj + +%% Contents for page 18 +74 0 obj +<< + /Length 75 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 17) Tj +ET +endstream +endobj + +75 0 obj +47 +endobj + +%% Contents for page 19 +76 0 obj +<< + /Length 77 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 18) Tj +ET +endstream +endobj + +77 0 obj +47 +endobj + +%% Contents for page 20 +78 0 obj +<< + /Length 79 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 19) Tj +ET +endstream +endobj + +79 0 obj +47 +endobj + +%% Contents for page 21 +80 0 obj +<< + /Length 81 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 20) Tj +ET +endstream +endobj + +81 0 obj +47 +endobj + +%% Contents for page 22 +82 0 obj +<< + /Length 83 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 21) Tj +ET +endstream +endobj + +83 0 obj +47 +endobj + +%% Contents for page 23 +84 0 obj +<< + /Length 85 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 22) Tj +ET +endstream +endobj + +85 0 obj +47 +endobj + +%% Contents for page 24 +86 0 obj +<< + /Length 87 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 23) Tj +ET +endstream +endobj + +87 0 obj +47 +endobj + +%% Contents for page 25 +88 0 obj +<< + /Length 89 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 24) Tj +ET +endstream +endobj + +89 0 obj +47 +endobj + +%% Contents for page 26 +90 0 obj +<< + /Length 91 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 25) Tj +ET +endstream +endobj + +91 0 obj +47 +endobj + +%% Contents for page 27 +92 0 obj +<< + /Length 93 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 26) Tj +ET +endstream +endobj + +93 0 obj +47 +endobj + +%% Contents for page 28 +94 0 obj +<< + /Length 95 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 27) Tj +ET +endstream +endobj + +95 0 obj +47 +endobj + +%% Contents for page 29 +96 0 obj +<< + /Length 97 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 28) Tj +ET +endstream +endobj + +97 0 obj +47 +endobj + +%% Contents for page 30 +98 0 obj +<< + /Length 99 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 29) Tj +ET +endstream +endobj + +99 0 obj +47 +endobj + +xref +0 100 +0000000000 65535 f +0000000025 00000 n +0000000099 00000 n +0000000155 00000 n +0000000544 00000 n +0000000631 00000 n +0000000814 00000 n +0000001019 00000 n +0000001224 00000 n +0000001429 00000 n +0000001634 00000 n +0000001840 00000 n +0000002046 00000 n +0000002252 00000 n +0000002458 00000 n +0000002665 00000 n +0000002872 00000 n +0000003079 00000 n +0000003286 00000 n +0000003493 00000 n +0000003700 00000 n +0000003907 00000 n +0000004114 00000 n +0000004321 00000 n +0000004528 00000 n +0000004735 00000 n +0000004942 00000 n +0000005149 00000 n +0000005356 00000 n +0000005563 00000 n +0000005770 00000 n +0000005977 00000 n +0000006184 00000 n +0000006391 00000 n +0000006598 00000 n +0000006805 00000 n +0000007001 00000 n +0000007200 00000 n +0000007389 00000 n +0000007492 00000 n +0000007512 00000 n +0000007631 00000 n +0000007690 00000 n +0000007793 00000 n +0000007836 00000 n +0000007939 00000 n +0000007982 00000 n +0000008085 00000 n +0000008128 00000 n +0000008231 00000 n +0000008274 00000 n +0000008377 00000 n +0000008420 00000 n +0000008523 00000 n +0000008566 00000 n +0000008669 00000 n +0000008712 00000 n +0000008815 00000 n +0000008859 00000 n +0000008962 00000 n +0000009006 00000 n +0000009110 00000 n +0000009154 00000 n +0000009258 00000 n +0000009302 00000 n +0000009406 00000 n +0000009450 00000 n +0000009554 00000 n +0000009598 00000 n +0000009702 00000 n +0000009746 00000 n +0000009850 00000 n +0000009894 00000 n +0000009998 00000 n +0000010042 00000 n +0000010146 00000 n +0000010190 00000 n +0000010294 00000 n +0000010338 00000 n +0000010442 00000 n +0000010486 00000 n +0000010590 00000 n +0000010634 00000 n +0000010738 00000 n +0000010782 00000 n +0000010886 00000 n +0000010930 00000 n +0000011034 00000 n +0000011078 00000 n +0000011182 00000 n +0000011226 00000 n +0000011330 00000 n +0000011374 00000 n +0000011478 00000 n +0000011522 00000 n +0000011626 00000 n +0000011670 00000 n +0000011774 00000 n +0000011818 00000 n +0000011922 00000 n +trailer << + /Root 1 0 R + /Size 100 + /ID [<90f919de7874f3bc5cb7afbd1e9537bb><0dfe18a94cde0f4bfdc86c03af19010e>] +>> +startxref +11942 +%%EOF