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 2021-01-16 Jay Berkenbilt <ejb@ql.org> 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 * Re-implement QPDFNameTreeObjectHelper and 9 * Re-implement QPDFNameTreeObjectHelper and
4 QPDFNumberTreeObjectHelper to be much more efficient and to have 10 QPDFNumberTreeObjectHelper to be much more efficient and to have
5 an iterator-based API in addition to the existing one. This makes 11 an iterator-based API in addition to the existing one. This makes
@@ -84,7 +84,12 @@ ABI Changes @@ -84,7 +84,12 @@ ABI Changes
84 This is a list of changes to make next time there is an ABI change. 84 This is a list of changes to make next time there is an ABI change.
85 Comments appear in the code prefixed by "ABI" 85 Comments appear in the code prefixed by "ABI"
86 86
  87 +* Search for ABI to find items not listed here.
87 * Merge two versions of QPDFObjectHandle::makeDirect per comment 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 Page splitting/merging 94 Page splitting/merging
90 ====================== 95 ======================
include/qpdf/QPDFNameTreeObjectHelper.hh
@@ -42,6 +42,16 @@ class NNTreeDetails; @@ -42,6 +42,16 @@ class NNTreeDetails;
42 class QPDFNameTreeObjectHelper: public QPDFObjectHelper 42 class QPDFNameTreeObjectHelper: public QPDFObjectHelper
43 { 43 {
44 public: 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 QPDF_DLL 55 QPDF_DLL
46 QPDFNameTreeObjectHelper(QPDFObjectHandle); 56 QPDFNameTreeObjectHelper(QPDFObjectHandle);
47 QPDF_DLL 57 QPDF_DLL
@@ -133,7 +143,7 @@ class QPDFNameTreeObjectHelper: public QPDFObjectHelper @@ -133,7 +143,7 @@ class QPDFNameTreeObjectHelper: public QPDFObjectHelper
133 ~Members(); 143 ~Members();
134 144
135 private: 145 private:
136 - Members(QPDFObjectHandle& oh); 146 + Members(QPDFObjectHandle& oh, QPDF*, bool auto_repair);
137 Members(Members const&) = delete; 147 Members(Members const&) = delete;
138 148
139 std::shared_ptr<NNTreeImpl> impl; 149 std::shared_ptr<NNTreeImpl> impl;
include/qpdf/QPDFNumberTreeObjectHelper.hh
@@ -39,6 +39,16 @@ class NNTreeDetails; @@ -39,6 +39,16 @@ class NNTreeDetails;
39 class QPDFNumberTreeObjectHelper: public QPDFObjectHelper 39 class QPDFNumberTreeObjectHelper: public QPDFObjectHelper
40 { 40 {
41 public: 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 QPDF_DLL 52 QPDF_DLL
43 QPDFNumberTreeObjectHelper(QPDFObjectHandle); 53 QPDFNumberTreeObjectHelper(QPDFObjectHandle);
44 QPDF_DLL 54 QPDF_DLL
@@ -154,7 +164,7 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper @@ -154,7 +164,7 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper
154 ~Members(); 164 ~Members();
155 165
156 private: 166 private:
157 - Members(QPDFObjectHandle& oh); 167 + Members(QPDFObjectHandle& oh, QPDF*, bool auto_repair);
158 Members(Members const&) = delete; 168 Members(Members const&) = delete;
159 169
160 std::shared_ptr<NNTreeImpl> impl; 170 std::shared_ptr<NNTreeImpl> impl;
libqpdf/NNTree.cc
1 #include <qpdf/NNTree.hh> 1 #include <qpdf/NNTree.hh>
  2 +#include <qpdf/QTC.hh>
2 #include <qpdf/QUtil.hh> 3 #include <qpdf/QUtil.hh>
3 4
4 #include <exception> 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 NNTreeIterator::PathElement::PathElement( 47 NNTreeIterator::PathElement::PathElement(
7 QPDFObjectHandle const& node, int kid_number) : 48 QPDFObjectHandle const& node, int kid_number) :
8 node(node), 49 node(node),
@@ -138,6 +179,13 @@ NNTreeIterator::addPathElement(QPDFObjectHandle const&amp; node, @@ -138,6 +179,13 @@ NNTreeIterator::addPathElement(QPDFObjectHandle const&amp; node,
138 } 179 }
139 180
140 void 181 void
  182 +NNTreeIterator::reset()
  183 +{
  184 + this->path.clear();
  185 + this->item_number = -1;
  186 +}
  187 +
  188 +void
141 NNTreeIterator::deepen(QPDFObjectHandle node, bool first) 189 NNTreeIterator::deepen(QPDFObjectHandle node, bool first)
142 { 190 {
143 std::set<QPDFObjGen> seen; 191 std::set<QPDFObjGen> seen;
@@ -148,7 +196,11 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first) @@ -148,7 +196,11 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first)
148 auto og = node.getObjGen(); 196 auto og = node.getObjGen();
149 if (seen.count(og)) 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 seen.insert(og); 205 seen.insert(og);
154 } 206 }
@@ -169,7 +221,11 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first) @@ -169,7 +221,11 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first)
169 } 221 }
170 else 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,6 +235,7 @@ NNTreeImpl::NNTreeImpl(NNTreeDetails const&amp; details,
179 QPDFObjectHandle& oh, 235 QPDFObjectHandle& oh,
180 bool auto_repair) : 236 bool auto_repair) :
181 details(details), 237 details(details),
  238 + qpdf(qpdf),
182 oh(oh) 239 oh(oh)
183 { 240 {
184 } 241 }
@@ -186,7 +243,7 @@ NNTreeImpl::NNTreeImpl(NNTreeDetails const&amp; details, @@ -186,7 +243,7 @@ NNTreeImpl::NNTreeImpl(NNTreeDetails const&amp; details,
186 NNTreeImpl::iterator 243 NNTreeImpl::iterator
187 NNTreeImpl::begin() 244 NNTreeImpl::begin()
188 { 245 {
189 - iterator result(details); 246 + iterator result(details, this->qpdf);
190 result.deepen(this->oh, true); 247 result.deepen(this->oh, true);
191 return result; 248 return result;
192 } 249 }
@@ -194,13 +251,13 @@ NNTreeImpl::begin() @@ -194,13 +251,13 @@ NNTreeImpl::begin()
194 NNTreeImpl::iterator 251 NNTreeImpl::iterator
195 NNTreeImpl::end() 252 NNTreeImpl::end()
196 { 253 {
197 - return iterator(details); 254 + return iterator(details, this->qpdf);
198 } 255 }
199 256
200 NNTreeImpl::iterator 257 NNTreeImpl::iterator
201 NNTreeImpl::last() 258 NNTreeImpl::last()
202 { 259 {
203 - iterator result(details); 260 + iterator result(details, this->qpdf);
204 result.deepen(this->oh, false); 261 result.deepen(this->oh, false);
205 return result; 262 return result;
206 } 263 }
@@ -315,21 +372,22 @@ NNTreeImpl::compareKeyItem( @@ -315,21 +372,22 @@ NNTreeImpl::compareKeyItem(
315 if (! ((items.isArray() && (items.getArrayNItems() > (2 * idx)) && 372 if (! ((items.isArray() && (items.getArrayNItems() > (2 * idx)) &&
316 details.keyValid(items.getArrayItem(2 * idx))))) 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 return details.compareKeys(key, items.getArrayItem(2 * idx)); 379 return details.compareKeys(key, items.getArrayItem(2 * idx));
323 } 380 }
324 381
325 int 382 int
326 -NNTreeImpl::compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& kids, int idx) 383 +NNTreeImpl::compareKeyKid(
  384 + QPDFObjectHandle& key, QPDFObjectHandle& kids, int idx)
327 { 385 {
328 if (! (kids.isArray() && (idx < kids.getArrayNItems()) && 386 if (! (kids.isArray() && (idx < kids.getArrayNItems()) &&
329 kids.getArrayItem(idx).isDictionary())) 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 return withinLimits(key, kids.getArrayItem(idx)); 392 return withinLimits(key, kids.getArrayItem(idx));
335 } 393 }
@@ -364,14 +422,14 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) @@ -364,14 +422,14 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found)
364 422
365 std::set<QPDFObjGen> seen; 423 std::set<QPDFObjGen> seen;
366 auto node = this->oh; 424 auto node = this->oh;
367 - iterator result(details); 425 + iterator result(details, this->qpdf);
368 426
369 while (true) 427 while (true)
370 { 428 {
371 auto og = node.getObjGen(); 429 auto og = node.getObjGen();
372 if (seen.count(og)) 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 seen.insert(og); 434 seen.insert(og);
377 435
@@ -397,16 +455,16 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) @@ -397,16 +455,16 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found)
397 &NNTreeImpl::compareKeyKid); 455 &NNTreeImpl::compareKeyKid);
398 if (idx == -1) 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 result.addPathElement(node, idx); 462 result.addPathElement(node, idx);
405 node = kids.getArrayItem(idx); 463 node = kids.getArrayItem(idx);
406 } 464 }
407 else 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,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 QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper(QPDFObjectHandle oh) : 49 QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper(QPDFObjectHandle oh) :
42 QPDFObjectHelper(oh), 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,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 QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper(QPDFObjectHandle oh) : 49 QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper(QPDFObjectHandle oh) :
42 QPDFObjectHelper(oh), 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,17 +57,21 @@ class NNTreeIterator: public std::iterator&lt;
57 int kid_number; 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 details(details), 62 details(details),
  63 + qpdf(qpdf),
62 item_number(-1) 64 item_number(-1)
63 { 65 {
64 } 66 }
  67 + void reset();
65 void deepen(QPDFObjectHandle node, bool first); 68 void deepen(QPDFObjectHandle node, bool first);
66 void setItemNumber(QPDFObjectHandle const& node, int); 69 void setItemNumber(QPDFObjectHandle const& node, int);
67 void addPathElement(QPDFObjectHandle const& node, int kid_number); 70 void addPathElement(QPDFObjectHandle const& node, int kid_number);
68 void increment(bool backward); 71 void increment(bool backward);
69 72
70 NNTreeDetails const& details; 73 NNTreeDetails const& details;
  74 + QPDF* qpdf;
71 std::list<PathElement> path; 75 std::list<PathElement> path;
72 QPDFObjectHandle node; 76 QPDFObjectHandle node;
73 int item_number; 77 int item_number;
@@ -99,6 +103,7 @@ class NNTreeImpl @@ -99,6 +103,7 @@ class NNTreeImpl
99 QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); 103 QPDFObjectHandle& key, QPDFObjectHandle& items, int idx);
100 104
101 NNTreeDetails const& details; 105 NNTreeDetails const& details;
  106 + QPDF* qpdf;
102 QPDFObjectHandle oh; 107 QPDFObjectHandle oh;
103 }; 108 };
104 109
qpdf/qpdf.testcov
@@ -522,3 +522,5 @@ qpdf-c called qpdf_oh_unparse_resolved 0 @@ -522,3 +522,5 @@ qpdf-c called qpdf_oh_unparse_resolved 0
522 qpdf-c called qpdf_oh_unparse_binary 0 522 qpdf-c called qpdf_oh_unparse_binary 0
523 QPDFWriter getFilterOnWrite false 0 523 QPDFWriter getFilterOnWrite false 0
524 QPDFPageObjectHelper::forEachXObject 3 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,4 +26,6 @@
26 22 twenty-two 26 22 twenty-two
27 23 twenty-three 27 23 twenty-three
28 29 twenty-nine 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 test 46 done 31 test 46 done
qpdf/qtest/qpdf/number-tree.pdf
@@ -144,9 +144,22 @@ endobj @@ -144,9 +144,22 @@ endobj
144 >> 144 >>
145 endobj 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 xref 161 xref
149 -0 13 162 +0 15
150 0000000000 65535 f 163 0000000000 65535 f
151 0000000025 00000 n 164 0000000025 00000 n
152 0000000079 00000 n 165 0000000079 00000 n
@@ -160,12 +173,15 @@ xref @@ -160,12 +173,15 @@ xref
160 0000000791 00000 n 173 0000000791 00000 n
161 0000000937 00000 n 174 0000000937 00000 n
162 0000001078 00000 n 175 0000001078 00000 n
  176 +0000001214 00000 n
  177 +0000001273 00000 n
163 trailer << 178 trailer <<
164 /Root 1 0 R 179 /Root 1 0 R
165 /QTest 8 0 R 180 /QTest 8 0 R
166 - /Size 13 181 + /Bad1 13 0 R
  182 + /Size 15
167 /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>] 183 /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>]
168 >> 184 >>
169 startxref 185 startxref
170 -1215 186 +1296
171 %%EOF 187 %%EOF
qpdf/test_driver.cc
@@ -1775,6 +1775,16 @@ void runtest(int n, char const* filename1, char const* arg2) @@ -1775,6 +1775,16 @@ void runtest(int n, char const* filename1, char const* arg2)
1775 assert(ntoh.findObjectAtOrBelow(8, oh, offset)); 1775 assert(ntoh.findObjectAtOrBelow(8, oh, offset));
1776 assert("six" == oh.getStringValue()); 1776 assert("six" == oh.getStringValue());
1777 assert(2 == offset); 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 else if (n == 47) 1789 else if (n == 47)
1780 { 1790 {