Commit ef14676bfb0b4674facc305e1df8ac00af84f5b2

Authored by m-holger
Committed by GitHub
2 parents f4e29052 023a0027

Merge pull request #1529 from m-holger/nnt

Add stricter name/number tree validation
include/qpdf/QPDFEmbeddedFileDocumentHelper.hh
... ... @@ -67,19 +67,7 @@ class QPDFEmbeddedFileDocumentHelper: public QPDFDocumentHelper
67 67 private:
68 68 void initEmbeddedFiles();
69 69  
70   - class Members
71   - {
72   - friend class QPDFEmbeddedFileDocumentHelper;
73   -
74   - public:
75   - ~Members() = default;
76   -
77   - private:
78   - Members() = default;
79   - Members(Members const&) = delete;
80   -
81   - std::shared_ptr<QPDFNameTreeObjectHelper> embedded_files;
82   - };
  70 + class Members;
83 71  
84 72 std::shared_ptr<Members> m;
85 73 };
... ...
include/qpdf/QPDFNameTreeObjectHelper.hh
... ... @@ -45,6 +45,19 @@ class QPDF_DLL_CLASS QPDFNameTreeObjectHelper: public QPDFObjectHelper
45 45 QPDF_DLL
46 46 QPDFNameTreeObjectHelper(QPDFObjectHandle, QPDF&, bool auto_repair = true);
47 47  
  48 + QPDF_DLL
  49 + QPDFNameTreeObjectHelper(
  50 + QPDFObjectHandle,
  51 + QPDF&,
  52 + std::function<bool(QPDFObjectHandle const&)> value_validator,
  53 + bool auto_repair);
  54 +
  55 + // Validate the name tree. Returns true if the tree is valid.
  56 + //
  57 + // If the tree is not valid and auto_repair is true, attempt to repair the tree.
  58 + QPDF_DLL
  59 + bool validate(bool repair = true);
  60 +
48 61 // Create an empty name tree
49 62 QPDF_DLL
50 63 static QPDFNameTreeObjectHelper newEmpty(QPDF&, bool auto_repair = true);
... ... @@ -171,7 +184,11 @@ class QPDF_DLL_CLASS QPDFNameTreeObjectHelper: public QPDFObjectHelper
171 184 ~Members() = default;
172 185  
173 186 private:
174   - Members(QPDFObjectHandle& oh, QPDF&, bool auto_repair);
  187 + Members(
  188 + QPDFObjectHandle& oh,
  189 + QPDF&,
  190 + std::function<bool(QPDFObjectHandle const&)> value_validator,
  191 + bool auto_repair);
175 192 Members(Members const&) = delete;
176 193  
177 194 std::shared_ptr<NNTreeImpl> impl;
... ...
include/qpdf/QPDFNumberTreeObjectHelper.hh
... ... @@ -44,6 +44,13 @@ class QPDF_DLL_CLASS QPDFNumberTreeObjectHelper: public QPDFObjectHelper
44 44 QPDFNumberTreeObjectHelper(QPDFObjectHandle, QPDF&, bool auto_repair = true);
45 45  
46 46 QPDF_DLL
  47 + QPDFNumberTreeObjectHelper(
  48 + QPDFObjectHandle,
  49 + QPDF&,
  50 + std::function<bool(QPDFObjectHandle const&)> value_validator,
  51 + bool auto_repair);
  52 +
  53 + QPDF_DLL
47 54 ~QPDFNumberTreeObjectHelper() override;
48 55  
49 56 // Create an empty number tree
... ... @@ -52,6 +59,12 @@ class QPDF_DLL_CLASS QPDFNumberTreeObjectHelper: public QPDFObjectHelper
52 59  
53 60 typedef long long int numtree_number;
54 61  
  62 + // Validate the name tree. Returns true if the tree is valid.
  63 + //
  64 + // If the tree is not valid and auto_repair is true, attempt to repair the tree.
  65 + QPDF_DLL
  66 + bool validate(bool repair = true);
  67 +
55 68 // Return overall minimum and maximum indices
56 69 QPDF_DLL
57 70 numtree_number getMin();
... ... @@ -188,7 +201,11 @@ class QPDF_DLL_CLASS QPDFNumberTreeObjectHelper: public QPDFObjectHelper
188 201 ~Members() = default;
189 202  
190 203 private:
191   - Members(QPDFObjectHandle& oh, QPDF&, bool auto_repair);
  204 + Members(
  205 + QPDFObjectHandle& oh,
  206 + QPDF&,
  207 + std::function<bool(QPDFObjectHandle const&)> value_validator,
  208 + bool auto_repair);
192 209 Members(Members const&) = delete;
193 210  
194 211 std::shared_ptr<NNTreeImpl> impl;
... ...
include/qpdf/QPDFOutlineDocumentHelper.hh
... ... @@ -63,33 +63,13 @@ class QPDFOutlineDocumentHelper: public QPDFDocumentHelper
63 63 {
64 64 friend class QPDFOutlineObjectHelper;
65 65  
66   - static bool
67   - checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen og)
68   - {
69   - return !dh.m->seen.add(og);
70   - }
  66 + static bool checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen og);
71 67 };
72 68  
73 69 private:
74 70 void initializeByPage();
75 71  
76   - class Members
77   - {
78   - friend class QPDFOutlineDocumentHelper;
79   -
80   - public:
81   - ~Members() = default;
82   -
83   - private:
84   - Members() = default;
85   - Members(Members const&) = delete;
86   -
87   - std::vector<QPDFOutlineObjectHelper> outlines;
88   - QPDFObjGen::set seen;
89   - QPDFObjectHandle dest_dict;
90   - std::shared_ptr<QPDFNameTreeObjectHelper> names_dest;
91   - std::map<QPDFObjGen, std::vector<QPDFOutlineObjectHelper>> by_page;
92   - };
  72 + class Members;
93 73  
94 74 std::shared_ptr<Members> m;
95 75 };
... ...
include/qpdf/QPDFPageLabelDocumentHelper.hh
... ... @@ -77,19 +77,7 @@ class QPDFPageLabelDocumentHelper: public QPDFDocumentHelper
77 77 std::vector<QPDFObjectHandle>& new_labels);
78 78  
79 79 private:
80   - class Members
81   - {
82   - friend class QPDFPageLabelDocumentHelper;
83   -
84   - public:
85   - ~Members() = default;
86   -
87   - private:
88   - Members() = default;
89   - Members(Members const&) = delete;
90   -
91   - std::shared_ptr<QPDFNumberTreeObjectHelper> labels;
92   - };
  80 + class Members;
93 81  
94 82 std::shared_ptr<Members> m;
95 83 };
... ...
libqpdf/NNTree.cc
... ... @@ -141,8 +141,8 @@ NNTreeIterator::increment(bool backward)
141 141 impl.warn(node, "items array doesn't have enough elements");
142 142 } else if (!impl.details.keyValid(items[item_number])) {
143 143 impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type"));
144   - } else if (!items[item_number + 1]) {
145   - impl.warn(node, "item " + std::to_string(item_number) + " is null");
  144 + } else if (!impl.value_valid(items[item_number + 1])) {
  145 + impl.warn(node, "item " + std::to_string(item_number + 1) + " is invalid");
146 146 } else {
147 147 return;
148 148 }
... ... @@ -386,6 +386,9 @@ NNTreeIterator::insertAfter(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp;
386 386 if (!(key && value)) {
387 387 impl.error(node, "insert: key or value is null");
388 388 }
  389 + if (!impl.value_valid(value)) {
  390 + impl.error(node, "insert: value is invalid");
  391 + }
389 392 items.insert(item_number + 2, key);
390 393 items.insert(item_number + 3, value);
391 394 resetLimits(node, lastPathElement());
... ... @@ -621,10 +624,15 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
621 624 }
622 625  
623 626 NNTreeImpl::NNTreeImpl(
624   - NNTreeDetails const& details, QPDF& qpdf, QPDFObjectHandle& oh, bool auto_repair) :
  627 + NNTreeDetails const& details,
  628 + QPDF& qpdf,
  629 + QPDFObjectHandle& oh,
  630 + std::function<bool(QPDFObjectHandle const&)> value_validator,
  631 + bool auto_repair) :
625 632 details(details),
626 633 qpdf(qpdf),
627 634 oh(oh),
  635 + value_valid(value_validator),
628 636 auto_repair(auto_repair)
629 637 {
630 638 }
... ... @@ -740,7 +748,7 @@ NNTreeImpl::repair()
740 748 {
741 749 auto new_node = QPDFObjectHandle::newDictionary();
742 750 new_node.replaceKey(details.itemsKey(), Array());
743   - NNTreeImpl repl(details, qpdf, new_node, false);
  751 + NNTreeImpl repl(details, qpdf, new_node, value_valid, false);
744 752 for (auto const& [key, value]: *this) {
745 753 if (key && value) {
746 754 repl.insert(key, value);
... ... @@ -774,10 +782,17 @@ NNTreeImpl::findInternal(QPDFObjectHandle const&amp; key, bool return_prev_if_not_fo
774 782 if (first_item == end()) {
775 783 return end();
776 784 }
777   - if (first_item.valid() && details.keyValid(first_item->first) &&
778   - details.compareKeys(key, first_item->first) < 0) {
779   - // Before the first key
780   - return end();
  785 + if (first_item.valid()) {
  786 + if (!details.keyValid(first_item->first)) {
  787 + error(oh, "encountered invalid key in find");
  788 + }
  789 + if (!value_valid(first_item->second)) {
  790 + error(oh, "encountered invalid value in find");
  791 + }
  792 + if (details.compareKeys(key, first_item->first) < 0) {
  793 + // Before the first key
  794 + return end();
  795 + }
781 796 }
782 797 qpdf_assert_debug(!last_item.valid());
783 798  
... ... @@ -797,6 +812,12 @@ NNTreeImpl::findInternal(QPDFObjectHandle const&amp; key, bool return_prev_if_not_fo
797 812 key, items, nitems / 2, return_prev_if_not_found, &NNTreeImpl::compareKeyItem);
798 813 if (idx >= 0) {
799 814 result.setItemNumber(node, 2 * idx);
  815 + if (!result.impl.details.keyValid(result.ivalue.first)) {
  816 + error(node, "encountered invalid key in find");
  817 + }
  818 + if (!result.impl.value_valid(result.ivalue.second)) {
  819 + error(oh, "encountered invalid value in find");
  820 + }
800 821 }
801 822 return result;
802 823 }
... ... @@ -830,6 +851,9 @@ NNTreeImpl::insertFirst(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp; val
830 851 if (!(key && value)) {
831 852 error(oh, "unable to insert null key or value");
832 853 }
  854 + if (!value_valid(value)) {
  855 + error(oh, "attempting to insert an invalid value");
  856 + }
833 857 items.insert(0, key);
834 858 items.insert(1, value);
835 859 iter.setItemNumber(iter.node, 0);
... ... @@ -867,3 +891,33 @@ NNTreeImpl::remove(QPDFObjectHandle const&amp; key, QPDFObjectHandle* value)
867 891 iter.remove();
868 892 return true;
869 893 }
  894 +
  895 +bool
  896 +NNTreeImpl::validate(bool a_repair)
  897 +{
  898 + bool first = true;
  899 + QPDFObjectHandle last_key;
  900 + try {
  901 + for (auto const& [key, value]: *this) {
  902 + if (!details.keyValid(key)) {
  903 + error(oh, "invalid key in validate");
  904 + }
  905 + if (!value_valid(value)) {
  906 + error(oh, "invalid value in validate");
  907 + }
  908 + if (first) {
  909 + first = false;
  910 + } else if (last_key && details.compareKeys(last_key, key) != -1) {
  911 + error(oh, "keys are not sorted in validate");
  912 + }
  913 + last_key = key;
  914 + }
  915 + } catch (QPDFExc& e) {
  916 + if (a_repair) {
  917 + warn(oh, std::string("attempting to repair after error: ") + e.what());
  918 + repair();
  919 + }
  920 + return false;
  921 + }
  922 + return true;
  923 +}
... ...
libqpdf/QPDFEmbeddedFileDocumentHelper.cc
... ... @@ -30,16 +30,30 @@
30 30 // >>
31 31 // >>
32 32  
  33 +class QPDFEmbeddedFileDocumentHelper::Members
  34 +{
  35 + public:
  36 + Members() = default;
  37 + Members(Members const&) = delete;
  38 + ~Members() = default;
  39 +
  40 + std::unique_ptr<QPDFNameTreeObjectHelper> embedded_files;
  41 +};
  42 +
33 43 QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF& qpdf) :
34 44 QPDFDocumentHelper(qpdf),
35   - m(new Members())
  45 + m(std::make_shared<Members>())
36 46 {
37   - auto root = qpdf.getRoot();
38   - auto names = root.getKey("/Names");
  47 + auto names = qpdf.getRoot().getKey("/Names");
39 48 if (names.isDictionary()) {
40 49 auto embedded_files = names.getKey("/EmbeddedFiles");
41 50 if (embedded_files.isDictionary()) {
42   - m->embedded_files = std::make_shared<QPDFNameTreeObjectHelper>(embedded_files, qpdf);
  51 + m->embedded_files = std::make_unique<QPDFNameTreeObjectHelper>(
  52 + embedded_files,
  53 + qpdf,
  54 + [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); },
  55 + true);
  56 + m->embedded_files->validate();
43 57 }
44 58 }
45 59 }
... ... @@ -65,7 +79,8 @@ QPDFEmbeddedFileDocumentHelper::initEmbeddedFiles()
65 79 if (!embedded_files.isDictionary()) {
66 80 auto nth = QPDFNameTreeObjectHelper::newEmpty(qpdf);
67 81 names.replaceKey("/EmbeddedFiles", nth.getObjectHandle());
68   - m->embedded_files = std::make_shared<QPDFNameTreeObjectHelper>(nth);
  82 + m->embedded_files = std::make_unique<QPDFNameTreeObjectHelper>(
  83 + nth, qpdf, [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, true);
69 84 }
70 85 }
71 86  
... ...
libqpdf/QPDFNameTreeObjectHelper.cc
... ... @@ -40,14 +40,29 @@ QPDFNameTreeObjectHelper::~QPDFNameTreeObjectHelper() // NOLINT (modernize-use-e
40 40 // class, see github issue #745.
41 41 }
42 42  
43   -QPDFNameTreeObjectHelper::Members::Members(QPDFObjectHandle& oh, QPDF& q, bool auto_repair) :
44   - impl(std::make_shared<NNTreeImpl>(name_tree_details, q, oh, auto_repair))
  43 +QPDFNameTreeObjectHelper::Members::Members(
  44 + QPDFObjectHandle& oh,
  45 + QPDF& q,
  46 + std::function<bool(QPDFObjectHandle const&)> value_validator,
  47 + bool auto_repair) :
  48 + impl(std::make_shared<NNTreeImpl>(name_tree_details, q, oh, value_validator, auto_repair))
45 49 {
46 50 }
47 51  
48 52 QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper(QPDFObjectHandle oh, QPDF& q, bool auto_repair) :
49 53 QPDFObjectHelper(oh),
50   - m(new Members(oh, q, auto_repair))
  54 + m(new Members(
  55 + oh, q, [](QPDFObjectHandle const& o) -> bool { return static_cast<bool>(o); }, auto_repair))
  56 +{
  57 +}
  58 +
  59 +QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper(
  60 + QPDFObjectHandle oh,
  61 + QPDF& q,
  62 + std::function<bool(QPDFObjectHandle const&)> value_validator,
  63 + bool auto_repair) :
  64 + QPDFObjectHelper(oh),
  65 + m(new Members(oh, q, value_validator, auto_repair))
51 66 {
52 67 }
53 68  
... ... @@ -200,3 +215,9 @@ QPDFNameTreeObjectHelper::getAsMap() const
200 215 result.insert(begin(), end());
201 216 return result;
202 217 }
  218 +
  219 +bool
  220 +QPDFNameTreeObjectHelper::validate(bool repair)
  221 +{
  222 + return m->impl->validate(repair);
  223 +}
... ...
libqpdf/QPDFNumberTreeObjectHelper.cc
... ... @@ -41,15 +41,30 @@ QPDFNumberTreeObjectHelper::~QPDFNumberTreeObjectHelper() // NOLINT (modernize-u
41 41 // class, see github issue #745.
42 42 }
43 43  
44   -QPDFNumberTreeObjectHelper::Members::Members(QPDFObjectHandle& oh, QPDF& q, bool auto_repair) :
45   - impl(std::make_shared<NNTreeImpl>(number_tree_details, q, oh, auto_repair))
  44 +QPDFNumberTreeObjectHelper::Members::Members(
  45 + QPDFObjectHandle& oh,
  46 + QPDF& q,
  47 + std::function<bool(QPDFObjectHandle const&)> value_validator,
  48 + bool auto_repair) :
  49 + impl(std::make_shared<NNTreeImpl>(number_tree_details, q, oh, value_validator, auto_repair))
46 50 {
47 51 }
48 52  
49 53 QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper(
50 54 QPDFObjectHandle oh, QPDF& q, bool auto_repair) :
51 55 QPDFObjectHelper(oh),
52   - m(new Members(oh, q, auto_repair))
  56 + m(new Members(
  57 + oh, q, [](QPDFObjectHandle const& o) -> bool { return static_cast<bool>(o); }, auto_repair))
  58 +{
  59 +}
  60 +
  61 +QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper(
  62 + QPDFObjectHandle oh,
  63 + QPDF& q,
  64 + std::function<bool(QPDFObjectHandle const&)> value_validator,
  65 + bool auto_repair) :
  66 + QPDFObjectHelper(oh),
  67 + m(new Members(oh, q, value_validator, auto_repair))
53 68 {
54 69 }
55 70  
... ... @@ -236,3 +251,9 @@ QPDFNumberTreeObjectHelper::getAsMap() const
236 251 result.insert(begin(), end());
237 252 return result;
238 253 }
  254 +
  255 +bool
  256 +QPDFNumberTreeObjectHelper::validate(bool repair)
  257 +{
  258 + return m->impl->validate(repair);
  259 +}
... ...
libqpdf/QPDFOutlineDocumentHelper.cc
... ... @@ -2,22 +2,42 @@
2 2  
3 3 #include <qpdf/QTC.hh>
4 4  
  5 +class QPDFOutlineDocumentHelper::Members
  6 +{
  7 + public:
  8 + Members() = default;
  9 + Members(Members const&) = delete;
  10 + ~Members() = default;
  11 +
  12 + std::vector<QPDFOutlineObjectHelper> outlines;
  13 + QPDFObjGen::set seen;
  14 + QPDFObjectHandle dest_dict;
  15 + std::unique_ptr<QPDFNameTreeObjectHelper> names_dest;
  16 + std::map<QPDFObjGen, std::vector<QPDFOutlineObjectHelper>> by_page;
  17 +};
  18 +
  19 +bool
  20 +QPDFOutlineDocumentHelper::Accessor::checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen og)
  21 +{
  22 + return !dh.m->seen.add(og);
  23 +}
  24 +
5 25 QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) :
6 26 QPDFDocumentHelper(qpdf),
7   - m(new Members())
  27 + m(std::make_shared<Members>())
8 28 {
9 29 QPDFObjectHandle root = qpdf.getRoot();
10 30 if (!root.hasKey("/Outlines")) {
11 31 return;
12 32 }
13   - QPDFObjectHandle outlines = root.getKey("/Outlines");
  33 + auto outlines = root.getKey("/Outlines");
14 34 if (!(outlines.isDictionary() && outlines.hasKey("/First"))) {
15 35 return;
16 36 }
17 37 QPDFObjectHandle cur = outlines.getKey("/First");
18 38 QPDFObjGen::set seen;
19 39 while (!cur.isNull() && seen.add(cur)) {
20   - m->outlines.push_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1));
  40 + m->outlines.emplace_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1));
21 41 cur = cur.getKey("/Next");
22 42 }
23 43 }
... ... @@ -55,11 +75,10 @@ QPDFOutlineDocumentHelper::getOutlinesForPage(QPDFObjGen og)
55 75 if (m->by_page.empty()) {
56 76 initializeByPage();
57 77 }
58   - std::vector<QPDFOutlineObjectHelper> result;
59 78 if (m->by_page.contains(og)) {
60   - result = m->by_page[og];
  79 + return m->by_page[og];
61 80 }
62   - return result;
  81 + return {};
63 82 }
64 83  
65 84 QPDFObjectHandle
... ... @@ -70,13 +89,19 @@ QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name)
70 89 if (!m->dest_dict) {
71 90 m->dest_dict = qpdf.getRoot().getKey("/Dests");
72 91 }
73   - QTC::TC("qpdf", "QPDFOutlineDocumentHelper name named dest");
74 92 result = m->dest_dict.getKeyIfDict(name.getName());
75 93 } else if (name.isString()) {
76 94 if (!m->names_dest) {
77 95 auto dests = qpdf.getRoot().getKey("/Names").getKeyIfDict("/Dests");
78 96 if (dests.isDictionary()) {
79   - m->names_dest = std::make_shared<QPDFNameTreeObjectHelper>(dests, qpdf);
  97 + m->names_dest = std::make_unique<QPDFNameTreeObjectHelper>(
  98 + dests,
  99 + qpdf,
  100 + [](QPDFObjectHandle const& o) -> bool {
  101 + return o.isArray() || o.isDictionary();
  102 + },
  103 + true);
  104 + m->names_dest->validate();
80 105 }
81 106 }
82 107 if (m->names_dest) {
... ... @@ -89,7 +114,6 @@ QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name)
89 114 return QPDFObjectHandle::newNull();
90 115 }
91 116 if (result.isDictionary()) {
92   - QTC::TC("qpdf", "QPDFOutlineDocumentHelper named dest dictionary");
93 117 return result.getKey("/D");
94 118 }
95 119 return result;
... ...
libqpdf/QPDFPageLabelDocumentHelper.cc
... ... @@ -2,37 +2,47 @@
2 2  
3 3 #include <qpdf/QTC.hh>
4 4  
  5 +class QPDFPageLabelDocumentHelper::Members
  6 +{
  7 + public:
  8 + Members() = default;
  9 + Members(Members const&) = delete;
  10 + ~Members() = default;
  11 +
  12 + std::unique_ptr<QPDFNumberTreeObjectHelper> labels;
  13 +};
  14 +
5 15 QPDFPageLabelDocumentHelper::QPDFPageLabelDocumentHelper(QPDF& qpdf) :
6 16 QPDFDocumentHelper(qpdf),
7   - m(new Members())
  17 + m(std::make_shared<Members>())
8 18 {
9 19 QPDFObjectHandle root = qpdf.getRoot();
10 20 if (root.hasKey("/PageLabels")) {
11   - m->labels =
12   - std::make_shared<QPDFNumberTreeObjectHelper>(root.getKey("/PageLabels"), this->qpdf);
  21 + m->labels = std::make_unique<QPDFNumberTreeObjectHelper>(
  22 + root.getKey("/PageLabels"),
  23 + this->qpdf,
  24 + [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); },
  25 + true);
  26 + m->labels->validate();
13 27 }
14 28 }
15 29  
16 30 bool
17 31 QPDFPageLabelDocumentHelper::hasPageLabels()
18 32 {
19   - return nullptr != m->labels;
  33 + return m->labels != nullptr;
20 34 }
21 35  
22 36 QPDFObjectHandle
23 37 QPDFPageLabelDocumentHelper::getLabelForPage(long long page_idx)
24 38 {
25   - QPDFObjectHandle result(QPDFObjectHandle::newNull());
26 39 if (!hasPageLabels()) {
27   - return result;
  40 + return QPDFObjectHandle::newNull();
28 41 }
29 42 QPDFNumberTreeObjectHelper::numtree_number offset = 0;
30 43 QPDFObjectHandle label;
31 44 if (!m->labels->findObjectAtOrBelow(page_idx, label, offset)) {
32   - return result;
33   - }
34   - if (!label.isDictionary()) {
35   - return result;
  45 + return QPDFObjectHandle::newNull();
36 46 }
37 47 QPDFObjectHandle S = label.getKey("/S"); // type (D, R, r, A, a)
38 48 QPDFObjectHandle P = label.getKey("/P"); // prefix
... ... @@ -43,7 +53,7 @@ QPDFPageLabelDocumentHelper::getLabelForPage(long long page_idx)
43 53 }
44 54 QIntC::range_check(start, offset);
45 55 start += offset;
46   - result = QPDFObjectHandle::newDictionary();
  56 + auto result = QPDFObjectHandle::newDictionary();
47 57 result.replaceKey("/S", S);
48 58 result.replaceKey("/P", P);
49 59 result.replaceKey("/St", QPDFObjectHandle::newInteger(start));
... ... @@ -81,21 +91,20 @@ QPDFPageLabelDocumentHelper::getLabelsForPageRange(
81 91 label.getKey("/St").getIntValue() - last.getKey("/St").getIntValue();
82 92 long long int idx_delta = new_start_idx - last_idx.getIntValue();
83 93 if (st_delta == idx_delta) {
84   - QTC::TC("qpdf", "QPDFPageLabelDocumentHelper skip first");
85 94 skip_first = true;
86 95 }
87 96 }
88 97 }
89 98 if (!skip_first) {
90   - new_labels.push_back(QPDFObjectHandle::newInteger(new_start_idx));
91   - new_labels.push_back(label);
  99 + new_labels.emplace_back(QPDFObjectHandle::newInteger(new_start_idx));
  100 + new_labels.emplace_back(label);
92 101 }
93 102  
94 103 long long int idx_offset = new_start_idx - start_idx;
95 104 for (long long i = start_idx + 1; i <= end_idx; ++i) {
96 105 if (m->labels->hasIndex(i) && (label = getLabelForPage(i)).isDictionary()) {
97   - new_labels.push_back(QPDFObjectHandle::newInteger(i + idx_offset));
98   - new_labels.push_back(label);
  106 + new_labels.emplace_back(QPDFObjectHandle::newInteger(i + idx_offset));
  107 + new_labels.emplace_back(label);
99 108 }
100 109 }
101 110 }
... ...
libqpdf/qpdf/NNTree.hh
... ... @@ -95,7 +95,12 @@ class NNTreeImpl
95 95 public:
96 96 typedef NNTreeIterator iterator;
97 97  
98   - NNTreeImpl(NNTreeDetails const&, QPDF&, QPDFObjectHandle&, bool auto_repair = true);
  98 + NNTreeImpl(
  99 + NNTreeDetails const&,
  100 + QPDF&,
  101 + QPDFObjectHandle&,
  102 + std::function<bool(QPDFObjectHandle const&)> value_validator,
  103 + bool auto_repair = true);
99 104 iterator begin();
100 105 iterator end();
101 106 iterator last();
... ... @@ -104,6 +109,8 @@ class NNTreeImpl
104 109 iterator insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value);
105 110 bool remove(QPDFObjectHandle const& key, QPDFObjectHandle* value = nullptr);
106 111  
  112 + bool validate(bool repair = true);
  113 +
107 114 // Change the split threshold for easier testing. There's no real reason to expose this to
108 115 // downstream tree helpers, but it has to be public so we can call it from the test suite.
109 116 void setSplitThreshold(int split_threshold);
... ... @@ -127,6 +134,7 @@ class NNTreeImpl
127 134 QPDF& qpdf;
128 135 int split_threshold{32};
129 136 QPDFObjectHandle oh;
  137 + const std::function<bool(QPDFObjectHandle const&)> value_valid;
130 138 bool auto_repair{true};
131 139 size_t error_count{0};
132 140 };
... ...
manual/release-notes.rst
... ... @@ -23,6 +23,16 @@ more detail.
23 23 not work on some older Linux distributions. If you need support
24 24 for an older distribution, please use version 12.2.0 or below.
25 25  
  26 + - Library Enhancements
  27 +
  28 + - Add ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper``
  29 + constructor overloads that allow a function to ne passed to
  30 + validate the values in the tree.
  31 +
  32 + - Add new ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper``
  33 + ``validate`` method to validate and optionally repair the name/number
  34 + tree.
  35 +
26 36 - CLI Enhancements
27 37  
28 38 - Disallow option :qpdf:ref:`--deterministic-id` to be used together
... ...
qpdf/qpdf.testcov
... ... @@ -338,13 +338,10 @@ QPDFAcroFormDocumentHelper annotation found 1
338 338 QPDFJob keep files open n 0
339 339 QPDFJob keep files open y 0
340 340 QPDFJob automatically set keep files open 1
341   -QPDFPageLabelDocumentHelper skip first 0
342 341 QPDFOutlineObjectHelper direct dest 0
343 342 QPDFOutlineObjectHelper action dest 0
344 343 QPDFOutlineObjectHelper named dest 0
345   -QPDFOutlineDocumentHelper name named dest 0
346 344 QPDFOutlineDocumentHelper string named dest 0
347   -QPDFOutlineDocumentHelper named dest dictionary 0
348 345 QPDFOutlineObjectHelper loop 0
349 346 QPDFObjectHandle merge top type mismatch 0
350 347 QPDFObjectHandle merge shallow copy 0
... ...
qpdf/qtest/page-labels.test
... ... @@ -14,7 +14,7 @@ cleanup();
14 14  
15 15 my $td = new TestDriver('page-labels');
16 16  
17   -my $n_tests = 3;
  17 +my $n_tests = 4;
18 18  
19 19 $td->runtest("complex page labels",
20 20 {$td->COMMAND => "test_driver 47 page-labels-num-tree.pdf"},
... ... @@ -29,6 +29,15 @@ $td-&gt;runtest(&quot;no page labels&quot;,
29 29 {$td->FILE => "no-page-labels.out", $td->EXIT_STATUS => 0},
30 30 $td->NORMALIZE_NEWLINES);
31 31  
  32 +# page-labels-num-tree-damaged.pdf has the following errors:
  33 +# - entry for page 29 preceedes entry for page 20
  34 +# - entry for page 3 follows entry for page 6
  35 +# - entry for page 12 is an integer rather than a dictionary
  36 +$td->runtest("damaged page labels",
  37 + {$td->COMMAND => "test_driver 47 page-labels-num-tree-damaged.pdf"},
  38 + {$td->FILE => "page-labels-num-tree-damaged.out", $td->EXIT_STATUS => 0},
  39 + $td->NORMALIZE_NEWLINES);
  40 +
32 41 # --set-page-labels
33 42 my @errors = (
34 43 ["quack", ".*page label spec must be.*"],
... ...
qpdf/qtest/qpdf/page-labels-num-tree-damaged.out 0 โ†’ 100644
  1 +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
  2 +WARNING: page-labels-num-tree-damaged.pdf (Name/Number tree node (object 37)): item 1 is invalid
  3 +1 << /S /r /St 1 >>
  4 +3 << /P (blank) /St 1 >>
  5 +4 << /P (X-) /S /A /St 17 >>
  6 +6 << /P () /St 1 >>
  7 +7 << /S /R /St 3 >>
  8 +10 << /S /D /St 1 >>
  9 +13 << /S /a /St 3 >>
  10 +16 << /P (q.) /S /D /St 6 >>
  11 +20 << /P (www) /St 1 >>
  12 +21 << /S /D /St 12 >>
  13 +23 << /S /D /St 16059 >>
  14 +24 << /S /R /St 50 >>
  15 +30 << /S /r /St 54 >>
  16 +test 47 done
... ...
qpdf/qtest/qpdf/page-labels-num-tree-damaged.pdf 0 โ†’ 100644
  1 +%PDF-1.3
  2 +%ยฟรทยขรพ
  3 +%QDF-1.0
  4 +
  5 +1 0 obj
  6 +<<
  7 + /PageLabels 2 0 R
  8 + /Pages 3 0 R
  9 + /Type /Catalog
  10 +>>
  11 +endobj
  12 +
  13 +2 0 obj
  14 +<<
  15 + /Kids [
  16 + 4 0 R
  17 + 5 0 R
  18 + ]
  19 +>>
  20 +endobj
  21 +
  22 +3 0 obj
  23 +<<
  24 + /Count 30
  25 + /Kids [
  26 + 6 0 R
  27 + 7 0 R
  28 + 8 0 R
  29 + 9 0 R
  30 + 10 0 R
  31 + 11 0 R
  32 + 12 0 R
  33 + 13 0 R
  34 + 14 0 R
  35 + 15 0 R
  36 + 16 0 R
  37 + 17 0 R
  38 + 18 0 R
  39 + 19 0 R
  40 + 20 0 R
  41 + 21 0 R
  42 + 22 0 R
  43 + 23 0 R
  44 + 24 0 R
  45 + 25 0 R
  46 + 26 0 R
  47 + 27 0 R
  48 + 28 0 R
  49 + 29 0 R
  50 + 30 0 R
  51 + 31 0 R
  52 + 32 0 R
  53 + 33 0 R
  54 + 34 0 R
  55 + 35 0 R
  56 + ]
  57 + /Type /Pages
  58 +>>
  59 +endobj
  60 +
  61 +4 0 obj
  62 +<<
  63 + /Kids [
  64 + 36 0 R
  65 + 37 0 R
  66 + ]
  67 + /Limits [
  68 + 0
  69 + 19
  70 + ]
  71 +>>
  72 +endobj
  73 +
  74 +5 0 obj
  75 +<<
  76 + /Limits [
  77 + 20
  78 + 29
  79 + ]
  80 + /Nums [
  81 + 29 << /S /r /St 54 >>
  82 + 20 << /S /D /St 12 >>
  83 + 22 << /S /D /St 16059 >>
  84 + 23 << /S /R /St 50 >>
  85 + ]
  86 +>>
  87 +endobj
  88 +
  89 +%% Page 1
  90 +6 0 obj
  91 +<<
  92 + /Contents 38 0 R
  93 + /MediaBox [
  94 + 0
  95 + 0
  96 + 612
  97 + 792
  98 + ]
  99 + /Parent 3 0 R
  100 + /Resources <<
  101 + /Font <<
  102 + /F1 40 0 R
  103 + >>
  104 + /ProcSet 41 0 R
  105 + >>
  106 + /Type /Page
  107 +>>
  108 +endobj
  109 +
  110 +%% Page 2
  111 +7 0 obj
  112 +<<
  113 + /Contents 42 0 R
  114 + /MediaBox [
  115 + 0
  116 + 0
  117 + 612
  118 + 792
  119 + ]
  120 + /Parent 3 0 R
  121 + /Resources <<
  122 + /Font <<
  123 + /F1 40 0 R
  124 + >>
  125 + /ProcSet 41 0 R
  126 + >>
  127 + /Type /Page
  128 +>>
  129 +endobj
  130 +
  131 +%% Page 3
  132 +8 0 obj
  133 +<<
  134 + /Contents 44 0 R
  135 + /MediaBox [
  136 + 0
  137 + 0
  138 + 612
  139 + 792
  140 + ]
  141 + /Parent 3 0 R
  142 + /Resources <<
  143 + /Font <<
  144 + /F1 40 0 R
  145 + >>
  146 + /ProcSet 41 0 R
  147 + >>
  148 + /Type /Page
  149 +>>
  150 +endobj
  151 +
  152 +%% Page 4
  153 +9 0 obj
  154 +<<
  155 + /Contents 46 0 R
  156 + /MediaBox [
  157 + 0
  158 + 0
  159 + 612
  160 + 792
  161 + ]
  162 + /Parent 3 0 R
  163 + /Resources <<
  164 + /Font <<
  165 + /F1 40 0 R
  166 + >>
  167 + /ProcSet 41 0 R
  168 + >>
  169 + /Type /Page
  170 +>>
  171 +endobj
  172 +
  173 +%% Page 5
  174 +10 0 obj
  175 +<<
  176 + /Contents 48 0 R
  177 + /MediaBox [
  178 + 0
  179 + 0
  180 + 612
  181 + 792
  182 + ]
  183 + /Parent 3 0 R
  184 + /Resources <<
  185 + /Font <<
  186 + /F1 40 0 R
  187 + >>
  188 + /ProcSet 41 0 R
  189 + >>
  190 + /Type /Page
  191 +>>
  192 +endobj
  193 +
  194 +%% Page 6
  195 +11 0 obj
  196 +<<
  197 + /Contents 50 0 R
  198 + /MediaBox [
  199 + 0
  200 + 0
  201 + 612
  202 + 792
  203 + ]
  204 + /Parent 3 0 R
  205 + /Resources <<
  206 + /Font <<
  207 + /F1 40 0 R
  208 + >>
  209 + /ProcSet 41 0 R
  210 + >>
  211 + /Type /Page
  212 +>>
  213 +endobj
  214 +
  215 +%% Page 7
  216 +12 0 obj
  217 +<<
  218 + /Contents 52 0 R
  219 + /MediaBox [
  220 + 0
  221 + 0
  222 + 612
  223 + 792
  224 + ]
  225 + /Parent 3 0 R
  226 + /Resources <<
  227 + /Font <<
  228 + /F1 40 0 R
  229 + >>
  230 + /ProcSet 41 0 R
  231 + >>
  232 + /Type /Page
  233 +>>
  234 +endobj
  235 +
  236 +%% Page 8
  237 +13 0 obj
  238 +<<
  239 + /Contents 54 0 R
  240 + /MediaBox [
  241 + 0
  242 + 0
  243 + 612
  244 + 792
  245 + ]
  246 + /Parent 3 0 R
  247 + /Resources <<
  248 + /Font <<
  249 + /F1 40 0 R
  250 + >>
  251 + /ProcSet 41 0 R
  252 + >>
  253 + /Type /Page
  254 +>>
  255 +endobj
  256 +
  257 +%% Page 9
  258 +14 0 obj
  259 +<<
  260 + /Contents 56 0 R
  261 + /MediaBox [
  262 + 0
  263 + 0
  264 + 612
  265 + 792
  266 + ]
  267 + /Parent 3 0 R
  268 + /Resources <<
  269 + /Font <<
  270 + /F1 40 0 R
  271 + >>
  272 + /ProcSet 41 0 R
  273 + >>
  274 + /Type /Page
  275 +>>
  276 +endobj
  277 +
  278 +%% Page 10
  279 +15 0 obj
  280 +<<
  281 + /Contents 58 0 R
  282 + /MediaBox [
  283 + 0
  284 + 0
  285 + 612
  286 + 792
  287 + ]
  288 + /Parent 3 0 R
  289 + /Resources <<
  290 + /Font <<
  291 + /F1 40 0 R
  292 + >>
  293 + /ProcSet 41 0 R
  294 + >>
  295 + /Type /Page
  296 +>>
  297 +endobj
  298 +
  299 +%% Page 11
  300 +16 0 obj
  301 +<<
  302 + /Contents 60 0 R
  303 + /MediaBox [
  304 + 0
  305 + 0
  306 + 612
  307 + 792
  308 + ]
  309 + /Parent 3 0 R
  310 + /Resources <<
  311 + /Font <<
  312 + /F1 40 0 R
  313 + >>
  314 + /ProcSet 41 0 R
  315 + >>
  316 + /Type /Page
  317 +>>
  318 +endobj
  319 +
  320 +%% Page 12
  321 +17 0 obj
  322 +<<
  323 + /Contents 62 0 R
  324 + /MediaBox [
  325 + 0
  326 + 0
  327 + 612
  328 + 792
  329 + ]
  330 + /Parent 3 0 R
  331 + /Resources <<
  332 + /Font <<
  333 + /F1 40 0 R
  334 + >>
  335 + /ProcSet 41 0 R
  336 + >>
  337 + /Type /Page
  338 +>>
  339 +endobj
  340 +
  341 +%% Page 13
  342 +18 0 obj
  343 +<<
  344 + /Contents 64 0 R
  345 + /MediaBox [
  346 + 0
  347 + 0
  348 + 612
  349 + 792
  350 + ]
  351 + /Parent 3 0 R
  352 + /Resources <<
  353 + /Font <<
  354 + /F1 40 0 R
  355 + >>
  356 + /ProcSet 41 0 R
  357 + >>
  358 + /Type /Page
  359 +>>
  360 +endobj
  361 +
  362 +%% Page 14
  363 +19 0 obj
  364 +<<
  365 + /Contents 66 0 R
  366 + /MediaBox [
  367 + 0
  368 + 0
  369 + 612
  370 + 792
  371 + ]
  372 + /Parent 3 0 R
  373 + /Resources <<
  374 + /Font <<
  375 + /F1 40 0 R
  376 + >>
  377 + /ProcSet 41 0 R
  378 + >>
  379 + /Type /Page
  380 +>>
  381 +endobj
  382 +
  383 +%% Page 15
  384 +20 0 obj
  385 +<<
  386 + /Contents 68 0 R
  387 + /MediaBox [
  388 + 0
  389 + 0
  390 + 612
  391 + 792
  392 + ]
  393 + /Parent 3 0 R
  394 + /Resources <<
  395 + /Font <<
  396 + /F1 40 0 R
  397 + >>
  398 + /ProcSet 41 0 R
  399 + >>
  400 + /Type /Page
  401 +>>
  402 +endobj
  403 +
  404 +%% Page 16
  405 +21 0 obj
  406 +<<
  407 + /Contents 70 0 R
  408 + /MediaBox [
  409 + 0
  410 + 0
  411 + 612
  412 + 792
  413 + ]
  414 + /Parent 3 0 R
  415 + /Resources <<
  416 + /Font <<
  417 + /F1 40 0 R
  418 + >>
  419 + /ProcSet 41 0 R
  420 + >>
  421 + /Type /Page
  422 +>>
  423 +endobj
  424 +
  425 +%% Page 17
  426 +22 0 obj
  427 +<<
  428 + /Contents 72 0 R
  429 + /MediaBox [
  430 + 0
  431 + 0
  432 + 612
  433 + 792
  434 + ]
  435 + /Parent 3 0 R
  436 + /Resources <<
  437 + /Font <<
  438 + /F1 40 0 R
  439 + >>
  440 + /ProcSet 41 0 R
  441 + >>
  442 + /Type /Page
  443 +>>
  444 +endobj
  445 +
  446 +%% Page 18
  447 +23 0 obj
  448 +<<
  449 + /Contents 74 0 R
  450 + /MediaBox [
  451 + 0
  452 + 0
  453 + 612
  454 + 792
  455 + ]
  456 + /Parent 3 0 R
  457 + /Resources <<
  458 + /Font <<
  459 + /F1 40 0 R
  460 + >>
  461 + /ProcSet 41 0 R
  462 + >>
  463 + /Type /Page
  464 +>>
  465 +endobj
  466 +
  467 +%% Page 19
  468 +24 0 obj
  469 +<<
  470 + /Contents 76 0 R
  471 + /MediaBox [
  472 + 0
  473 + 0
  474 + 612
  475 + 792
  476 + ]
  477 + /Parent 3 0 R
  478 + /Resources <<
  479 + /Font <<
  480 + /F1 40 0 R
  481 + >>
  482 + /ProcSet 41 0 R
  483 + >>
  484 + /Type /Page
  485 +>>
  486 +endobj
  487 +
  488 +%% Page 20
  489 +25 0 obj
  490 +<<
  491 + /Contents 78 0 R
  492 + /MediaBox [
  493 + 0
  494 + 0
  495 + 612
  496 + 792
  497 + ]
  498 + /Parent 3 0 R
  499 + /Resources <<
  500 + /Font <<
  501 + /F1 40 0 R
  502 + >>
  503 + /ProcSet 41 0 R
  504 + >>
  505 + /Type /Page
  506 +>>
  507 +endobj
  508 +
  509 +%% Page 21
  510 +26 0 obj
  511 +<<
  512 + /Contents 80 0 R
  513 + /MediaBox [
  514 + 0
  515 + 0
  516 + 612
  517 + 792
  518 + ]
  519 + /Parent 3 0 R
  520 + /Resources <<
  521 + /Font <<
  522 + /F1 40 0 R
  523 + >>
  524 + /ProcSet 41 0 R
  525 + >>
  526 + /Type /Page
  527 +>>
  528 +endobj
  529 +
  530 +%% Page 22
  531 +27 0 obj
  532 +<<
  533 + /Contents 82 0 R
  534 + /MediaBox [
  535 + 0
  536 + 0
  537 + 612
  538 + 792
  539 + ]
  540 + /Parent 3 0 R
  541 + /Resources <<
  542 + /Font <<
  543 + /F1 40 0 R
  544 + >>
  545 + /ProcSet 41 0 R
  546 + >>
  547 + /Type /Page
  548 +>>
  549 +endobj
  550 +
  551 +%% Page 23
  552 +28 0 obj
  553 +<<
  554 + /Contents 84 0 R
  555 + /MediaBox [
  556 + 0
  557 + 0
  558 + 612
  559 + 792
  560 + ]
  561 + /Parent 3 0 R
  562 + /Resources <<
  563 + /Font <<
  564 + /F1 40 0 R
  565 + >>
  566 + /ProcSet 41 0 R
  567 + >>
  568 + /Type /Page
  569 +>>
  570 +endobj
  571 +
  572 +%% Page 24
  573 +29 0 obj
  574 +<<
  575 + /Contents 86 0 R
  576 + /MediaBox [
  577 + 0
  578 + 0
  579 + 612
  580 + 792
  581 + ]
  582 + /Parent 3 0 R
  583 + /Resources <<
  584 + /Font <<
  585 + /F1 40 0 R
  586 + >>
  587 + /ProcSet 41 0 R
  588 + >>
  589 + /Type /Page
  590 +>>
  591 +endobj
  592 +
  593 +%% Page 25
  594 +30 0 obj
  595 +<<
  596 + /Contents 88 0 R
  597 + /MediaBox [
  598 + 0
  599 + 0
  600 + 612
  601 + 792
  602 + ]
  603 + /Parent 3 0 R
  604 + /Resources <<
  605 + /Font <<
  606 + /F1 40 0 R
  607 + >>
  608 + /ProcSet 41 0 R
  609 + >>
  610 + /Type /Page
  611 +>>
  612 +endobj
  613 +
  614 +%% Page 26
  615 +31 0 obj
  616 +<<
  617 + /Contents 90 0 R
  618 + /MediaBox [
  619 + 0
  620 + 0
  621 + 612
  622 + 792
  623 + ]
  624 + /Parent 3 0 R
  625 + /Resources <<
  626 + /Font <<
  627 + /F1 40 0 R
  628 + >>
  629 + /ProcSet 41 0 R
  630 + >>
  631 + /Type /Page
  632 +>>
  633 +endobj
  634 +
  635 +%% Page 27
  636 +32 0 obj
  637 +<<
  638 + /Contents 92 0 R
  639 + /MediaBox [
  640 + 0
  641 + 0
  642 + 612
  643 + 792
  644 + ]
  645 + /Parent 3 0 R
  646 + /Resources <<
  647 + /Font <<
  648 + /F1 40 0 R
  649 + >>
  650 + /ProcSet 41 0 R
  651 + >>
  652 + /Type /Page
  653 +>>
  654 +endobj
  655 +
  656 +%% Page 28
  657 +33 0 obj
  658 +<<
  659 + /Contents 94 0 R
  660 + /MediaBox [
  661 + 0
  662 + 0
  663 + 612
  664 + 792
  665 + ]
  666 + /Parent 3 0 R
  667 + /Resources <<
  668 + /Font <<
  669 + /F1 40 0 R
  670 + >>
  671 + /ProcSet 41 0 R
  672 + >>
  673 + /Type /Page
  674 +>>
  675 +endobj
  676 +
  677 +%% Page 29
  678 +34 0 obj
  679 +<<
  680 + /Contents 96 0 R
  681 + /MediaBox [
  682 + 0
  683 + 0
  684 + 612
  685 + 792
  686 + ]
  687 + /Parent 3 0 R
  688 + /Resources <<
  689 + /Font <<
  690 + /F1 40 0 R
  691 + >>
  692 + /ProcSet 41 0 R
  693 + >>
  694 + /Type /Page
  695 +>>
  696 +endobj
  697 +
  698 +%% Page 30
  699 +35 0 obj
  700 +<<
  701 + /Contents 98 0 R
  702 + /MediaBox [
  703 + 0
  704 + 0
  705 + 612
  706 + 792
  707 + ]
  708 + /Parent 3 0 R
  709 + /Resources <<
  710 + /Font <<
  711 + /F1 40 0 R
  712 + >>
  713 + /ProcSet 41 0 R
  714 + >>
  715 + /Type /Page
  716 +>>
  717 +endobj
  718 +
  719 +36 0 obj
  720 +<<
  721 + /Limits [
  722 + 0
  723 + 9
  724 + ]
  725 + /Nums [
  726 + 0 << /S /r >>
  727 + 2 << /P (blank) >>
  728 + 5 << /P () >>
  729 + 6 << /S /R /St 3 >>
  730 + 3 << /P (X-) /S /A /St 17 >>
  731 + 9 << /S /D >>
  732 + ]
  733 +>>
  734 +endobj
  735 +
  736 +37 0 obj
  737 +<<
  738 + /Limits [
  739 + 11
  740 + 19
  741 + ]
  742 + /Nums [
  743 + 11 42
  744 + 12 << /S /a /St 3 >>
  745 + 15 << /P (q.) /S /D /St 6 >>
  746 + 19 << /P (www) >>
  747 + ]
  748 +>>
  749 +endobj
  750 +
  751 +%% Contents for page 1
  752 +38 0 obj
  753 +<<
  754 + /Length 39 0 R
  755 +>>
  756 +stream
  757 +BT
  758 + /F1 24 Tf
  759 + 72 720 Td
  760 + (Potato 0) Tj
  761 +ET
  762 +endstream
  763 +endobj
  764 +
  765 +39 0 obj
  766 +46
  767 +endobj
  768 +
  769 +40 0 obj
  770 +<<
  771 + /BaseFont /Helvetica
  772 + /Encoding /WinAnsiEncoding
  773 + /Name /F1
  774 + /Subtype /Type1
  775 + /Type /Font
  776 +>>
  777 +endobj
  778 +
  779 +41 0 obj
  780 +[
  781 + /PDF
  782 + /Text
  783 +]
  784 +endobj
  785 +
  786 +%% Contents for page 2
  787 +42 0 obj
  788 +<<
  789 + /Length 43 0 R
  790 +>>
  791 +stream
  792 +BT
  793 + /F1 24 Tf
  794 + 72 720 Td
  795 + (Potato 1) Tj
  796 +ET
  797 +endstream
  798 +endobj
  799 +
  800 +43 0 obj
  801 +46
  802 +endobj
  803 +
  804 +%% Contents for page 3
  805 +44 0 obj
  806 +<<
  807 + /Length 45 0 R
  808 +>>
  809 +stream
  810 +BT
  811 + /F1 24 Tf
  812 + 72 720 Td
  813 + (Potato 2) Tj
  814 +ET
  815 +endstream
  816 +endobj
  817 +
  818 +45 0 obj
  819 +46
  820 +endobj
  821 +
  822 +%% Contents for page 4
  823 +46 0 obj
  824 +<<
  825 + /Length 47 0 R
  826 +>>
  827 +stream
  828 +BT
  829 + /F1 24 Tf
  830 + 72 720 Td
  831 + (Potato 3) Tj
  832 +ET
  833 +endstream
  834 +endobj
  835 +
  836 +47 0 obj
  837 +46
  838 +endobj
  839 +
  840 +%% Contents for page 5
  841 +48 0 obj
  842 +<<
  843 + /Length 49 0 R
  844 +>>
  845 +stream
  846 +BT
  847 + /F1 24 Tf
  848 + 72 720 Td
  849 + (Potato 4) Tj
  850 +ET
  851 +endstream
  852 +endobj
  853 +
  854 +49 0 obj
  855 +46
  856 +endobj
  857 +
  858 +%% Contents for page 6
  859 +50 0 obj
  860 +<<
  861 + /Length 51 0 R
  862 +>>
  863 +stream
  864 +BT
  865 + /F1 24 Tf
  866 + 72 720 Td
  867 + (Potato 5) Tj
  868 +ET
  869 +endstream
  870 +endobj
  871 +
  872 +51 0 obj
  873 +46
  874 +endobj
  875 +
  876 +%% Contents for page 7
  877 +52 0 obj
  878 +<<
  879 + /Length 53 0 R
  880 +>>
  881 +stream
  882 +BT
  883 + /F1 24 Tf
  884 + 72 720 Td
  885 + (Potato 6) Tj
  886 +ET
  887 +endstream
  888 +endobj
  889 +
  890 +53 0 obj
  891 +46
  892 +endobj
  893 +
  894 +%% Contents for page 8
  895 +54 0 obj
  896 +<<
  897 + /Length 55 0 R
  898 +>>
  899 +stream
  900 +BT
  901 + /F1 24 Tf
  902 + 72 720 Td
  903 + (Potato 7) Tj
  904 +ET
  905 +endstream
  906 +endobj
  907 +
  908 +55 0 obj
  909 +46
  910 +endobj
  911 +
  912 +%% Contents for page 9
  913 +56 0 obj
  914 +<<
  915 + /Length 57 0 R
  916 +>>
  917 +stream
  918 +BT
  919 + /F1 24 Tf
  920 + 72 720 Td
  921 + (Potato 8) Tj
  922 +ET
  923 +endstream
  924 +endobj
  925 +
  926 +57 0 obj
  927 +46
  928 +endobj
  929 +
  930 +%% Contents for page 10
  931 +58 0 obj
  932 +<<
  933 + /Length 59 0 R
  934 +>>
  935 +stream
  936 +BT
  937 + /F1 24 Tf
  938 + 72 720 Td
  939 + (Potato 9) Tj
  940 +ET
  941 +endstream
  942 +endobj
  943 +
  944 +59 0 obj
  945 +46
  946 +endobj
  947 +
  948 +%% Contents for page 11
  949 +60 0 obj
  950 +<<
  951 + /Length 61 0 R
  952 +>>
  953 +stream
  954 +BT
  955 + /F1 24 Tf
  956 + 72 720 Td
  957 + (Potato 10) Tj
  958 +ET
  959 +endstream
  960 +endobj
  961 +
  962 +61 0 obj
  963 +47
  964 +endobj
  965 +
  966 +%% Contents for page 12
  967 +62 0 obj
  968 +<<
  969 + /Length 63 0 R
  970 +>>
  971 +stream
  972 +BT
  973 + /F1 24 Tf
  974 + 72 720 Td
  975 + (Potato 11) Tj
  976 +ET
  977 +endstream
  978 +endobj
  979 +
  980 +63 0 obj
  981 +47
  982 +endobj
  983 +
  984 +%% Contents for page 13
  985 +64 0 obj
  986 +<<
  987 + /Length 65 0 R
  988 +>>
  989 +stream
  990 +BT
  991 + /F1 24 Tf
  992 + 72 720 Td
  993 + (Potato 12) Tj
  994 +ET
  995 +endstream
  996 +endobj
  997 +
  998 +65 0 obj
  999 +47
  1000 +endobj
  1001 +
  1002 +%% Contents for page 14
  1003 +66 0 obj
  1004 +<<
  1005 + /Length 67 0 R
  1006 +>>
  1007 +stream
  1008 +BT
  1009 + /F1 24 Tf
  1010 + 72 720 Td
  1011 + (Potato 13) Tj
  1012 +ET
  1013 +endstream
  1014 +endobj
  1015 +
  1016 +67 0 obj
  1017 +47
  1018 +endobj
  1019 +
  1020 +%% Contents for page 15
  1021 +68 0 obj
  1022 +<<
  1023 + /Length 69 0 R
  1024 +>>
  1025 +stream
  1026 +BT
  1027 + /F1 24 Tf
  1028 + 72 720 Td
  1029 + (Potato 14) Tj
  1030 +ET
  1031 +endstream
  1032 +endobj
  1033 +
  1034 +69 0 obj
  1035 +47
  1036 +endobj
  1037 +
  1038 +%% Contents for page 16
  1039 +70 0 obj
  1040 +<<
  1041 + /Length 71 0 R
  1042 +>>
  1043 +stream
  1044 +BT
  1045 + /F1 24 Tf
  1046 + 72 720 Td
  1047 + (Potato 15) Tj
  1048 +ET
  1049 +endstream
  1050 +endobj
  1051 +
  1052 +71 0 obj
  1053 +47
  1054 +endobj
  1055 +
  1056 +%% Contents for page 17
  1057 +72 0 obj
  1058 +<<
  1059 + /Length 73 0 R
  1060 +>>
  1061 +stream
  1062 +BT
  1063 + /F1 24 Tf
  1064 + 72 720 Td
  1065 + (Potato 16) Tj
  1066 +ET
  1067 +endstream
  1068 +endobj
  1069 +
  1070 +73 0 obj
  1071 +47
  1072 +endobj
  1073 +
  1074 +%% Contents for page 18
  1075 +74 0 obj
  1076 +<<
  1077 + /Length 75 0 R
  1078 +>>
  1079 +stream
  1080 +BT
  1081 + /F1 24 Tf
  1082 + 72 720 Td
  1083 + (Potato 17) Tj
  1084 +ET
  1085 +endstream
  1086 +endobj
  1087 +
  1088 +75 0 obj
  1089 +47
  1090 +endobj
  1091 +
  1092 +%% Contents for page 19
  1093 +76 0 obj
  1094 +<<
  1095 + /Length 77 0 R
  1096 +>>
  1097 +stream
  1098 +BT
  1099 + /F1 24 Tf
  1100 + 72 720 Td
  1101 + (Potato 18) Tj
  1102 +ET
  1103 +endstream
  1104 +endobj
  1105 +
  1106 +77 0 obj
  1107 +47
  1108 +endobj
  1109 +
  1110 +%% Contents for page 20
  1111 +78 0 obj
  1112 +<<
  1113 + /Length 79 0 R
  1114 +>>
  1115 +stream
  1116 +BT
  1117 + /F1 24 Tf
  1118 + 72 720 Td
  1119 + (Potato 19) Tj
  1120 +ET
  1121 +endstream
  1122 +endobj
  1123 +
  1124 +79 0 obj
  1125 +47
  1126 +endobj
  1127 +
  1128 +%% Contents for page 21
  1129 +80 0 obj
  1130 +<<
  1131 + /Length 81 0 R
  1132 +>>
  1133 +stream
  1134 +BT
  1135 + /F1 24 Tf
  1136 + 72 720 Td
  1137 + (Potato 20) Tj
  1138 +ET
  1139 +endstream
  1140 +endobj
  1141 +
  1142 +81 0 obj
  1143 +47
  1144 +endobj
  1145 +
  1146 +%% Contents for page 22
  1147 +82 0 obj
  1148 +<<
  1149 + /Length 83 0 R
  1150 +>>
  1151 +stream
  1152 +BT
  1153 + /F1 24 Tf
  1154 + 72 720 Td
  1155 + (Potato 21) Tj
  1156 +ET
  1157 +endstream
  1158 +endobj
  1159 +
  1160 +83 0 obj
  1161 +47
  1162 +endobj
  1163 +
  1164 +%% Contents for page 23
  1165 +84 0 obj
  1166 +<<
  1167 + /Length 85 0 R
  1168 +>>
  1169 +stream
  1170 +BT
  1171 + /F1 24 Tf
  1172 + 72 720 Td
  1173 + (Potato 22) Tj
  1174 +ET
  1175 +endstream
  1176 +endobj
  1177 +
  1178 +85 0 obj
  1179 +47
  1180 +endobj
  1181 +
  1182 +%% Contents for page 24
  1183 +86 0 obj
  1184 +<<
  1185 + /Length 87 0 R
  1186 +>>
  1187 +stream
  1188 +BT
  1189 + /F1 24 Tf
  1190 + 72 720 Td
  1191 + (Potato 23) Tj
  1192 +ET
  1193 +endstream
  1194 +endobj
  1195 +
  1196 +87 0 obj
  1197 +47
  1198 +endobj
  1199 +
  1200 +%% Contents for page 25
  1201 +88 0 obj
  1202 +<<
  1203 + /Length 89 0 R
  1204 +>>
  1205 +stream
  1206 +BT
  1207 + /F1 24 Tf
  1208 + 72 720 Td
  1209 + (Potato 24) Tj
  1210 +ET
  1211 +endstream
  1212 +endobj
  1213 +
  1214 +89 0 obj
  1215 +47
  1216 +endobj
  1217 +
  1218 +%% Contents for page 26
  1219 +90 0 obj
  1220 +<<
  1221 + /Length 91 0 R
  1222 +>>
  1223 +stream
  1224 +BT
  1225 + /F1 24 Tf
  1226 + 72 720 Td
  1227 + (Potato 25) Tj
  1228 +ET
  1229 +endstream
  1230 +endobj
  1231 +
  1232 +91 0 obj
  1233 +47
  1234 +endobj
  1235 +
  1236 +%% Contents for page 27
  1237 +92 0 obj
  1238 +<<
  1239 + /Length 93 0 R
  1240 +>>
  1241 +stream
  1242 +BT
  1243 + /F1 24 Tf
  1244 + 72 720 Td
  1245 + (Potato 26) Tj
  1246 +ET
  1247 +endstream
  1248 +endobj
  1249 +
  1250 +93 0 obj
  1251 +47
  1252 +endobj
  1253 +
  1254 +%% Contents for page 28
  1255 +94 0 obj
  1256 +<<
  1257 + /Length 95 0 R
  1258 +>>
  1259 +stream
  1260 +BT
  1261 + /F1 24 Tf
  1262 + 72 720 Td
  1263 + (Potato 27) Tj
  1264 +ET
  1265 +endstream
  1266 +endobj
  1267 +
  1268 +95 0 obj
  1269 +47
  1270 +endobj
  1271 +
  1272 +%% Contents for page 29
  1273 +96 0 obj
  1274 +<<
  1275 + /Length 97 0 R
  1276 +>>
  1277 +stream
  1278 +BT
  1279 + /F1 24 Tf
  1280 + 72 720 Td
  1281 + (Potato 28) Tj
  1282 +ET
  1283 +endstream
  1284 +endobj
  1285 +
  1286 +97 0 obj
  1287 +47
  1288 +endobj
  1289 +
  1290 +%% Contents for page 30
  1291 +98 0 obj
  1292 +<<
  1293 + /Length 99 0 R
  1294 +>>
  1295 +stream
  1296 +BT
  1297 + /F1 24 Tf
  1298 + 72 720 Td
  1299 + (Potato 29) Tj
  1300 +ET
  1301 +endstream
  1302 +endobj
  1303 +
  1304 +99 0 obj
  1305 +47
  1306 +endobj
  1307 +
  1308 +xref
  1309 +0 100
  1310 +0000000000 65535 f
  1311 +0000000025 00000 n
  1312 +0000000099 00000 n
  1313 +0000000155 00000 n
  1314 +0000000544 00000 n
  1315 +0000000631 00000 n
  1316 +0000000814 00000 n
  1317 +0000001019 00000 n
  1318 +0000001224 00000 n
  1319 +0000001429 00000 n
  1320 +0000001634 00000 n
  1321 +0000001840 00000 n
  1322 +0000002046 00000 n
  1323 +0000002252 00000 n
  1324 +0000002458 00000 n
  1325 +0000002665 00000 n
  1326 +0000002872 00000 n
  1327 +0000003079 00000 n
  1328 +0000003286 00000 n
  1329 +0000003493 00000 n
  1330 +0000003700 00000 n
  1331 +0000003907 00000 n
  1332 +0000004114 00000 n
  1333 +0000004321 00000 n
  1334 +0000004528 00000 n
  1335 +0000004735 00000 n
  1336 +0000004942 00000 n
  1337 +0000005149 00000 n
  1338 +0000005356 00000 n
  1339 +0000005563 00000 n
  1340 +0000005770 00000 n
  1341 +0000005977 00000 n
  1342 +0000006184 00000 n
  1343 +0000006391 00000 n
  1344 +0000006598 00000 n
  1345 +0000006805 00000 n
  1346 +0000007001 00000 n
  1347 +0000007200 00000 n
  1348 +0000007389 00000 n
  1349 +0000007492 00000 n
  1350 +0000007512 00000 n
  1351 +0000007631 00000 n
  1352 +0000007690 00000 n
  1353 +0000007793 00000 n
  1354 +0000007836 00000 n
  1355 +0000007939 00000 n
  1356 +0000007982 00000 n
  1357 +0000008085 00000 n
  1358 +0000008128 00000 n
  1359 +0000008231 00000 n
  1360 +0000008274 00000 n
  1361 +0000008377 00000 n
  1362 +0000008420 00000 n
  1363 +0000008523 00000 n
  1364 +0000008566 00000 n
  1365 +0000008669 00000 n
  1366 +0000008712 00000 n
  1367 +0000008815 00000 n
  1368 +0000008859 00000 n
  1369 +0000008962 00000 n
  1370 +0000009006 00000 n
  1371 +0000009110 00000 n
  1372 +0000009154 00000 n
  1373 +0000009258 00000 n
  1374 +0000009302 00000 n
  1375 +0000009406 00000 n
  1376 +0000009450 00000 n
  1377 +0000009554 00000 n
  1378 +0000009598 00000 n
  1379 +0000009702 00000 n
  1380 +0000009746 00000 n
  1381 +0000009850 00000 n
  1382 +0000009894 00000 n
  1383 +0000009998 00000 n
  1384 +0000010042 00000 n
  1385 +0000010146 00000 n
  1386 +0000010190 00000 n
  1387 +0000010294 00000 n
  1388 +0000010338 00000 n
  1389 +0000010442 00000 n
  1390 +0000010486 00000 n
  1391 +0000010590 00000 n
  1392 +0000010634 00000 n
  1393 +0000010738 00000 n
  1394 +0000010782 00000 n
  1395 +0000010886 00000 n
  1396 +0000010930 00000 n
  1397 +0000011034 00000 n
  1398 +0000011078 00000 n
  1399 +0000011182 00000 n
  1400 +0000011226 00000 n
  1401 +0000011330 00000 n
  1402 +0000011374 00000 n
  1403 +0000011478 00000 n
  1404 +0000011522 00000 n
  1405 +0000011626 00000 n
  1406 +0000011670 00000 n
  1407 +0000011774 00000 n
  1408 +0000011818 00000 n
  1409 +0000011922 00000 n
  1410 +trailer <<
  1411 + /Root 1 0 R
  1412 + /Size 100
  1413 + /ID [<90f919de7874f3bc5cb7afbd1e9537bb><0dfe18a94cde0f4bfdc86c03af19010e>]
  1414 +>>
  1415 +startxref
  1416 +11942
  1417 +%%EOF
... ...