Commit d61ffb65d034848157291b9825f4b33155bd55e7
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.
Showing
12 changed files
with
169 additions
and
29 deletions
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 | ... | ... |
TODO
| ... | ... | @@ -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& 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& 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& 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< |
| 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
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 | { | ... | ... |