Commit 9b28933647f0a3ed535dd488c4526b75d1c10fc6

Authored by Jay Berkenbilt
1 parent 73752683

Check object ownership when adding

When adding a QPDFObjectHandle to an array or dictionary, if possible,
check if the new object belongs to the same QPDF. This makes it much
easier to find incorrect code than waiting for the situation to be
detected when the file is written.
ChangeLog
  1 +2021-11-04 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Add an extra check to the library to detect when foreign objects
  4 + are inserted directly (instead of using
  5 + <function>QPDF::copyForeignObject</function>) at the time of
  6 + insertion rather than when the file is written. Catching the error
  7 + sooner makes it much easier to locate the incorrect code.
  8 +
1 9 2021-11-03 Jay Berkenbilt <ejb@ql.org>
2 10  
3 11 * Bug fix: make overlay/underlay work on a page with no resource
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -1336,6 +1336,7 @@ class QPDFObjectHandle
1336 1336 std::vector<QPDFObjectHandle> arrayOrStreamToStreamArray(
1337 1337 std::string const& description, std::string& all_description);
1338 1338 static void warn(QPDF*, QPDFExc const&);
  1339 + void checkOwnership(QPDFObjectHandle const&) const;
1339 1340  
1340 1341 bool initialized;
1341 1342  
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -864,6 +864,7 @@ QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const&amp; item)
864 864 {
865 865 if (isArray())
866 866 {
  867 + checkOwnership(item);
867 868 dynamic_cast<QPDF_Array*>(obj.getPointer())->setItem(n, item);
868 869 }
869 870 else
... ... @@ -878,6 +879,10 @@ QPDFObjectHandle::setArrayFromVector(std::vector&lt;QPDFObjectHandle&gt; const&amp; items)
878 879 {
879 880 if (isArray())
880 881 {
  882 + for (auto const& item: items)
  883 + {
  884 + checkOwnership(item);
  885 + }
881 886 dynamic_cast<QPDF_Array*>(obj.getPointer())->setFromVector(items);
882 887 }
883 888 else
... ... @@ -906,6 +911,7 @@ QPDFObjectHandle::appendItem(QPDFObjectHandle const&amp; item)
906 911 {
907 912 if (isArray())
908 913 {
  914 + checkOwnership(item);
909 915 dynamic_cast<QPDF_Array*>(obj.getPointer())->appendItem(item);
910 916 }
911 917 else
... ... @@ -1283,6 +1289,7 @@ QPDFObjectHandle::replaceKey(std::string const&amp; key,
1283 1289 {
1284 1290 if (isDictionary())
1285 1291 {
  1292 + checkOwnership(value);
1286 1293 dynamic_cast<QPDF_Dictionary*>(
1287 1294 obj.getPointer())->replaceKey(key, value);
1288 1295 }
... ... @@ -1313,6 +1320,7 @@ QPDFObjectHandle::replaceOrRemoveKey(std::string const&amp; key,
1313 1320 {
1314 1321 if (isDictionary())
1315 1322 {
  1323 + checkOwnership(value);
1316 1324 dynamic_cast<QPDF_Dictionary*>(
1317 1325 obj.getPointer())->replaceOrRemoveKey(key, value);
1318 1326 }
... ... @@ -3270,6 +3278,20 @@ QPDFObjectHandle::isImage(bool exclude_imagemask)
3270 3278 }
3271 3279  
3272 3280 void
  3281 +QPDFObjectHandle::checkOwnership(QPDFObjectHandle const& item) const
  3282 +{
  3283 + if ((this->qpdf != nullptr) &&
  3284 + (item.qpdf != nullptr) &&
  3285 + (this->qpdf != item.qpdf))
  3286 + {
  3287 + QTC::TC("qpdf", "QPDFObjectHandle check ownership");
  3288 + throw std::logic_error(
  3289 + "Attempting to add an object from a different QPDF."
  3290 + " Use QPDF::copyForeignObject to add objects from another file.");
  3291 + }
  3292 +}
  3293 +
  3294 +void
3273 3295 QPDFObjectHandle::assertPageObject()
3274 3296 {
3275 3297 if (! isPageObject())
... ...
manual/qpdf-manual.xml
... ... @@ -5104,6 +5104,16 @@ print &quot;\n&quot;;
5104 5104 receive warnings on certain recoverable conditions.
5105 5105 </para>
5106 5106 </listitem>
  5107 + <listitem>
  5108 + <para>
  5109 + Add an extra check to the library to detect when foreign
  5110 + objects are inserted directly (instead of using
  5111 + <function>QPDF::copyForeignObject</function>) at the time of
  5112 + insertion rather than when the file is written. Catching the
  5113 + error sooner makes it much easier to locate the incorrect
  5114 + code.
  5115 + </para>
  5116 + </listitem>
5107 5117 </itemizedlist>
5108 5118 </listitem>
5109 5119 <listitem>
... ...
qpdf/qpdf.cc
... ... @@ -5705,7 +5705,9 @@ static QPDFObjectHandle added_page(QPDF&amp; pdf, QPDFPageObjectHelper page)
5705 5705 return added_page(pdf, page.getObjectHandle());
5706 5706 }
5707 5707  
5708   -static void handle_page_specs(QPDF& pdf, Options& o, bool& warnings)
  5708 +static void handle_page_specs(
  5709 + QPDF& pdf, Options& o, bool& warnings,
  5710 + std::vector<PointerHolder<QPDF>>& page_heap)
5709 5711 {
5710 5712 // Parse all page specifications and translate them into lists of
5711 5713 // actual pages.
... ... @@ -5757,7 +5759,6 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
5757 5759 }
5758 5760  
5759 5761 // Create a QPDF object for each file that we may take pages from.
5760   - std::vector<PointerHolder<QPDF> > page_heap;
5761 5762 std::map<std::string, QPDF*> page_spec_qpdfs;
5762 5763 std::map<std::string, ClosedFileInputSource*> page_spec_cfis;
5763 5764 page_spec_qpdfs[o.infilename] = &pdf;
... ... @@ -6009,7 +6010,7 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
6009 6010 pdf.warn(
6010 6011 QPDFExc(qpdf_e_damaged_pdf, pdf.getFilename(),
6011 6012 "", 0, "Exception caught while fixing copied"
6012   - " annotations. This may be a qpdf bug." +
  6013 + " annotations. This may be a qpdf bug. " +
6013 6014 std::string("Exception: ") + e.what()));
6014 6015 }
6015 6016 }
... ... @@ -6649,9 +6650,10 @@ int realmain(int argc, char* argv[])
6649 6650 }
6650 6651 }
6651 6652 bool other_warnings = false;
  6653 + std::vector<PointerHolder<QPDF>> page_heap;
6652 6654 if (! o.page_specs.empty())
6653 6655 {
6654   - handle_page_specs(pdf, o, other_warnings);
  6656 + handle_page_specs(pdf, o, other_warnings, page_heap);
6655 6657 }
6656 6658 if (! o.rotations.empty())
6657 6659 {
... ...
qpdf/qpdf.testcov
... ... @@ -597,3 +597,4 @@ QPDFWriter exclude from object stream 0
597 597 check unclosed --pages 1
598 598 QPDF_pages findPage not found 0
599 599 qpdf overlay page with no resources 0
  600 +QPDFObjectHandle check ownership 0
... ...
qpdf/qtest/qpdf/foreign-in-write.out
1 1 logic error: QPDFObjectHandle from different QPDF found while writing. Use QPDF::copyForeignObject to add objects from another file.
  2 +logic error: Attempting to add an object from a different QPDF. Use QPDF::copyForeignObject to add objects from another file.
2 3 test 29 done
... ...
qpdf/test_driver.cc
... ... @@ -1274,6 +1274,18 @@ void runtest(int n, char const* filename1, char const* arg2)
1274 1274 {
1275 1275 std::cout << "logic error: " << e.what() << std::endl;
1276 1276 }
  1277 +
  1278 + // Detect adding a foreign object
  1279 + auto root1 = pdf.getRoot();
  1280 + auto root2 = other.getRoot();
  1281 + try
  1282 + {
  1283 + root1.replaceKey("/Oops", root2);
  1284 + }
  1285 + catch (std::logic_error const& e)
  1286 + {
  1287 + std::cout << "logic error: " << e.what() << std::endl;
  1288 + }
1277 1289 }
1278 1290 else if (n == 30)
1279 1291 {
... ...