Commit ec6784411d4f9fbf99a03f8e092e38fd3816049a
Committed by
GitHub
Merge pull request #1028 from m-holger/i1003
Maintain links to foreign pages when copying foreign objects (fixes #1003)
Showing
5 changed files
with
382 additions
and
363 deletions
include/qpdf/QPDF.hh
| ... | ... | @@ -351,6 +351,8 @@ class QPDF |
| 351 | 351 | // QPDF with QPDFWriter if it has any reserved objects in it. |
| 352 | 352 | QPDF_DLL |
| 353 | 353 | QPDFObjectHandle newReserved(); |
| 354 | + QPDF_DLL | |
| 355 | + QPDFObjectHandle newIndirectNull(); | |
| 354 | 356 | |
| 355 | 357 | // Install this object handle as an indirect object and return an indirect reference to it. |
| 356 | 358 | QPDF_DLL |
| ... | ... | @@ -391,8 +393,8 @@ class QPDF |
| 391 | 393 | void swapObjects(int objid1, int generation1, int objid2, int generation2); |
| 392 | 394 | |
| 393 | 395 | // Replace a reserved object. This is a wrapper around replaceObject but it guarantees that the |
| 394 | - // underlying object is a reserved object. After this call, reserved will be a reference to | |
| 395 | - // replacement. | |
| 396 | + // underlying object is a reserved object or a null object. After this call, reserved will | |
| 397 | + // be a reference to replacement. | |
| 396 | 398 | QPDF_DLL |
| 397 | 399 | void replaceReserved(QPDFObjectHandle reserved, QPDFObjectHandle replacement); |
| 398 | 400 | ... | ... |
libqpdf/QPDF.cc
| ... | ... | @@ -1861,6 +1861,12 @@ QPDF::newReserved() |
| 1861 | 1861 | } |
| 1862 | 1862 | |
| 1863 | 1863 | QPDFObjectHandle |
| 1864 | +QPDF::newIndirectNull() | |
| 1865 | +{ | |
| 1866 | + return makeIndirectFromQPDFObject(QPDF_Null::create()); | |
| 1867 | +} | |
| 1868 | + | |
| 1869 | +QPDFObjectHandle | |
| 1864 | 1870 | QPDF::newStream() |
| 1865 | 1871 | { |
| 1866 | 1872 | return makeIndirectFromQPDFObject( |
| ... | ... | @@ -1948,7 +1954,10 @@ void |
| 1948 | 1954 | QPDF::replaceReserved(QPDFObjectHandle reserved, QPDFObjectHandle replacement) |
| 1949 | 1955 | { |
| 1950 | 1956 | QTC::TC("qpdf", "QPDF replaceReserved"); |
| 1951 | - reserved.assertReserved(); | |
| 1957 | + auto tc = reserved.getTypeCode(); | |
| 1958 | + if (!(tc == ::ot_reserved || tc == ::ot_null)) { | |
| 1959 | + throw std::logic_error("replaceReserved called with non-reserverd object"); | |
| 1960 | + } | |
| 1952 | 1961 | replaceObject(reserved.getObjGen(), replacement); |
| 1953 | 1962 | } |
| 1954 | 1963 | |
| ... | ... | @@ -2012,8 +2021,7 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign) |
| 2012 | 2021 | reserveObjects(foreign, obj_copier, true); |
| 2013 | 2022 | |
| 2014 | 2023 | if (!obj_copier.visiting.empty()) { |
| 2015 | - throw std::logic_error("obj_copier.visiting is not empty" | |
| 2016 | - " after reserving objects"); | |
| 2024 | + throw std::logic_error("obj_copier.visiting is not empty after reserving objects"); | |
| 2017 | 2025 | } |
| 2018 | 2026 | |
| 2019 | 2027 | // Copy any new objects and replace the reservations. |
| ... | ... | @@ -2026,7 +2034,13 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign) |
| 2026 | 2034 | } |
| 2027 | 2035 | obj_copier.to_copy.clear(); |
| 2028 | 2036 | |
| 2029 | - return obj_copier.object_map[foreign.getObjGen()]; | |
| 2037 | + auto& result = obj_copier.object_map[foreign.getObjGen()]; | |
| 2038 | + if (!result.isInitialized()) { | |
| 2039 | + result = QPDFObjectHandle::newNull(); | |
| 2040 | + warn(damagedPDF("Unexpected reference to /Pages object while copying foreign object. " | |
| 2041 | + "Replacing with Null object.")); | |
| 2042 | + } | |
| 2043 | + return result; | |
| 2030 | 2044 | } |
| 2031 | 2045 | |
| 2032 | 2046 | void |
| ... | ... | @@ -2042,11 +2056,6 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) |
| 2042 | 2056 | return; |
| 2043 | 2057 | } |
| 2044 | 2058 | |
| 2045 | - if ((!top) && foreign.isPageObject()) { | |
| 2046 | - QTC::TC("qpdf", "QPDF not crossing page boundary"); | |
| 2047 | - return; | |
| 2048 | - } | |
| 2049 | - | |
| 2050 | 2059 | if (foreign.isIndirect()) { |
| 2051 | 2060 | QPDFObjGen foreign_og(foreign.getObjGen()); |
| 2052 | 2061 | if (obj_copier.object_map.count(foreign_og) > 0) { |
| ... | ... | @@ -2061,8 +2070,14 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) |
| 2061 | 2070 | } |
| 2062 | 2071 | QTC::TC("qpdf", "QPDF copy indirect"); |
| 2063 | 2072 | if (obj_copier.object_map.count(foreign_og) == 0) { |
| 2073 | + obj_copier.object_map[foreign_og] = | |
| 2074 | + foreign.isStream() ? newStream() : newIndirectNull(); | |
| 2075 | + if ((!top) && foreign.isPageObject()) { | |
| 2076 | + QTC::TC("qpdf", "QPDF not crossing page boundary"); | |
| 2077 | + obj_copier.visiting.erase(foreign); | |
| 2078 | + return; | |
| 2079 | + } | |
| 2064 | 2080 | obj_copier.to_copy.push_back(foreign); |
| 2065 | - obj_copier.object_map[foreign_og] = foreign.isStream() ? newStream() : newReserved(); | |
| 2066 | 2081 | } |
| 2067 | 2082 | } |
| 2068 | 2083 | |
| ... | ... | @@ -2094,7 +2109,7 @@ QPDF::replaceForeignIndirectObjects(QPDFObjectHandle foreign, ObjCopier& obj_cop |
| 2094 | 2109 | QTC::TC("qpdf", "QPDF replace indirect"); |
| 2095 | 2110 | auto mapping = obj_copier.object_map.find(foreign.getObjGen()); |
| 2096 | 2111 | if (mapping == obj_copier.object_map.end()) { |
| 2097 | - // This case would occur if this is a reference to a Page or Pages object that we didn't | |
| 2112 | + // This case would occur if this is a reference to a Pages object that we didn't | |
| 2098 | 2113 | // traverse into. |
| 2099 | 2114 | QTC::TC("qpdf", "QPDF replace foreign indirect with null"); |
| 2100 | 2115 | result = QPDFObjectHandle::newNull(); | ... | ... |
libqpdf/QPDF_Dictionary.cc
| ... | ... | @@ -121,8 +121,10 @@ QPDF_Dictionary::getAsMap() const |
| 121 | 121 | void |
| 122 | 122 | QPDF_Dictionary::replaceKey(std::string const& key, QPDFObjectHandle value) |
| 123 | 123 | { |
| 124 | - if (value.isNull()) { | |
| 125 | - // The PDF spec doesn't distinguish between keys with null values and missing keys. | |
| 124 | + if (value.isNull() && !value.isIndirect()) { | |
| 125 | + // The PDF spec doesn't distinguish between keys with null values and missing keys. Allow | |
| 126 | + // indirect nulls which are equivalent to a dangling reference, which is permitted by the | |
| 127 | + // spec. | |
| 126 | 128 | removeKey(key); |
| 127 | 129 | } else { |
| 128 | 130 | // add or replace value | ... | ... |
qpdf/qtest/qpdf/test80b1.pdf
No preview for this file type
qpdf/qtest/qpdf/test80b2.pdf
No preview for this file type