Commit 9f9f01d523dc2708ecd89f87c1b34c21f65d2cc2
1 parent
d285dab8
Refactor `NNTree` to encapsulate warning and error handling into `NNTreeImpl`, a…
…dding error counting and leveraging `reconstructed_xref` for improved robustness, limiting the acceptable number of errors for damaged files.
Showing
7 changed files
with
43 additions
and
38 deletions
fuzz/CMakeLists.txt
| @@ -160,6 +160,7 @@ set(CORPUS_OTHER | @@ -160,6 +160,7 @@ set(CORPUS_OTHER | ||
| 160 | 411312393.fuzz | 160 | 411312393.fuzz |
| 161 | 433311400.fuzz | 161 | 433311400.fuzz |
| 162 | 5109284021272576.fuzz | 162 | 5109284021272576.fuzz |
| 163 | + 6489005569146880.fuzz | ||
| 163 | ) | 164 | ) |
| 164 | 165 | ||
| 165 | set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) | 166 | set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) |
fuzz/qpdf_extra/6489005569146880.fuzz
0 → 100644
No preview for this file type
fuzz/qtest/fuzz.test
| @@ -11,7 +11,7 @@ my $td = new TestDriver('fuzz'); | @@ -11,7 +11,7 @@ my $td = new TestDriver('fuzz'); | ||
| 11 | 11 | ||
| 12 | my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS"; | 12 | my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS"; |
| 13 | 13 | ||
| 14 | -my $n_qpdf_files = 97; # increment when adding new files | 14 | +my $n_qpdf_files = 98; # increment when adding new files |
| 15 | 15 | ||
| 16 | my @fuzzers = ( | 16 | my @fuzzers = ( |
| 17 | ['ascii85' => 1], | 17 | ['ascii85' => 1], |
include/qpdf/QPDF.hh
| @@ -791,6 +791,8 @@ class QPDF | @@ -791,6 +791,8 @@ class QPDF | ||
| 791 | class Pipe; | 791 | class Pipe; |
| 792 | class JobSetter; | 792 | class JobSetter; |
| 793 | 793 | ||
| 794 | + inline bool reconstructed_xref() const; | ||
| 795 | + | ||
| 794 | // For testing only -- do not add to DLL | 796 | // For testing only -- do not add to DLL |
| 795 | static bool test_json_validators(); | 797 | static bool test_json_validators(); |
| 796 | 798 |
libqpdf/NNTree.cc
| 1 | #include <qpdf/NNTree.hh> | 1 | #include <qpdf/NNTree.hh> |
| 2 | 2 | ||
| 3 | +#include <qpdf/QPDF_private.hh> | ||
| 3 | #include <qpdf/QTC.hh> | 4 | #include <qpdf/QTC.hh> |
| 4 | #include <qpdf/QUtil.hh> | 5 | #include <qpdf/QUtil.hh> |
| 5 | 6 | ||
| @@ -15,14 +16,17 @@ get_description(QPDFObjectHandle& node) | @@ -15,14 +16,17 @@ get_description(QPDFObjectHandle& node) | ||
| 15 | return result; | 16 | return result; |
| 16 | } | 17 | } |
| 17 | 18 | ||
| 18 | -static void | ||
| 19 | -warn(QPDF& qpdf, QPDFObjectHandle& node, std::string const& msg) | 19 | +void |
| 20 | +NNTreeImpl::warn(QPDFObjectHandle& node, std::string const& msg) | ||
| 20 | { | 21 | { |
| 21 | qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg); | 22 | qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg); |
| 23 | + if (++error_count > 5 && qpdf.reconstructed_xref()) { | ||
| 24 | + error(node, "too many errors - giving up"); | ||
| 25 | + } | ||
| 22 | } | 26 | } |
| 23 | 27 | ||
| 24 | -static void | ||
| 25 | -error(QPDF& qpdf, QPDFObjectHandle& node, std::string const& msg) | 28 | +void |
| 29 | +NNTreeImpl::error(QPDFObjectHandle& node, std::string const& msg) | ||
| 26 | { | 30 | { |
| 27 | throw QPDFExc(qpdf_e_damaged_pdf, qpdf.getFilename(), get_description(node), 0, msg); | 31 | throw QPDFExc(qpdf_e_damaged_pdf, qpdf.getFilename(), get_description(node), 0, msg); |
| 28 | } | 32 | } |
| @@ -56,7 +60,7 @@ NNTreeIterator::updateIValue(bool allow_invalid) | @@ -56,7 +60,7 @@ NNTreeIterator::updateIValue(bool allow_invalid) | ||
| 56 | ivalue.first = items.getArrayItem(item_number); | 60 | ivalue.first = items.getArrayItem(item_number); |
| 57 | ivalue.second = items.getArrayItem(1 + item_number); | 61 | ivalue.second = items.getArrayItem(1 + item_number); |
| 58 | } else { | 62 | } else { |
| 59 | - error(impl.qpdf, node, "update ivalue: items array is too short"); | 63 | + impl.error(node, "update ivalue: items array is too short"); |
| 60 | } | 64 | } |
| 61 | } | 65 | } |
| 62 | if (!okay) { | 66 | if (!okay) { |
| @@ -90,8 +94,7 @@ NNTreeIterator::getNextKid(PathElement& pe, bool backward) | @@ -90,8 +94,7 @@ NNTreeIterator::getNextKid(PathElement& pe, bool backward) | ||
| 90 | found = true; | 94 | found = true; |
| 91 | } else { | 95 | } else { |
| 92 | QTC::TC("qpdf", "NNTree skip invalid kid"); | 96 | QTC::TC("qpdf", "NNTree skip invalid kid"); |
| 93 | - warn( | ||
| 94 | - impl.qpdf, | 97 | + impl.warn( |
| 95 | pe.node, | 98 | pe.node, |
| 96 | ("skipping over invalid kid at index " + std::to_string(pe.kid_number))); | 99 | ("skipping over invalid kid at index " + std::to_string(pe.kid_number))); |
| 97 | } | 100 | } |
| @@ -138,13 +141,10 @@ NNTreeIterator::increment(bool backward) | @@ -138,13 +141,10 @@ NNTreeIterator::increment(bool backward) | ||
| 138 | items = node.getKey(impl.details.itemsKey()); | 141 | items = node.getKey(impl.details.itemsKey()); |
| 139 | if (item_number + 1 >= items.getArrayNItems()) { | 142 | if (item_number + 1 >= items.getArrayNItems()) { |
| 140 | QTC::TC("qpdf", "NNTree skip item at end of short items"); | 143 | QTC::TC("qpdf", "NNTree skip item at end of short items"); |
| 141 | - warn(impl.qpdf, node, "items array doesn't have enough elements"); | 144 | + impl.warn(node, "items array doesn't have enough elements"); |
| 142 | } else if (!impl.details.keyValid(items.getArrayItem(item_number))) { | 145 | } else if (!impl.details.keyValid(items.getArrayItem(item_number))) { |
| 143 | QTC::TC("qpdf", "NNTree skip invalid key"); | 146 | QTC::TC("qpdf", "NNTree skip invalid key"); |
| 144 | - warn( | ||
| 145 | - impl.qpdf, | ||
| 146 | - node, | ||
| 147 | - ("item " + std::to_string(item_number) + " has the wrong type")); | 147 | + impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type")); |
| 148 | } else { | 148 | } else { |
| 149 | found_valid_key = true; | 149 | found_valid_key = true; |
| 150 | } | 150 | } |
| @@ -207,7 +207,7 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list<PathElement>::ite | @@ -207,7 +207,7 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list<PathElement>::ite | ||
| 207 | } | 207 | } |
| 208 | } else { | 208 | } else { |
| 209 | QTC::TC("qpdf", "NNTree unable to determine limits"); | 209 | QTC::TC("qpdf", "NNTree unable to determine limits"); |
| 210 | - warn(impl.qpdf, a_node, "unable to determine limits"); | 210 | + impl.warn(a_node, "unable to determine limits"); |
| 211 | } | 211 | } |
| 212 | 212 | ||
| 213 | if (!changed || parent == path.begin()) { | 213 | if (!changed || parent == path.begin()) { |
| @@ -396,10 +396,10 @@ NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) | @@ -396,10 +396,10 @@ NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) | ||
| 396 | 396 | ||
| 397 | auto items = node.getKey(impl.details.itemsKey()); | 397 | auto items = node.getKey(impl.details.itemsKey()); |
| 398 | if (!items.isArray()) { | 398 | if (!items.isArray()) { |
| 399 | - error(impl.qpdf, node, "node contains no items array"); | 399 | + impl.error(node, "node contains no items array"); |
| 400 | } | 400 | } |
| 401 | if (items.getArrayNItems() < item_number + 2) { | 401 | if (items.getArrayNItems() < item_number + 2) { |
| 402 | - error(impl.qpdf, node, "insert: items array is too short"); | 402 | + impl.error(node, "insert: items array is too short"); |
| 403 | } | 403 | } |
| 404 | items.insertItem(item_number + 2, key); | 404 | items.insertItem(item_number + 2, key); |
| 405 | items.insertItem(item_number + 3, value); | 405 | items.insertItem(item_number + 3, value); |
| @@ -419,7 +419,7 @@ NNTreeIterator::remove() | @@ -419,7 +419,7 @@ NNTreeIterator::remove() | ||
| 419 | auto items = node.getKey(impl.details.itemsKey()); | 419 | auto items = node.getKey(impl.details.itemsKey()); |
| 420 | int nitems = items.getArrayNItems(); | 420 | int nitems = items.getArrayNItems(); |
| 421 | if (item_number + 2 > nitems) { | 421 | if (item_number + 2 > nitems) { |
| 422 | - error(impl.qpdf, node, "found short items array while removing an item"); | 422 | + impl.error(node, "found short items array while removing an item"); |
| 423 | } | 423 | } |
| 424 | 424 | ||
| 425 | items.eraseItem(item_number); | 425 | items.eraseItem(item_number); |
| @@ -596,14 +596,14 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) | @@ -596,14 +596,14 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) | ||
| 596 | while (!failed) { | 596 | while (!failed) { |
| 597 | if (!seen.add(a_node)) { | 597 | if (!seen.add(a_node)) { |
| 598 | QTC::TC("qpdf", "NNTree deepen: loop"); | 598 | QTC::TC("qpdf", "NNTree deepen: loop"); |
| 599 | - warn(impl.qpdf, a_node, "loop detected while traversing name/number tree"); | 599 | + impl.warn(a_node, "loop detected while traversing name/number tree"); |
| 600 | failed = true; | 600 | failed = true; |
| 601 | break; | 601 | break; |
| 602 | } | 602 | } |
| 603 | 603 | ||
| 604 | if (!a_node.isDictionary()) { | 604 | if (!a_node.isDictionary()) { |
| 605 | QTC::TC("qpdf", "NNTree node is not a dictionary"); | 605 | QTC::TC("qpdf", "NNTree node is not a dictionary"); |
| 606 | - warn(impl.qpdf, a_node, "non-dictionary node while traversing name/number tree"); | 606 | + impl.warn(a_node, "non-dictionary node while traversing name/number tree"); |
| 607 | failed = true; | 607 | failed = true; |
| 608 | break; | 608 | break; |
| 609 | } | 609 | } |
| @@ -622,8 +622,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) | @@ -622,8 +622,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) | ||
| 622 | if (!next.isIndirect()) { | 622 | if (!next.isIndirect()) { |
| 623 | if (impl.auto_repair) { | 623 | if (impl.auto_repair) { |
| 624 | QTC::TC("qpdf", "NNTree fix indirect kid"); | 624 | QTC::TC("qpdf", "NNTree fix indirect kid"); |
| 625 | - warn( | ||
| 626 | - impl.qpdf, | 625 | + impl.warn( |
| 627 | a_node, | 626 | a_node, |
| 628 | ("converting kid number " + std::to_string(kid_number) + | 627 | ("converting kid number " + std::to_string(kid_number) + |
| 629 | " to an indirect object")); | 628 | " to an indirect object")); |
| @@ -631,8 +630,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) | @@ -631,8 +630,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) | ||
| 631 | kids.setArrayItem(kid_number, next); | 630 | kids.setArrayItem(kid_number, next); |
| 632 | } else { | 631 | } else { |
| 633 | QTC::TC("qpdf", "NNTree warn indirect kid"); | 632 | QTC::TC("qpdf", "NNTree warn indirect kid"); |
| 634 | - warn( | ||
| 635 | - impl.qpdf, | 633 | + impl.warn( |
| 636 | a_node, | 634 | a_node, |
| 637 | ("kid number " + std::to_string(kid_number) + | 635 | ("kid number " + std::to_string(kid_number) + |
| 638 | " is not an indirect object")); | 636 | " is not an indirect object")); |
| @@ -645,8 +643,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) | @@ -645,8 +643,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) | ||
| 645 | break; | 643 | break; |
| 646 | } else { | 644 | } else { |
| 647 | QTC::TC("qpdf", "NNTree deepen: invalid node"); | 645 | QTC::TC("qpdf", "NNTree deepen: invalid node"); |
| 648 | - warn( | ||
| 649 | - impl.qpdf, | 646 | + impl.warn( |
| 650 | a_node, | 647 | a_node, |
| 651 | ("name/number tree node has neither non-empty " + impl.details.itemsKey() + | 648 | ("name/number tree node has neither non-empty " + impl.details.itemsKey() + |
| 652 | " nor /Kids")); | 649 | " nor /Kids")); |
| @@ -712,7 +709,7 @@ NNTreeImpl::withinLimits(QPDFObjectHandle key, QPDFObjectHandle node) | @@ -712,7 +709,7 @@ NNTreeImpl::withinLimits(QPDFObjectHandle key, QPDFObjectHandle node) | ||
| 712 | } | 709 | } |
| 713 | } else { | 710 | } else { |
| 714 | QTC::TC("qpdf", "NNTree missing limits"); | 711 | QTC::TC("qpdf", "NNTree missing limits"); |
| 715 | - error(qpdf, node, "node is missing /Limits"); | 712 | + error(node, "node is missing /Limits"); |
| 716 | } | 713 | } |
| 717 | return result; | 714 | return result; |
| 718 | } | 715 | } |
| @@ -782,7 +779,7 @@ NNTreeImpl::compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int i | @@ -782,7 +779,7 @@ NNTreeImpl::compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int i | ||
| 782 | if (!((items.isArray() && (items.getArrayNItems() > (2 * idx)) && | 779 | if (!((items.isArray() && (items.getArrayNItems() > (2 * idx)) && |
| 783 | details.keyValid(items.getArrayItem(2 * idx))))) { | 780 | details.keyValid(items.getArrayItem(2 * idx))))) { |
| 784 | QTC::TC("qpdf", "NNTree item is wrong type"); | 781 | QTC::TC("qpdf", "NNTree item is wrong type"); |
| 785 | - error(qpdf, oh, ("item at index " + std::to_string(2 * idx) + " is not the right type")); | 782 | + error(oh, ("item at index " + std::to_string(2 * idx) + " is not the right type")); |
| 786 | } | 783 | } |
| 787 | return details.compareKeys(key, items.getArrayItem(2 * idx)); | 784 | return details.compareKeys(key, items.getArrayItem(2 * idx)); |
| 788 | } | 785 | } |
| @@ -793,7 +790,7 @@ NNTreeImpl::compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& kids, int idx | @@ -793,7 +790,7 @@ NNTreeImpl::compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& kids, int idx | ||
| 793 | if (!(kids.isArray() && (idx < kids.getArrayNItems()) && | 790 | if (!(kids.isArray() && (idx < kids.getArrayNItems()) && |
| 794 | kids.getArrayItem(idx).isDictionary())) { | 791 | kids.getArrayItem(idx).isDictionary())) { |
| 795 | QTC::TC("qpdf", "NNTree kid is invalid"); | 792 | QTC::TC("qpdf", "NNTree kid is invalid"); |
| 796 | - error(qpdf, oh, "invalid kid at index " + std::to_string(idx)); | 793 | + error(oh, "invalid kid at index " + std::to_string(idx)); |
| 797 | } | 794 | } |
| 798 | return withinLimits(key, kids.getArrayItem(idx)); | 795 | return withinLimits(key, kids.getArrayItem(idx)); |
| 799 | } | 796 | } |
| @@ -819,7 +816,7 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) | @@ -819,7 +816,7 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) | ||
| 819 | } catch (QPDFExc& e) { | 816 | } catch (QPDFExc& e) { |
| 820 | if (auto_repair) { | 817 | if (auto_repair) { |
| 821 | QTC::TC("qpdf", "NNTree repair"); | 818 | QTC::TC("qpdf", "NNTree repair"); |
| 822 | - warn(qpdf, oh, std::string("attempting to repair after error: ") + e.what()); | 819 | + warn(oh, std::string("attempting to repair after error: ") + e.what()); |
| 823 | repair(); | 820 | repair(); |
| 824 | return findInternal(key, return_prev_if_not_found); | 821 | return findInternal(key, return_prev_if_not_found); |
| 825 | } else { | 822 | } else { |
| @@ -859,7 +856,7 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) | @@ -859,7 +856,7 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) | ||
| 859 | while (true) { | 856 | while (true) { |
| 860 | if (!seen.add(node)) { | 857 | if (!seen.add(node)) { |
| 861 | QTC::TC("qpdf", "NNTree loop in find"); | 858 | QTC::TC("qpdf", "NNTree loop in find"); |
| 862 | - error(qpdf, node, "loop detected in find"); | 859 | + error(node, "loop detected in find"); |
| 863 | } | 860 | } |
| 864 | 861 | ||
| 865 | auto kids = node.getKey("/Kids"); | 862 | auto kids = node.getKey("/Kids"); |
| @@ -877,17 +874,13 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) | @@ -877,17 +874,13 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) | ||
| 877 | int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid); | 874 | int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid); |
| 878 | if (idx == -1) { | 875 | if (idx == -1) { |
| 879 | QTC::TC("qpdf", "NNTree -1 in binary search"); | 876 | QTC::TC("qpdf", "NNTree -1 in binary search"); |
| 880 | - error( | ||
| 881 | - qpdf, | ||
| 882 | - node, | ||
| 883 | - "unexpected -1 from binary search of kids;" | ||
| 884 | - " limits may by wrong"); | 877 | + error(node, "unexpected -1 from binary search of kids; limits may by wrong"); |
| 885 | } | 878 | } |
| 886 | result.addPathElement(node, idx); | 879 | result.addPathElement(node, idx); |
| 887 | node = kids.getArrayItem(idx); | 880 | node = kids.getArrayItem(idx); |
| 888 | } else { | 881 | } else { |
| 889 | QTC::TC("qpdf", "NNTree bad node during find"); | 882 | QTC::TC("qpdf", "NNTree bad node during find"); |
| 890 | - error(qpdf, node, "bad node during find"); | 883 | + error(node, "bad node during find"); |
| 891 | } | 884 | } |
| 892 | } | 885 | } |
| 893 | 886 | ||
| @@ -904,7 +897,7 @@ NNTreeImpl::insertFirst(QPDFObjectHandle key, QPDFObjectHandle value) | @@ -904,7 +897,7 @@ NNTreeImpl::insertFirst(QPDFObjectHandle key, QPDFObjectHandle value) | ||
| 904 | } | 897 | } |
| 905 | if (!(items.isArray())) { | 898 | if (!(items.isArray())) { |
| 906 | QTC::TC("qpdf", "NNTree no valid items node in insertFirst"); | 899 | QTC::TC("qpdf", "NNTree no valid items node in insertFirst"); |
| 907 | - error(qpdf, oh, "unable to find a valid items node"); | 900 | + error(oh, "unable to find a valid items node"); |
| 908 | } | 901 | } |
| 909 | items.insertItem(0, key); | 902 | items.insertItem(0, key); |
| 910 | items.insertItem(1, value); | 903 | items.insertItem(1, value); |
libqpdf/qpdf/NNTree.hh
| @@ -120,12 +120,15 @@ class NNTreeImpl | @@ -120,12 +120,15 @@ class NNTreeImpl | ||
| 120 | int (NNTreeImpl::*compare)(QPDFObjectHandle& key, QPDFObjectHandle& arr, int item)); | 120 | int (NNTreeImpl::*compare)(QPDFObjectHandle& key, QPDFObjectHandle& arr, int item)); |
| 121 | int compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); | 121 | int compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); |
| 122 | int compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); | 122 | int compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); |
| 123 | + void warn(QPDFObjectHandle& node, std::string const& msg); | ||
| 124 | + void error(QPDFObjectHandle& node, std::string const& msg); | ||
| 123 | 125 | ||
| 124 | NNTreeDetails const& details; | 126 | NNTreeDetails const& details; |
| 125 | QPDF& qpdf; | 127 | QPDF& qpdf; |
| 126 | int split_threshold{32}; | 128 | int split_threshold{32}; |
| 127 | QPDFObjectHandle oh; | 129 | QPDFObjectHandle oh; |
| 128 | - bool auto_repair; | 130 | + bool auto_repair{true}; |
| 131 | + size_t error_count{0}; | ||
| 129 | }; | 132 | }; |
| 130 | 133 | ||
| 131 | #endif // NNTREE_HH | 134 | #endif // NNTREE_HH |
libqpdf/qpdf/QPDF_private.hh
| @@ -581,4 +581,10 @@ class QPDF::ResolveRecorder | @@ -581,4 +581,10 @@ class QPDF::ResolveRecorder | ||
| 581 | std::set<QPDFObjGen>::const_iterator iter; | 581 | std::set<QPDFObjGen>::const_iterator iter; |
| 582 | }; | 582 | }; |
| 583 | 583 | ||
| 584 | +inline bool | ||
| 585 | +QPDF::reconstructed_xref() const | ||
| 586 | +{ | ||
| 587 | + return m->reconstructed_xref; | ||
| 588 | +} | ||
| 589 | + | ||
| 584 | #endif // QPDF_PRIVATE_HH | 590 | #endif // QPDF_PRIVATE_HH |