Commit d61ffb65d034848157291b9825f4b33155bd55e7

Authored by Jay Berkenbilt
1 parent ba814703

Add new constructors for name/number tree helpers

Add constructors that take a QPDF object so we can issue warnings and
create new indirect objects.
ChangeLog
1 1 2021-01-16 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Add new constructors for QPDFNameTreeObjectHelper and
  4 + QPDFNumberTreeObjectHelper that take a QPDF object so they can
  5 + create indirect objects and issue warnings. The old constructors
  6 + are deprecated and will be removed in qpdf 11. Just pass in the
  7 + owning QPDF of the object handle used to initialize the helpers.
  8 +
3 9 * Re-implement QPDFNameTreeObjectHelper and
4 10 QPDFNumberTreeObjectHelper to be much more efficient and to have
5 11 an iterator-based API in addition to the existing one. This makes
... ...
... ... @@ -84,7 +84,12 @@ ABI Changes
84 84 This is a list of changes to make next time there is an ABI change.
85 85 Comments appear in the code prefixed by "ABI"
86 86  
  87 +* Search for ABI to find items not listed here.
87 88 * Merge two versions of QPDFObjectHandle::makeDirect per comment
  89 +* After removing legacy QPDFNameTreeObjectHelper and
  90 + QPDFNumberTreeObjectHelper constructors, NNTreeImpl can switch to
  91 + having a QPDF reference and assume that the reference is always
  92 + valid.
88 93  
89 94 Page splitting/merging
90 95 ======================
... ...
include/qpdf/QPDFNameTreeObjectHelper.hh
... ... @@ -42,6 +42,16 @@ class NNTreeDetails;
42 42 class QPDFNameTreeObjectHelper: public QPDFObjectHelper
43 43 {
44 44 public:
  45 + // The qpdf object is required so that this class can issue
  46 + // warnings, attempt repairs, and add indirect objects.
  47 + QPDF_DLL
  48 + QPDFNameTreeObjectHelper(QPDFObjectHandle, QPDF&,
  49 + bool auto_repair = true);
  50 +
  51 + // ABI: Legacy Constructor will be removed in QPDF 11. A
  52 + // QPDFNameTreeObjectHelper constructed in this way can't be
  53 + // modified or repaired and will silently ignore problems in the
  54 + // structure.
45 55 QPDF_DLL
46 56 QPDFNameTreeObjectHelper(QPDFObjectHandle);
47 57 QPDF_DLL
... ... @@ -133,7 +143,7 @@ class QPDFNameTreeObjectHelper: public QPDFObjectHelper
133 143 ~Members();
134 144  
135 145 private:
136   - Members(QPDFObjectHandle& oh);
  146 + Members(QPDFObjectHandle& oh, QPDF*, bool auto_repair);
137 147 Members(Members const&) = delete;
138 148  
139 149 std::shared_ptr<NNTreeImpl> impl;
... ...
include/qpdf/QPDFNumberTreeObjectHelper.hh
... ... @@ -39,6 +39,16 @@ class NNTreeDetails;
39 39 class QPDFNumberTreeObjectHelper: public QPDFObjectHelper
40 40 {
41 41 public:
  42 + // The qpdf object is required so that this class can issue
  43 + // warnings, attempt repairs, and add indirect objects.
  44 + QPDF_DLL
  45 + QPDFNumberTreeObjectHelper(QPDFObjectHandle, QPDF&,
  46 + bool auto_repair = true);
  47 +
  48 + // ABI: Legacy Constructor will be removed in QPDF 11. A
  49 + // QPDFNumberTreeObjectHelper constructed in this way can't be
  50 + // modified or repaired and will silently ignore problems in the
  51 + // structure.
42 52 QPDF_DLL
43 53 QPDFNumberTreeObjectHelper(QPDFObjectHandle);
44 54 QPDF_DLL
... ... @@ -154,7 +164,7 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper
154 164 ~Members();
155 165  
156 166 private:
157   - Members(QPDFObjectHandle& oh);
  167 + Members(QPDFObjectHandle& oh, QPDF*, bool auto_repair);
158 168 Members(Members const&) = delete;
159 169  
160 170 std::shared_ptr<NNTreeImpl> impl;
... ...
libqpdf/NNTree.cc
1 1 #include <qpdf/NNTree.hh>
  2 +#include <qpdf/QTC.hh>
2 3 #include <qpdf/QUtil.hh>
3 4  
4 5 #include <exception>
5 6  
  7 +static std::string
  8 +get_description(QPDFObjectHandle& node)
  9 +{
  10 + std::string result("Name/Number tree node");
  11 + if (node.isIndirect())
  12 + {
  13 + result += " (object " + QUtil::int_to_string(node.getObjectID()) + ")";
  14 + }
  15 + return result;
  16 +}
  17 +
  18 +static void
  19 +warn(QPDF* qpdf, QPDFObjectHandle& node, std::string const& msg)
  20 +{
  21 + // ABI: in qpdf 11, change to a reference.
  22 +
  23 + if (qpdf)
  24 + {
  25 + qpdf->warn(QPDFExc(
  26 + qpdf_e_damaged_pdf,
  27 + qpdf->getFilename(), get_description(node), 0, msg));
  28 + }
  29 +}
  30 +
  31 +static void
  32 +error(QPDF* qpdf, QPDFObjectHandle& node, std::string const& msg)
  33 +{
  34 + // ABI: in qpdf 11, change to a reference.
  35 +
  36 + if (qpdf)
  37 + {
  38 + throw QPDFExc(qpdf_e_damaged_pdf,
  39 + qpdf->getFilename(), get_description(node), 0, msg);
  40 + }
  41 + else
  42 + {
  43 + throw std::runtime_error(get_description(node) + ": " + msg);
  44 + }
  45 +}
  46 +
6 47 NNTreeIterator::PathElement::PathElement(
7 48 QPDFObjectHandle const& node, int kid_number) :
8 49 node(node),
... ... @@ -138,6 +179,13 @@ NNTreeIterator::addPathElement(QPDFObjectHandle const&amp; node,
138 179 }
139 180  
140 181 void
  182 +NNTreeIterator::reset()
  183 +{
  184 + this->path.clear();
  185 + this->item_number = -1;
  186 +}
  187 +
  188 +void
141 189 NNTreeIterator::deepen(QPDFObjectHandle node, bool first)
142 190 {
143 191 std::set<QPDFObjGen> seen;
... ... @@ -148,7 +196,11 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first)
148 196 auto og = node.getObjGen();
149 197 if (seen.count(og))
150 198 {
151   - throw std::runtime_error("loop detected");
  199 + QTC::TC("qpdf", "NNTree deepen: loop");
  200 + warn(qpdf, node,
  201 + "loop detected while traversing name/number tree");
  202 + reset();
  203 + return;
152 204 }
153 205 seen.insert(og);
154 206 }
... ... @@ -169,7 +221,11 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first)
169 221 }
170 222 else
171 223 {
172   - throw std::runtime_error("node has neither /Kids nor /Names");
  224 + QTC::TC("qpdf", "NNTree deepen: invalid node");
  225 + warn(qpdf, node,
  226 + "name/number tree node has neither /Kids nor /Names");
  227 + reset();
  228 + return;
173 229 }
174 230 }
175 231 }
... ... @@ -179,6 +235,7 @@ NNTreeImpl::NNTreeImpl(NNTreeDetails const&amp; details,
179 235 QPDFObjectHandle& oh,
180 236 bool auto_repair) :
181 237 details(details),
  238 + qpdf(qpdf),
182 239 oh(oh)
183 240 {
184 241 }
... ... @@ -186,7 +243,7 @@ NNTreeImpl::NNTreeImpl(NNTreeDetails const&amp; details,
186 243 NNTreeImpl::iterator
187 244 NNTreeImpl::begin()
188 245 {
189   - iterator result(details);
  246 + iterator result(details, this->qpdf);
190 247 result.deepen(this->oh, true);
191 248 return result;
192 249 }
... ... @@ -194,13 +251,13 @@ NNTreeImpl::begin()
194 251 NNTreeImpl::iterator
195 252 NNTreeImpl::end()
196 253 {
197   - return iterator(details);
  254 + return iterator(details, this->qpdf);
198 255 }
199 256  
200 257 NNTreeImpl::iterator
201 258 NNTreeImpl::last()
202 259 {
203   - iterator result(details);
  260 + iterator result(details, this->qpdf);
204 261 result.deepen(this->oh, false);
205 262 return result;
206 263 }
... ... @@ -315,21 +372,22 @@ NNTreeImpl::compareKeyItem(
315 372 if (! ((items.isArray() && (items.getArrayNItems() > (2 * idx)) &&
316 373 details.keyValid(items.getArrayItem(2 * idx)))))
317 374 {
318   - throw std::runtime_error("item at index " +
319   - QUtil::int_to_string(2 * idx) +
320   - " is not the right type");
  375 + error(qpdf, this->oh,
  376 + "item at index " + QUtil::int_to_string(2 * idx) +
  377 + " is not the right type");
321 378 }
322 379 return details.compareKeys(key, items.getArrayItem(2 * idx));
323 380 }
324 381  
325 382 int
326   -NNTreeImpl::compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& kids, int idx)
  383 +NNTreeImpl::compareKeyKid(
  384 + QPDFObjectHandle& key, QPDFObjectHandle& kids, int idx)
327 385 {
328 386 if (! (kids.isArray() && (idx < kids.getArrayNItems()) &&
329 387 kids.getArrayItem(idx).isDictionary()))
330 388 {
331   - throw std::runtime_error("invalid kid at index " +
332   - QUtil::int_to_string(idx));
  389 + error(qpdf, this->oh,
  390 + "invalid kid at index " + QUtil::int_to_string(idx));
333 391 }
334 392 return withinLimits(key, kids.getArrayItem(idx));
335 393 }
... ... @@ -364,14 +422,14 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found)
364 422  
365 423 std::set<QPDFObjGen> seen;
366 424 auto node = this->oh;
367   - iterator result(details);
  425 + iterator result(details, this->qpdf);
368 426  
369 427 while (true)
370 428 {
371 429 auto og = node.getObjGen();
372 430 if (seen.count(og))
373 431 {
374   - throw std::runtime_error("loop detected in find");
  432 + error(qpdf, node, "loop detected in find");
375 433 }
376 434 seen.insert(og);
377 435  
... ... @@ -397,16 +455,16 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found)
397 455 &NNTreeImpl::compareKeyKid);
398 456 if (idx == -1)
399 457 {
400   - throw std::runtime_error(
401   - "unexpected -1 from binary search of kids;"
402   - " tree may not be sorted");
  458 + error(qpdf, node,
  459 + "unexpected -1 from binary search of kids;"
  460 + " tree may not be sorted");
403 461 }
404 462 result.addPathElement(node, idx);
405 463 node = kids.getArrayItem(idx);
406 464 }
407 465 else
408 466 {
409   - throw std::runtime_error("bad node during find");
  467 + error(qpdf, node, "bad node during find");
410 468 }
411 469 }
412 470  
... ...
libqpdf/QPDFNameTreeObjectHelper.cc
... ... @@ -33,14 +33,22 @@ QPDFNameTreeObjectHelper::Members::~Members()
33 33 {
34 34 }
35 35  
36   -QPDFNameTreeObjectHelper::Members::Members(QPDFObjectHandle& oh) :
37   - impl(std::make_shared<NNTreeImpl>(name_tree_details, nullptr, oh, false))
  36 +QPDFNameTreeObjectHelper::Members::Members(
  37 + QPDFObjectHandle& oh, QPDF* q, bool auto_repair) :
  38 + impl(std::make_shared<NNTreeImpl>(name_tree_details, q, oh, auto_repair))
  39 +{
  40 +}
  41 +
  42 +QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper(
  43 + QPDFObjectHandle oh, QPDF& q, bool auto_repair) :
  44 + QPDFObjectHelper(oh),
  45 + m(new Members(oh, &q, auto_repair))
38 46 {
39 47 }
40 48  
41 49 QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper(QPDFObjectHandle oh) :
42 50 QPDFObjectHelper(oh),
43   - m(new Members(oh))
  51 + m(new Members(oh, nullptr, false))
44 52 {
45 53 }
46 54  
... ...
libqpdf/QPDFNumberTreeObjectHelper.cc
... ... @@ -33,14 +33,22 @@ QPDFNumberTreeObjectHelper::Members::~Members()
33 33 {
34 34 }
35 35  
36   -QPDFNumberTreeObjectHelper::Members::Members(QPDFObjectHandle& oh) :
37   - impl(std::make_shared<NNTreeImpl>(number_tree_details, nullptr, oh, false))
  36 +QPDFNumberTreeObjectHelper::Members::Members(
  37 + QPDFObjectHandle& oh, QPDF* q, bool auto_repair) :
  38 + impl(std::make_shared<NNTreeImpl>(number_tree_details, q, oh, auto_repair))
  39 +{
  40 +}
  41 +
  42 +QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper(
  43 + QPDFObjectHandle oh, QPDF& q, bool auto_repair) :
  44 + QPDFObjectHelper(oh),
  45 + m(new Members(oh, &q, auto_repair))
38 46 {
39 47 }
40 48  
41 49 QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper(QPDFObjectHandle oh) :
42 50 QPDFObjectHelper(oh),
43   - m(new Members(oh))
  51 + m(new Members(oh, nullptr, false))
44 52 {
45 53 }
46 54  
... ...
libqpdf/qpdf/NNTree.hh
... ... @@ -57,17 +57,21 @@ class NNTreeIterator: public std::iterator&lt;
57 57 int kid_number;
58 58 };
59 59  
60   - NNTreeIterator(NNTreeDetails const& details) :
  60 + // ABI: for qpdf 11, make qpdf a reference
  61 + NNTreeIterator(NNTreeDetails const& details, QPDF* qpdf) :
61 62 details(details),
  63 + qpdf(qpdf),
62 64 item_number(-1)
63 65 {
64 66 }
  67 + void reset();
65 68 void deepen(QPDFObjectHandle node, bool first);
66 69 void setItemNumber(QPDFObjectHandle const& node, int);
67 70 void addPathElement(QPDFObjectHandle const& node, int kid_number);
68 71 void increment(bool backward);
69 72  
70 73 NNTreeDetails const& details;
  74 + QPDF* qpdf;
71 75 std::list<PathElement> path;
72 76 QPDFObjectHandle node;
73 77 int item_number;
... ... @@ -99,6 +103,7 @@ class NNTreeImpl
99 103 QPDFObjectHandle& key, QPDFObjectHandle& items, int idx);
100 104  
101 105 NNTreeDetails const& details;
  106 + QPDF* qpdf;
102 107 QPDFObjectHandle oh;
103 108 };
104 109  
... ...
qpdf/qpdf.testcov
... ... @@ -522,3 +522,5 @@ qpdf-c called qpdf_oh_unparse_resolved 0
522 522 qpdf-c called qpdf_oh_unparse_binary 0
523 523 QPDFWriter getFilterOnWrite false 0
524 524 QPDFPageObjectHelper::forEachXObject 3
  525 +NNTree deepen: invalid node 0
  526 +NNTree deepen: loop 0
... ...
qpdf/qtest/qpdf/number-tree.out
... ... @@ -26,4 +26,6 @@
26 26 22 twenty-two
27 27 23 twenty-three
28 28 29 twenty-nine
  29 +WARNING: number-tree.pdf (Name/Number tree node (object 14)): name/number tree node has neither /Kids nor /Names
  30 +WARNING: number-tree.pdf (Name/Number tree node (object 13)): loop detected while traversing name/number tree
29 31 test 46 done
... ...
qpdf/qtest/qpdf/number-tree.pdf
... ... @@ -144,9 +144,22 @@ endobj
144 144 >>
145 145 endobj
146 146  
  147 +13 0 obj
  148 +<<
  149 + /Kids [
  150 + 14 0 R
  151 + 13 0 R
  152 + ]
  153 +>>
  154 +endobj
  155 +
  156 +14 0 obj
  157 +<<
  158 +>>
  159 +endobj
147 160  
148 161 xref
149   -0 13
  162 +0 15
150 163 0000000000 65535 f
151 164 0000000025 00000 n
152 165 0000000079 00000 n
... ... @@ -160,12 +173,15 @@ xref
160 173 0000000791 00000 n
161 174 0000000937 00000 n
162 175 0000001078 00000 n
  176 +0000001214 00000 n
  177 +0000001273 00000 n
163 178 trailer <<
164 179 /Root 1 0 R
165 180 /QTest 8 0 R
166   - /Size 13
  181 + /Bad1 13 0 R
  182 + /Size 15
167 183 /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>]
168 184 >>
169 185 startxref
170   -1215
  186 +1296
171 187 %%EOF
... ...
qpdf/test_driver.cc
... ... @@ -1775,6 +1775,16 @@ void runtest(int n, char const* filename1, char const* arg2)
1775 1775 assert(ntoh.findObjectAtOrBelow(8, oh, offset));
1776 1776 assert("six" == oh.getStringValue());
1777 1777 assert(2 == offset);
  1778 +
  1779 + // Exercise deprecated API until qpdf 11
  1780 + auto bad1 = QPDFNumberTreeObjectHelper(
  1781 + pdf.getTrailer().getKey("/Bad1"));
  1782 + assert(bad1.begin() == bad1.end());
  1783 +
  1784 + bad1 = QPDFNumberTreeObjectHelper(
  1785 + pdf.getTrailer().getKey("/Bad1"), pdf);
  1786 + assert(bad1.begin() == bad1.end());
  1787 + assert(bad1.last() == bad1.end());
1778 1788 }
1779 1789 else if (n == 47)
1780 1790 {
... ...