Commit e0ebf44ffb7b160a396d4d8900f6ce16a9e32863

Authored by m-holger
1 parent d6ce922e

Move `Objects` to `QPDF::Doc` and update references

Relocate `Objects` to `QPDF::Doc` for improved encapsulation of object-related logic. Adjust all relevant methods and references to use the new placement.
include/qpdf/QPDF.hh
... ... @@ -765,66 +765,10 @@ class QPDF
765 765 class ResolveRecorder;
766 766 class JSONReactor;
767 767  
768   - void parse(char const* password);
769   - void inParse(bool);
770   - void setTrailer(QPDFObjectHandle obj);
771   - void read_xref(qpdf_offset_t offset, bool in_stream_recovery = false);
772   - bool resolveXRefTable();
773   - void reconstruct_xref(QPDFExc& e, bool found_startxref = true);
774   - bool parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes);
775   - bool read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
776   - bool read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
777   - qpdf_offset_t read_xrefTable(qpdf_offset_t offset);
778   - qpdf_offset_t read_xrefStream(qpdf_offset_t offset, bool in_stream_recovery = false);
779   - qpdf_offset_t processXRefStream(
780   - qpdf_offset_t offset, QPDFObjectHandle& xref_stream, bool in_stream_recovery = false);
781   - std::pair<int, std::array<int, 3>>
782   - processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged);
783   - int processXRefSize(
784   - QPDFObjectHandle& dict, int entry_size, std::function<QPDFExc(std::string_view)> damaged);
785   - std::pair<int, std::vector<std::pair<int, int>>> processXRefIndex(
786   - QPDFObjectHandle& dict,
787   - int max_num_entries,
788   - std::function<QPDFExc(std::string_view)> damaged);
789   - void insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2);
790   - void insertFreeXrefEntry(QPDFObjGen);
791   - void setLastObjectDescription(std::string const& description, QPDFObjGen og);
792   - QPDFObjectHandle readTrailer();
793   - QPDFObjectHandle readObject(std::string const& description, QPDFObjGen og);
794   - void readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
795   - void validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
796   - QPDFObjectHandle readObjectInStream(qpdf::is::OffsetBuffer& input, int stream_id, int obj_id);
797   - size_t recoverStreamLength(
798   - std::shared_ptr<InputSource> input, QPDFObjGen og, qpdf_offset_t stream_offset);
799   - QPDFTokenizer::Token readToken(InputSource&, size_t max_len = 0);
800   -
801   - QPDFObjGen read_object_start(qpdf_offset_t offset);
802   - void readObjectAtOffset(
803   - bool attempt_recovery,
804   - qpdf_offset_t offset,
805   - std::string const& description,
806   - QPDFObjGen exp_og);
807   - QPDFObjectHandle readObjectAtOffset(
808   - qpdf_offset_t offset, std::string const& description, bool skip_cache_if_in_xref);
809   - std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og);
810   - void resolveObjectsInStream(int obj_stream_number);
811 768 void stopOnError(std::string const& message);
812 769 inline void
813 770 no_ci_stop_if(bool condition, std::string const& message, std::string const& context = {});
814   - QPDFObjGen nextObjGen();
815   - QPDFObjectHandle newIndirect(QPDFObjGen, std::shared_ptr<QPDFObject> const&);
816   - QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj);
817   - bool isCached(QPDFObjGen og);
818   - bool isUnresolved(QPDFObjGen og);
819   - std::shared_ptr<QPDFObject> getObjectForParser(int id, int gen, bool parse_pdf);
820   - std::shared_ptr<QPDFObject> getObjectForJSON(int id, int gen);
821 771 void removeObject(QPDFObjGen og);
822   - void updateCache(
823   - QPDFObjGen og,
824   - std::shared_ptr<QPDFObject> const& object,
825   - qpdf_offset_t end_before_space,
826   - qpdf_offset_t end_after_space,
827   - bool destroy = true);
828 772 static QPDFExc damagedPDF(
829 773 InputSource& input,
830 774 std::string const& object,
... ... @@ -872,7 +816,6 @@ class QPDF
872 816 void optimize(
873 817 QPDFWriter::ObjTable const& obj,
874 818 std::function<int(QPDFObjectHandle&)> skip_stream_parameters);
875   - size_t tableSize();
876 819  
877 820 // Get lists of all objects in order according to the part of a linearized file that they belong
878 821 // to.
... ... @@ -892,12 +835,6 @@ class QPDF
892 835 int& O,
893 836 bool compressed);
894 837  
895   - // Get a list of objects that would be permitted in an object stream.
896   - template <typename T>
897   - std::vector<T> getCompressibleObjGens();
898   - std::vector<QPDFObjGen> getCompressibleObjVector();
899   - std::vector<bool> getCompressibleObjSet();
900   -
901 838 // methods to support page handling
902 839  
903 840 void getAllPagesInternal(
... ...
libqpdf/QPDF.cc
... ... @@ -180,6 +180,7 @@ QPDF::QPDFVersion()
180 180  
181 181 QPDF::Members::Members(QPDF& qpdf) :
182 182 doc(qpdf, *this),
  183 + objects(doc.objects()),
183 184 log(QPDFLogger::defaultLogger()),
184 185 file(new InvalidInputSource()),
185 186 encp(new EncryptionParameters)
... ... @@ -267,7 +268,7 @@ void
267 268 QPDF::processInputSource(std::shared_ptr<InputSource> source, char const* password)
268 269 {
269 270 m->file = source;
270   - parse(password);
  271 + m->objects.parse(password);
271 272 }
272 273  
273 274 void
... ... @@ -435,20 +436,20 @@ QPDF::warn(
435 436 QPDFObjectHandle
436 437 QPDF::newReserved()
437 438 {
438   - return makeIndirectFromQPDFObject(QPDFObject::create<QPDF_Reserved>());
  439 + return m->objects.makeIndirectFromQPDFObject(QPDFObject::create<QPDF_Reserved>());
439 440 }
440 441  
441 442 QPDFObjectHandle
442 443 QPDF::newIndirectNull()
443 444 {
444   - return makeIndirectFromQPDFObject(QPDFObject::create<QPDF_Null>());
  445 + return m->objects.makeIndirectFromQPDFObject(QPDFObject::create<QPDF_Null>());
445 446 }
446 447  
447 448 QPDFObjectHandle
448 449 QPDF::newStream()
449 450 {
450 451 return makeIndirectObject(
451   - qpdf::Stream(*this, nextObjGen(), QPDFObjectHandle::newDictionary(), 0, 0));
  452 + qpdf::Stream(*this, m->objects.nextObjGen(), Dictionary::empty(), 0, 0));
452 453 }
453 454  
454 455 QPDFObjectHandle
... ...
libqpdf/QPDFParser.cc
... ... @@ -21,26 +21,26 @@ class QPDF::Doc::ParseGuard
21 21 {
22 22 public:
23 23 ParseGuard(QPDF* qpdf) :
24   - qpdf(qpdf)
  24 + objects(qpdf ? &qpdf->m->objects : nullptr)
25 25 {
26   - if (qpdf) {
27   - qpdf->inParse(true);
  26 + if (objects) {
  27 + objects->inParse(true);
28 28 }
29 29 }
30 30  
31 31 static std::shared_ptr<QPDFObject>
32 32 getObject(QPDF* qpdf, int id, int gen, bool parse_pdf)
33 33 {
34   - return qpdf->getObjectForParser(id, gen, parse_pdf);
  34 + return qpdf->m->objects.getObjectForParser(id, gen, parse_pdf);
35 35 }
36 36  
37 37 ~ParseGuard()
38 38 {
39   - if (qpdf) {
40   - qpdf->inParse(false);
  39 + if (objects) {
  40 + objects->inParse(false);
41 41 }
42 42 }
43   - QPDF* qpdf;
  43 + QPDF::Doc::Objects* objects;
44 44 };
45 45  
46 46 using ParseGuard = QPDF::Doc::ParseGuard;
... ... @@ -422,7 +422,6 @@ QPDFParser::parseRemainder(bool content_stream)
422 422 frame = &stack.back();
423 423 add(std::move(object));
424 424 } else {
425   - QTC::TC("qpdf", "QPDFParser bad dictionary close in parseRemainder");
426 425 if (sanity_checks) {
427 426 // During sanity checks, assume nesting of containers is corrupt and object is
428 427 // unusable.
... ...
libqpdf/QPDFWriter.cc
... ... @@ -266,7 +266,8 @@ class QPDF::Doc::Writer
266 266 {
267 267 friend class QPDFWriter;
268 268 Writer(QPDF& pdf) :
269   - pdf(pdf)
  269 + pdf(pdf),
  270 + objects(pdf.m->objects)
270 271 {
271 272 }
272 273  
... ... @@ -306,13 +307,13 @@ class QPDF::Doc::Writer
306 307 std::vector<QPDFObjGen>
307 308 getCompressibleObjGens()
308 309 {
309   - return pdf.getCompressibleObjVector();
  310 + return objects.getCompressibleObjVector();
310 311 }
311 312  
312 313 std::vector<bool>
313 314 getCompressibleObjSet()
314 315 {
315   - return pdf.getCompressibleObjSet();
  316 + return objects.getCompressibleObjSet();
316 317 }
317 318  
318 319 std::map<QPDFObjGen, QPDFXRefEntry> const&
... ... @@ -324,10 +325,11 @@ class QPDF::Doc::Writer
324 325 size_t
325 326 tableSize()
326 327 {
327   - return pdf.tableSize();
  328 + return pdf.m->objects.tableSize();
328 329 }
329 330  
330 331 QPDF& pdf;
  332 + QPDF::Doc::Objects& objects;
331 333 };
332 334  
333 335 class QPDFWriter::Members: QPDF::Doc::Writer
... ...
libqpdf/QPDF_json.cc
... ... @@ -277,6 +277,7 @@ class QPDF::JSONReactor: public JSON::Reactor
277 277 void replaceObject(QPDFObjectHandle&& replacement, JSON const& value);
278 278  
279 279 QPDF& pdf;
  280 + QPDF::Doc::Objects& objects = pdf.m->objects;
280 281 std::shared_ptr<InputSource> is;
281 282 bool must_be_complete{true};
282 283 std::shared_ptr<QPDFObject::Description> descr;
... ... @@ -541,7 +542,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const&amp; key, JSON const&amp; value)
541 542 } else if (is_obj_key(key, obj, gen)) {
542 543 this->cur_object = key;
543 544 if (setNextStateIfDictionary(key, value, st_object_top)) {
544   - next_obj = pdf.getObjectForJSON(obj, gen);
  545 + next_obj = objects.getObjectForJSON(obj, gen);
545 546 }
546 547 } else {
547 548 QTC::TC("qpdf", "QPDF_json bad object key");
... ... @@ -743,7 +744,7 @@ QPDF::JSONReactor::makeObject(JSON const&amp; value)
743 744 int gen = 0;
744 745 std::string str;
745 746 if (is_indirect_object(str_v, obj, gen)) {
746   - result = pdf.getObjectForJSON(obj, gen);
  747 + result = objects.getObjectForJSON(obj, gen);
747 748 } else if (is_unicode_string(str_v, str)) {
748 749 result = QPDFObjectHandle::newUnicodeString(str);
749 750 } else if (is_binary_string(str_v, str)) {
... ...
libqpdf/QPDF_linearization.cc
... ... @@ -112,9 +112,9 @@ QPDF::isLinearized()
112 112 // next iteration.
113 113 m->file->seek(toO(pos), SEEK_SET);
114 114  
115   - auto t1 = readToken(*m->file, 20);
116   - if (!(t1.isInteger() && readToken(*m->file, 6).isInteger() &&
117   - readToken(*m->file, 4).isWord("obj"))) {
  115 + auto t1 = m->objects.readToken(*m->file, 20);
  116 + if (!(t1.isInteger() && m->objects.readToken(*m->file, 6).isInteger() &&
  117 + m->objects.readToken(*m->file, 4).isWord("obj"))) {
118 118 pos = buffer.find_first_not_of("0123456789"sv, pos);
119 119 if (pos == std::string::npos) {
120 120 return false;
... ... @@ -250,7 +250,7 @@ QPDF::readLinearizationData()
250 250 Dictionary
251 251 QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length)
252 252 {
253   - auto H = readObjectAtOffset(offset, "linearization hint stream", false);
  253 + auto H = m->objects.readObjectAtOffset(offset, "linearization hint stream", false);
254 254 ObjCache& oc = m->obj_cache[H];
255 255 qpdf_offset_t min_end_offset = oc.end_before_space;
256 256 qpdf_offset_t max_end_offset = oc.end_after_space;
... ...
libqpdf/QPDF_objects.cc
... ... @@ -23,6 +23,8 @@
23 23 using namespace qpdf;
24 24 using namespace std::literals;
25 25  
  26 +using Objects = QPDF::Doc::Objects;
  27 +
26 28 namespace
27 29 {
28 30 class InvalidInputSource: public InputSource
... ... @@ -102,7 +104,8 @@ class QPDF::ResolveRecorder final
102 104 bool
103 105 QPDF::findStartxref()
104 106 {
105   - if (readToken(*m->file).isWord("startxref") && readToken(*m->file).isInteger()) {
  107 + if (m->objects.readToken(*m->file).isWord("startxref") &&
  108 + m->objects.readToken(*m->file).isInteger()) {
106 109 // Position in front of offset token
107 110 m->file->seek(m->file->getLastOffset(), SEEK_SET);
108 111 return true;
... ... @@ -111,17 +114,16 @@ QPDF::findStartxref()
111 114 }
112 115  
113 116 void
114   -QPDF::parse(char const* password)
  117 +Objects::parse(char const* password)
115 118 {
116 119 if (password) {
117 120 m->encp->provided_password = password;
118 121 }
119 122  
120 123 // Find the header anywhere in the first 1024 bytes of the file.
121   - PatternFinder hf(*this, &QPDF::findHeader);
  124 + PatternFinder hf(qpdf, &QPDF::findHeader);
122 125 if (!m->file->findFirst("%PDF-", 0, 1024, hf)) {
123   - QTC::TC("qpdf", "QPDF not a pdf file");
124   - warn(damagedPDF("", -1, "can't find PDF header"));
  126 + qpdf.warn(qpdf.damagedPDF("", -1, "can't find PDF header"));
125 127 // QPDFWriter writes files that usually require at least version 1.2 for /FlateDecode
126 128 m->pdf_version = "1.2";
127 129 }
... ... @@ -137,7 +139,7 @@ QPDF::parse(char const* password)
137 139 m->xref_table_max_id = static_cast<int>(m->xref_table_max_offset / 3);
138 140 }
139 141 qpdf_offset_t start_offset = (end_offset > 1054 ? end_offset - 1054 : 0);
140   - PatternFinder sf(*this, &QPDF::findStartxref);
  142 + PatternFinder sf(qpdf, &QPDF::findStartxref);
141 143 qpdf_offset_t xref_offset = 0;
142 144 if (m->file->findLast("startxref", start_offset, 0, sf)) {
143 145 xref_offset = QUtil::string_to_ll(readToken(*m->file).getValue().c_str());
... ... @@ -145,35 +147,33 @@ QPDF::parse(char const* password)
145 147  
146 148 try {
147 149 if (xref_offset == 0) {
148   - QTC::TC("qpdf", "QPDF can't find startxref");
149   - throw damagedPDF("", -1, "can't find startxref");
  150 + throw qpdf.damagedPDF("", -1, "can't find startxref");
150 151 }
151 152 try {
152 153 read_xref(xref_offset);
153 154 } catch (QPDFExc&) {
154 155 throw;
155 156 } catch (std::exception& e) {
156   - throw damagedPDF("", -1, std::string("error reading xref: ") + e.what());
  157 + throw qpdf.damagedPDF("", -1, std::string("error reading xref: ") + e.what());
157 158 }
158 159 } catch (QPDFExc& e) {
159 160 if (m->attempt_recovery) {
160 161 reconstruct_xref(e, xref_offset > 0);
161   - QTC::TC("qpdf", "QPDF reconstructed xref table");
162 162 } else {
163 163 throw;
164 164 }
165 165 }
166 166  
167   - initializeEncryption();
  167 + qpdf.initializeEncryption();
168 168 m->parsed = true;
169   - if (!m->xref_table.empty() && !getRoot().getKey("/Pages").isDictionary()) {
  169 + if (!m->xref_table.empty() && !qpdf.getRoot().getKey("/Pages").isDictionary()) {
170 170 // QPDFs created from JSON have an empty xref table and no root object yet.
171   - throw damagedPDF("", -1, "unable to find page tree");
  171 + throw qpdf.damagedPDF("", -1, "unable to find page tree");
172 172 }
173 173 }
174 174  
175 175 void
176   -QPDF::inParse(bool v)
  176 +Objects::inParse(bool v)
177 177 {
178 178 if (m->in_parse == v) {
179 179 // This happens if QPDFParser::parse tries to resolve an indirect object while it is
... ... @@ -186,7 +186,7 @@ QPDF::inParse(bool v)
186 186 }
187 187  
188 188 void
189   -QPDF::setTrailer(QPDFObjectHandle obj)
  189 +Objects::setTrailer(QPDFObjectHandle obj)
190 190 {
191 191 if (m->trailer) {
192 192 return;
... ... @@ -195,7 +195,7 @@ QPDF::setTrailer(QPDFObjectHandle obj)
195 195 }
196 196  
197 197 void
198   -QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref)
  198 +Objects::reconstruct_xref(QPDFExc& e, bool found_startxref)
199 199 {
200 200 if (m->reconstructed_xref) {
201 201 // Avoid xref reconstruction infinite loops. This is getting very hard to reproduce because
... ... @@ -208,7 +208,8 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
208 208 const auto max_warnings = m->warnings.size() + 1000U;
209 209 auto check_warnings = [this, max_warnings]() {
210 210 if (m->warnings.size() > max_warnings) {
211   - throw damagedPDF("", -1, "too many errors while reconstructing cross-reference table");
  211 + throw qpdf.damagedPDF(
  212 + "", -1, "too many errors while reconstructing cross-reference table");
212 213 }
213 214 };
214 215  
... ... @@ -216,9 +217,9 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
216 217 // We may find more objects, which may contain dangling references.
217 218 m->fixed_dangling_refs = false;
218 219  
219   - warn(damagedPDF("", -1, "file is damaged"));
220   - warn(e);
221   - warn(damagedPDF("", -1, "Attempting to reconstruct cross-reference table"));
  220 + qpdf.warn(qpdf.damagedPDF("", -1, "file is damaged"));
  221 + qpdf.warn(e);
  222 + qpdf.warn(qpdf.damagedPDF("", -1, "Attempting to reconstruct cross-reference table"));
222 223  
223 224 // Delete all references to type 1 (uncompressed) objects
224 225 std::vector<QPDFObjGen> to_delete;
... ... @@ -241,18 +242,18 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
241 242 // Don't allow very long tokens here during recovery. All the interesting tokens are covered.
242 243 static size_t const MAX_LEN = 10;
243 244 while (m->file->tell() < eof) {
244   - QPDFTokenizer::Token t1 = readToken(*m->file, MAX_LEN);
  245 + QPDFTokenizer::Token t1 = m->objects.readToken(*m->file, MAX_LEN);
245 246 qpdf_offset_t token_start = m->file->tell() - toO(t1.getValue().length());
246 247 if (t1.isInteger()) {
247 248 auto pos = m->file->tell();
248   - auto t2 = readToken(*m->file, MAX_LEN);
249   - if (t2.isInteger() && readToken(*m->file, MAX_LEN).isWord("obj")) {
  249 + auto t2 = m->objects.readToken(*m->file, MAX_LEN);
  250 + if (t2.isInteger() && m->objects.readToken(*m->file, MAX_LEN).isWord("obj")) {
250 251 int obj = QUtil::string_to_int(t1.getValue().c_str());
251 252 int gen = QUtil::string_to_int(t2.getValue().c_str());
252 253 if (obj <= m->xref_table_max_id) {
253 254 found_objects.emplace_back(obj, gen, token_start);
254 255 } else {
255   - warn(damagedPDF(
  256 + qpdf.warn(qpdf.damagedPDF(
256 257 "", -1, "ignoring object with impossibly large id " + std::to_string(obj)));
257 258 }
258 259 }
... ... @@ -271,14 +272,15 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
271 272 auto xref_backup{m->xref_table};
272 273 try {
273 274 m->file->seek(startxrefs.back(), SEEK_SET);
274   - if (auto offset = QUtil::string_to_ll(readToken(*m->file).getValue().data())) {
275   - read_xref(offset);
  275 + if (auto offset =
  276 + QUtil::string_to_ll(m->objects.readToken(*m->file).getValue().data())) {
  277 + m->objects.read_xref(offset);
276 278  
277   - if (getRoot().getKey("/Pages").isDictionary()) {
  279 + if (qpdf.getRoot().getKey("/Pages").isDictionary()) {
278 280 QTC::TC("qpdf", "QPDF startxref more than 1024 before end");
279   - warn(damagedPDF(
  281 + qpdf.warn(qpdf.damagedPDF(
280 282 "", -1, "startxref was more than 1024 bytes before end of file"));
281   - initializeEncryption();
  283 + qpdf.initializeEncryption();
282 284 m->parsed = true;
283 285 m->reconstructed_xref = false;
284 286 return;
... ... @@ -311,7 +313,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
311 313 m->trailer = t;
312 314 break;
313 315 }
314   - warn(damagedPDF("trailer", *it, "recovered trailer has no /Root entry"));
  316 + qpdf.warn(qpdf.damagedPDF("trailer", *it, "recovered trailer has no /Root entry"));
315 317 }
316 318 check_warnings();
317 319 }
... ... @@ -325,7 +327,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
325 327 if (entry.getType() != 1) {
326 328 continue;
327 329 }
328   - auto oh = getObject(iter.first);
  330 + auto oh = qpdf.getObject(iter.first);
329 331 try {
330 332 if (!oh.isStreamOfType("/XRef")) {
331 333 continue;
... ... @@ -345,7 +347,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
345 347 try {
346 348 read_xref(max_offset, true);
347 349 } catch (std::exception&) {
348   - warn(damagedPDF(
  350 + qpdf.warn(qpdf.damagedPDF(
349 351 "", -1, "error decoding candidate xref stream while recovering damaged file"));
350 352 }
351 353 QTC::TC("qpdf", "QPDF recover xref stream");
... ... @@ -366,7 +368,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
366 368 }
367 369 if (root) {
368 370 if (!m->trailer) {
369   - warn(damagedPDF(
  371 + qpdf.warn(qpdf.damagedPDF(
370 372 "", -1, "unable to find trailer dictionary while recovering damaged file"));
371 373 m->trailer = QPDFObjectHandle::newDictionary();
372 374 }
... ... @@ -379,21 +381,22 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
379 381 // could try to get the trailer from there. This may make it possible to recover files with
380 382 // bad startxref pointers even when they have object streams.
381 383  
382   - throw damagedPDF("", -1, "unable to find trailer dictionary while recovering damaged file");
  384 + throw qpdf.damagedPDF(
  385 + "", -1, "unable to find trailer dictionary while recovering damaged file");
383 386 }
384 387 if (m->xref_table.empty()) {
385 388 // We cannot check for an empty xref table in parse because empty tables are valid when
386 389 // creating QPDF objects from JSON.
387   - throw damagedPDF("", -1, "unable to find objects while recovering damaged file");
  390 + throw qpdf.damagedPDF("", -1, "unable to find objects while recovering damaged file");
388 391 }
389 392 check_warnings();
390 393 if (!m->parsed) {
391 394 m->parsed = true;
392   - getAllPages();
  395 + qpdf.getAllPages();
393 396 check_warnings();
394 397 if (m->all_pages.empty()) {
395 398 m->parsed = false;
396   - throw damagedPDF("", -1, "unable to find any pages while recovering damaged file");
  399 + throw qpdf.damagedPDF("", -1, "unable to find any pages while recovering damaged file");
397 400 }
398 401 }
399 402  
... ... @@ -405,7 +408,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
405 408 }
406 409  
407 410 void
408   -QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
  411 +Objects::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
409 412 {
410 413 std::map<int, int> free_table;
411 414 std::set<qpdf_offset_t> visited;
... ... @@ -440,8 +443,7 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
440 443 // where it is terminated by arbitrary whitespace.
441 444 if ((strncmp(buf, "xref", 4) == 0) && util::is_space(buf[4])) {
442 445 if (skipped_space) {
443   - QTC::TC("qpdf", "QPDF xref skipped space");
444   - warn(damagedPDF("", -1, "extraneous whitespace seen before xref"));
  446 + qpdf.warn(qpdf.damagedPDF("", -1, "extraneous whitespace seen before xref"));
445 447 }
446 448 QTC::TC(
447 449 "qpdf",
... ... @@ -460,13 +462,12 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
460 462 xref_offset = read_xrefStream(xref_offset, in_stream_recovery);
461 463 }
462 464 if (visited.contains(xref_offset)) {
463   - QTC::TC("qpdf", "QPDF xref loop");
464   - throw damagedPDF("", -1, "loop detected following xref tables");
  465 + throw qpdf.damagedPDF("", -1, "loop detected following xref tables");
465 466 }
466 467 }
467 468  
468 469 if (!m->trailer) {
469   - throw damagedPDF("", -1, "unable to find trailer while reading xref");
  470 + throw qpdf.damagedPDF("", -1, "unable to find trailer while reading xref");
470 471 }
471 472 int size = m->trailer.getKey("/Size").getIntValueAsInt();
472 473 int max_obj = 0;
... ... @@ -477,8 +478,7 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
477 478 max_obj = std::max(max_obj, *(m->deleted_objects.rbegin()));
478 479 }
479 480 if ((size < 1) || (size - 1 != max_obj)) {
480   - QTC::TC("qpdf", "QPDF xref size mismatch");
481   - warn(damagedPDF(
  481 + qpdf.warn(qpdf.damagedPDF(
482 482 "",
483 483 -1,
484 484 ("reported number of objects (" + std::to_string(size) +
... ... @@ -494,14 +494,14 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
494 494 for (auto const& item: m->xref_table) {
495 495 auto id = item.first.getObj();
496 496 if (id == last_og.getObj() && id > 0) {
497   - removeObject(last_og);
  497 + qpdf.removeObject(last_og);
498 498 }
499 499 last_og = item.first;
500 500 }
501 501 }
502 502  
503 503 bool
504   -QPDF::parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes)
  504 +Objects::parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes)
505 505 {
506 506 // is_space and is_digit both return false on '\0', so this will not overrun the null-terminated
507 507 // buffer.
... ... @@ -549,7 +549,7 @@ QPDF::parse_xrefFirst(std::string const&amp; line, int&amp; obj, int&amp; num, int&amp; bytes)
549 549 }
550 550  
551 551 bool
552   -QPDF::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type)
  552 +Objects::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type)
553 553 {
554 554 // Reposition after initial read attempt and reread.
555 555 m->file->seek(m->file->getLastOffset(), SEEK_SET);
... ... @@ -563,7 +563,6 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
563 563 bool invalid = false;
564 564 while (util::is_space(*p)) {
565 565 ++p;
566   - QTC::TC("qpdf", "QPDF ignore first space in xref entry");
567 566 invalid = true;
568 567 }
569 568 // Require digit
... ... @@ -580,7 +579,6 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
580 579 return false;
581 580 }
582 581 if (util::is_space(*(p + 1))) {
583   - QTC::TC("qpdf", "QPDF ignore first extra space in xref entry");
584 582 invalid = true;
585 583 }
586 584 // Skip spaces
... ... @@ -601,7 +599,6 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
601 599 return false;
602 600 }
603 601 if (util::is_space(*(p + 1))) {
604   - QTC::TC("qpdf", "QPDF ignore second extra space in xref entry");
605 602 invalid = true;
606 603 }
607 604 // Skip spaces
... ... @@ -614,12 +611,11 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
614 611 return false;
615 612 }
616 613 if ((f1_str.length() != 10) || (f2_str.length() != 5)) {
617   - QTC::TC("qpdf", "QPDF ignore length error xref entry");
618 614 invalid = true;
619 615 }
620 616  
621 617 if (invalid) {
622   - warn(damagedPDF("xref table", "accepting invalid xref table entry"));
  618 + qpdf.warn(qpdf.damagedPDF("xref table", "accepting invalid xref table entry"));
623 619 }
624 620  
625 621 f1 = QUtil::string_to_ll(f1_str.c_str());
... ... @@ -631,7 +627,7 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
631 627 // Optimistically read and parse xref entry. If entry is bad, call read_bad_xrefEntry and return
632 628 // result.
633 629 bool
634   -QPDF::read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type)
  630 +Objects::read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type)
635 631 {
636 632 std::array<char, 21> line;
637 633 if (m->file->read(line.data(), 20) != 20) {
... ... @@ -685,7 +681,7 @@ QPDF::read_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
685 681  
686 682 // Read a single cross-reference table section and associated trailer.
687 683 qpdf_offset_t
688   -QPDF::read_xrefTable(qpdf_offset_t xref_offset)
  684 +Objects::read_xrefTable(qpdf_offset_t xref_offset)
689 685 {
690 686 m->file->seek(xref_offset, SEEK_SET);
691 687 std::string line;
... ... @@ -696,8 +692,7 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset)
696 692 int num = 0;
697 693 int bytes = 0;
698 694 if (!parse_xrefFirst(line, obj, num, bytes)) {
699   - QTC::TC("qpdf", "QPDF invalid xref");
700   - throw damagedPDF("xref table", "xref syntax invalid");
  695 + throw qpdf.damagedPDF("xref table", "xref syntax invalid");
701 696 }
702 697 m->file->seek(m->file->getLastOffset() + bytes, SEEK_SET);
703 698 for (qpdf_offset_t i = obj; i - num < obj; ++i) {
... ... @@ -710,8 +705,7 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset)
710 705 int f2 = 0;
711 706 char type = '\0';
712 707 if (!read_xrefEntry(f1, f2, type)) {
713   - QTC::TC("qpdf", "QPDF invalid xref entry");
714   - throw damagedPDF(
  708 + throw qpdf.damagedPDF(
715 709 "xref table", "invalid xref entry (obj=" + std::to_string(i) + ")");
716 710 }
717 711 if (type == 'f') {
... ... @@ -729,22 +723,19 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset)
729 723 }
730 724  
731 725 // Set offset to previous xref table if any
732   - QPDFObjectHandle cur_trailer = readTrailer();
  726 + QPDFObjectHandle cur_trailer = m->objects.readTrailer();
733 727 if (!cur_trailer.isDictionary()) {
734   - QTC::TC("qpdf", "QPDF missing trailer");
735   - throw damagedPDF("", "expected trailer dictionary");
  728 + throw qpdf.damagedPDF("", "expected trailer dictionary");
736 729 }
737 730  
738 731 if (!m->trailer) {
739 732 setTrailer(cur_trailer);
740 733  
741 734 if (!m->trailer.hasKey("/Size")) {
742   - QTC::TC("qpdf", "QPDF trailer lacks size");
743   - throw damagedPDF("trailer", "trailer dictionary lacks /Size key");
  735 + throw qpdf.damagedPDF("trailer", "trailer dictionary lacks /Size key");
744 736 }
745 737 if (!m->trailer.getKey("/Size").isInteger()) {
746   - QTC::TC("qpdf", "QPDF trailer size not integer");
747   - throw damagedPDF("trailer", "/Size key in trailer dictionary is not an integer");
  738 + throw qpdf.damagedPDF("trailer", "/Size key in trailer dictionary is not an integer");
748 739 }
749 740 }
750 741  
... ... @@ -757,17 +748,15 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset)
757 748 // /Prev key instead of the xref stream's.
758 749 (void)read_xrefStream(cur_trailer.getKey("/XRefStm").getIntValue());
759 750 } else {
760   - throw damagedPDF("xref stream", xref_offset, "invalid /XRefStm");
  751 + throw qpdf.damagedPDF("xref stream", xref_offset, "invalid /XRefStm");
761 752 }
762 753 }
763 754 }
764 755  
765 756 if (cur_trailer.hasKey("/Prev")) {
766 757 if (!cur_trailer.getKey("/Prev").isInteger()) {
767   - QTC::TC("qpdf", "QPDF trailer prev not integer");
768   - throw damagedPDF("trailer", "/Prev key in trailer dictionary is not an integer");
  758 + throw qpdf.damagedPDF("trailer", "/Prev key in trailer dictionary is not an integer");
769 759 }
770   - QTC::TC("qpdf", "QPDF prev key in trailer dictionary");
771 760 return cur_trailer.getKey("/Prev").getIntValue();
772 761 }
773 762  
... ... @@ -776,7 +765,7 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset)
776 765  
777 766 // Read a single cross-reference stream.
778 767 qpdf_offset_t
779   -QPDF::read_xrefStream(qpdf_offset_t xref_offset, bool in_stream_recovery)
  768 +Objects::read_xrefStream(qpdf_offset_t xref_offset, bool in_stream_recovery)
780 769 {
781 770 if (!m->ignore_xref_streams) {
782 771 QPDFObjectHandle xref_obj;
... ... @@ -788,19 +777,17 @@ QPDF::read_xrefStream(qpdf_offset_t xref_offset, bool in_stream_recovery)
788 777 }
789 778 m->in_read_xref_stream = false;
790 779 if (xref_obj.isStreamOfType("/XRef")) {
791   - QTC::TC("qpdf", "QPDF found xref stream");
792 780 return processXRefStream(xref_offset, xref_obj, in_stream_recovery);
793 781 }
794 782 }
795 783  
796   - QTC::TC("qpdf", "QPDF can't find xref");
797   - throw damagedPDF("", xref_offset, "xref not found");
  784 + throw qpdf.damagedPDF("", xref_offset, "xref not found");
798 785 return 0; // unreachable
799 786 }
800 787  
801 788 // Return the entry size of the xref stream and the processed W array.
802 789 std::pair<int, std::array<int, 3>>
803   -QPDF::processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged)
  790 +Objects::processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged)
804 791 {
805 792 auto W_obj = dict.getKey("/W");
806 793 if (!(W_obj.size() >= 3 && W_obj.getArrayItem(0).isInteger() &&
... ... @@ -830,7 +817,7 @@ QPDF::processXRefW(QPDFObjectHandle&amp; dict, std::function&lt;QPDFExc(std::string_vie
830 817  
831 818 // Validate Size key and return the maximum number of entries that the xref stream can contain.
832 819 int
833   -QPDF::processXRefSize(
  820 +Objects::processXRefSize(
834 821 QPDFObjectHandle& dict, int entry_size, std::function<QPDFExc(std::string_view)> damaged)
835 822 {
836 823 // Number of entries is limited by the highest possible object id and stream size.
... ... @@ -854,7 +841,7 @@ QPDF::processXRefSize(
854 841  
855 842 // Return the number of entries of the xref stream and the processed Index array.
856 843 std::pair<int, std::vector<std::pair<int, int>>>
857   -QPDF::processXRefIndex(
  844 +Objects::processXRefIndex(
858 845 QPDFObjectHandle& dict, int max_num_entries, std::function<QPDFExc(std::string_view)> damaged)
859 846 {
860 847 auto size = dict.getKey("/Size").getIntValueAsInt();
... ... @@ -921,11 +908,11 @@ QPDF::processXRefIndex(
921 908 }
922 909  
923 910 qpdf_offset_t
924   -QPDF::processXRefStream(
  911 +Objects::processXRefStream(
925 912 qpdf_offset_t xref_offset, QPDFObjectHandle& xref_obj, bool in_stream_recovery)
926 913 {
927 914 auto damaged = [this, xref_offset](std::string_view msg) -> QPDFExc {
928   - return damagedPDF("xref stream", xref_offset, msg.data());
  915 + return qpdf.damagedPDF("xref stream", xref_offset, msg.data());
929 916 };
930 917  
931 918 auto dict = xref_obj.getDict();
... ... @@ -945,7 +932,7 @@ QPDF::processXRefStream(
945 932 if (expected_size > actual_size) {
946 933 throw x;
947 934 } else {
948   - warn(x);
  935 + qpdf.warn(x);
949 936 }
950 937 }
951 938  
... ... @@ -960,7 +947,6 @@ QPDF::processXRefStream(
960 947 // Read this entry
961 948 std::array<qpdf_offset_t, 3> fields{};
962 949 if (W[0] == 0) {
963   - QTC::TC("qpdf", "QPDF default for xref stream field 0");
964 950 fields[0] = 1;
965 951 }
966 952 for (size_t j = 0; j < 3; ++j) {
... ... @@ -1006,10 +992,9 @@ QPDF::processXRefStream(
1006 992  
1007 993 if (dict.hasKey("/Prev")) {
1008 994 if (!dict.getKey("/Prev").isInteger()) {
1009   - throw damagedPDF(
  995 + throw qpdf.damagedPDF(
1010 996 "xref stream", "/Prev key in xref stream dictionary is not an integer");
1011 997 }
1012   - QTC::TC("qpdf", "QPDF prev key in xref stream dictionary");
1013 998 return dict.getKey("/Prev").getIntValue();
1014 999 } else {
1015 1000 return 0;
... ... @@ -1017,7 +1002,7 @@ QPDF::processXRefStream(
1017 1002 }
1018 1003  
1019 1004 void
1020   -QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
  1005 +Objects::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1021 1006 {
1022 1007 // Populate the xref table in such a way that the first reference to an object that we see,
1023 1008 // which is the one in the latest xref table in which it appears, is the one that gets stored.
... ... @@ -1035,25 +1020,23 @@ QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1035 1020 // We are ignoring invalid objgens. Most will arrive here from xref reconstruction. There
1036 1021 // is probably no point having another warning but we could count invalid items in order to
1037 1022 // decide when to give up.
1038   - QTC::TC("qpdf", "QPDF xref overwrite invalid objgen");
1039 1023 // ignore impossibly large object ids or object ids > Size.
1040 1024 return;
1041 1025 }
1042 1026  
1043 1027 if (m->deleted_objects.contains(obj)) {
1044   - QTC::TC("qpdf", "QPDF xref deleted object");
1045 1028 return;
1046 1029 }
1047 1030  
1048 1031 if (f0 == 2) {
1049 1032 if (f1 == obj) {
1050   - warn(
1051   - damagedPDF("xref stream", "self-referential object stream " + std::to_string(obj)));
  1033 + qpdf.warn(qpdf.damagedPDF(
  1034 + "xref stream", "self-referential object stream " + std::to_string(obj)));
1052 1035 return;
1053 1036 }
1054 1037 if (f1 > m->xref_table_max_id) {
1055 1038 // ignore impossibly large object stream ids
1056   - warn(damagedPDF(
  1039 + qpdf.warn(qpdf.damagedPDF(
1057 1040 "xref stream",
1058 1041 "object stream id " + std::to_string(f1) + " for object " + std::to_string(obj) +
1059 1042 " is impossibly large"));
... ... @@ -1063,7 +1046,6 @@ QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1063 1046  
1064 1047 auto [iter, created] = m->xref_table.try_emplace(QPDFObjGen(obj, (f0 == 2 ? 0 : f2)));
1065 1048 if (!created) {
1066   - QTC::TC("qpdf", "QPDF xref reused object");
1067 1049 return;
1068 1050 }
1069 1051  
... ... @@ -1079,13 +1061,14 @@ QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1079 1061 break;
1080 1062  
1081 1063 default:
1082   - throw damagedPDF("xref stream", "unknown xref stream entry type " + std::to_string(f0));
  1064 + throw qpdf.damagedPDF(
  1065 + "xref stream", "unknown xref stream entry type " + std::to_string(f0));
1083 1066 break;
1084 1067 }
1085 1068 }
1086 1069  
1087 1070 void
1088   -QPDF::insertFreeXrefEntry(QPDFObjGen og)
  1071 +Objects::insertFreeXrefEntry(QPDFObjGen og)
1089 1072 {
1090 1073 if (!m->xref_table.contains(og)) {
1091 1074 m->deleted_objects.insert(og.getObj());
... ... @@ -1121,7 +1104,7 @@ QPDF::showXRefTable()
1121 1104 // Resolve all objects in the xref table. If this triggers a xref table reconstruction abort and
1122 1105 // return false. Otherwise return true.
1123 1106 bool
1124   -QPDF::resolveXRefTable()
  1107 +Objects::resolveXRefTable()
1125 1108 {
1126 1109 bool may_change = !m->reconstructed_xref;
1127 1110 for (auto& iter: m->xref_table) {
... ... @@ -1143,9 +1126,8 @@ QPDF::fixDanglingReferences(bool force)
1143 1126 if (m->fixed_dangling_refs) {
1144 1127 return;
1145 1128 }
1146   - if (!resolveXRefTable()) {
1147   - QTC::TC("qpdf", "QPDF fix dangling triggered xref reconstruction");
1148   - resolveXRefTable();
  1129 + if (!m->objects.resolveXRefTable()) {
  1130 + m->objects.resolveXRefTable();
1149 1131 }
1150 1132 m->fixed_dangling_refs = true;
1151 1133 }
... ... @@ -1171,13 +1153,13 @@ QPDF::getAllObjects()
1171 1153 fixDanglingReferences();
1172 1154 std::vector<QPDFObjectHandle> result;
1173 1155 for (auto const& iter: m->obj_cache) {
1174   - result.push_back(newIndirect(iter.first, iter.second.object));
  1156 + result.emplace_back(m->objects.newIndirect(iter.first, iter.second.object));
1175 1157 }
1176 1158 return result;
1177 1159 }
1178 1160  
1179 1161 void
1180   -QPDF::setLastObjectDescription(std::string const& description, QPDFObjGen og)
  1162 +Objects::setLastObjectDescription(std::string const& description, QPDFObjGen og)
1181 1163 {
1182 1164 m->last_object_description.clear();
1183 1165 if (!description.empty()) {
... ... @@ -1192,17 +1174,17 @@ QPDF::setLastObjectDescription(std::string const&amp; description, QPDFObjGen og)
1192 1174 }
1193 1175  
1194 1176 QPDFObjectHandle
1195   -QPDF::readTrailer()
  1177 +Objects::readTrailer()
1196 1178 {
1197 1179 qpdf_offset_t offset = m->file->tell();
1198 1180 auto [object, empty] =
1199   - QPDFParser::parse(*m->file, "trailer", m->tokenizer, nullptr, *this, m->reconstructed_xref);
  1181 + QPDFParser::parse(*m->file, "trailer", m->tokenizer, nullptr, qpdf, m->reconstructed_xref);
1200 1182 if (empty) {
1201 1183 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1202 1184 // actual PDF files and Adobe Reader appears to ignore them.
1203   - warn(damagedPDF("trailer", "empty object treated as null"));
1204   - } else if (object.isDictionary() && readToken(*m->file).isWord("stream")) {
1205   - warn(damagedPDF("trailer", m->file->tell(), "stream keyword found in trailer"));
  1185 + qpdf.warn(qpdf.damagedPDF("trailer", "empty object treated as null"));
  1186 + } else if (object.isDictionary() && m->objects.readToken(*m->file).isWord("stream")) {
  1187 + qpdf.warn(qpdf.damagedPDF("trailer", m->file->tell(), "stream keyword found in trailer"));
1206 1188 }
1207 1189 // Override last_offset so that it points to the beginning of the object we just read
1208 1190 m->file->setLastOffset(offset);
... ... @@ -1210,25 +1192,26 @@ QPDF::readTrailer()
1210 1192 }
1211 1193  
1212 1194 QPDFObjectHandle
1213   -QPDF::readObject(std::string const& description, QPDFObjGen og)
  1195 +Objects::readObject(std::string const& description, QPDFObjGen og)
1214 1196 {
1215 1197 setLastObjectDescription(description, og);
1216 1198 qpdf_offset_t offset = m->file->tell();
1217 1199  
1218   - StringDecrypter decrypter{this, og};
  1200 + StringDecrypter decrypter{&qpdf, og};
1219 1201 StringDecrypter* decrypter_ptr = m->encp->encrypted ? &decrypter : nullptr;
1220 1202 auto [object, empty] = QPDFParser::parse(
1221 1203 *m->file,
1222 1204 m->last_object_description,
1223 1205 m->tokenizer,
1224 1206 decrypter_ptr,
1225   - *this,
  1207 + qpdf,
1226 1208 m->reconstructed_xref || m->in_read_xref_stream);
1227 1209 ;
1228 1210 if (empty) {
1229 1211 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1230 1212 // actual PDF files and Adobe Reader appears to ignore them.
1231   - warn(damagedPDF(*m->file, m->file->getLastOffset(), "empty object treated as null"));
  1213 + qpdf.warn(
  1214 + qpdf.damagedPDF(*m->file, m->file->getLastOffset(), "empty object treated as null"));
1232 1215 return object;
1233 1216 }
1234 1217 auto token = readToken(*m->file);
... ... @@ -1237,15 +1220,14 @@ QPDF::readObject(std::string const&amp; description, QPDFObjGen og)
1237 1220 token = readToken(*m->file);
1238 1221 }
1239 1222 if (!token.isWord("endobj")) {
1240   - QTC::TC("qpdf", "QPDF err expected endobj");
1241   - warn(damagedPDF("expected endobj"));
  1223 + qpdf.warn(qpdf.damagedPDF("expected endobj"));
1242 1224 }
1243 1225 return object;
1244 1226 }
1245 1227  
1246 1228 // After reading stream dictionary and stream keyword, read rest of stream.
1247 1229 void
1248   -QPDF::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset)
  1230 +Objects::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset)
1249 1231 {
1250 1232 validateStreamLineEnd(object, og, offset);
1251 1233  
... ... @@ -1259,9 +1241,9 @@ QPDF::readStream(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset_t offset)
1259 1241  
1260 1242 if (!length_obj.isInteger()) {
1261 1243 if (length_obj.null()) {
1262   - throw damagedPDF(offset, "stream dictionary lacks /Length key");
  1244 + throw qpdf.damagedPDF(offset, "stream dictionary lacks /Length key");
1263 1245 }
1264   - throw damagedPDF(offset, "/Length key in stream dictionary is not an integer");
  1246 + throw qpdf.damagedPDF(offset, "/Length key in stream dictionary is not an integer");
1265 1247 }
1266 1248  
1267 1249 length = toS(length_obj.getUIntValue());
... ... @@ -1269,21 +1251,21 @@ QPDF::readStream(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset_t offset)
1269 1251 m->file->seek(stream_offset, SEEK_SET);
1270 1252 m->file->seek(toO(length), SEEK_CUR);
1271 1253 if (!readToken(*m->file).isWord("endstream")) {
1272   - throw damagedPDF("expected endstream");
  1254 + throw qpdf.damagedPDF("expected endstream");
1273 1255 }
1274 1256 } catch (QPDFExc& e) {
1275 1257 if (m->attempt_recovery) {
1276   - warn(e);
  1258 + qpdf.warn(e);
1277 1259 length = recoverStreamLength(m->file, og, stream_offset);
1278 1260 } else {
1279 1261 throw;
1280 1262 }
1281 1263 }
1282   - object = QPDFObjectHandle(qpdf::Stream(*this, og, object, stream_offset, length));
  1264 + object = QPDFObjectHandle(qpdf::Stream(qpdf, og, object, stream_offset, length));
1283 1265 }
1284 1266  
1285 1267 void
1286   -QPDF::validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset)
  1268 +Objects::validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset)
1287 1269 {
1288 1270 // The PDF specification states that the word "stream" should be followed by either a carriage
1289 1271 // return and a newline or by a newline alone. It specifically disallowed following it by a
... ... @@ -1301,7 +1283,6 @@ QPDF::validateStreamLineEnd(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset
1301 1283 }
1302 1284 if (ch == '\n') {
1303 1285 // ready to read stream data
1304   - QTC::TC("qpdf", "QPDF stream with NL only");
1305 1286 return;
1306 1287 }
1307 1288 if (ch == '\r') {
... ... @@ -1313,33 +1294,32 @@ QPDF::validateStreamLineEnd(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset
1313 1294 } else {
1314 1295 // Treat the \r by itself as the whitespace after endstream and start reading
1315 1296 // stream data in spite of not having seen a newline.
1316   - QTC::TC("qpdf", "QPDF stream with CR only");
1317 1297 m->file->unreadCh(ch);
1318   - warn(damagedPDF(
  1298 + qpdf.warn(qpdf.damagedPDF(
1319 1299 m->file->tell(), "stream keyword followed by carriage return only"));
1320 1300 }
1321 1301 }
1322 1302 return;
1323 1303 }
1324 1304 if (!util::is_space(ch)) {
1325   - QTC::TC("qpdf", "QPDF stream without newline");
1326 1305 m->file->unreadCh(ch);
1327   - warn(damagedPDF(
  1306 + qpdf.warn(qpdf.damagedPDF(
1328 1307 m->file->tell(), "stream keyword not followed by proper line terminator"));
1329 1308 return;
1330 1309 }
1331   - warn(damagedPDF(m->file->tell(), "stream keyword followed by extraneous whitespace"));
  1310 + qpdf.warn(
  1311 + qpdf.damagedPDF(m->file->tell(), "stream keyword followed by extraneous whitespace"));
1332 1312 }
1333 1313 }
1334 1314  
1335 1315 QPDFObjectHandle
1336   -QPDF::readObjectInStream(is::OffsetBuffer& input, int stream_id, int obj_id)
  1316 +Objects::readObjectInStream(is::OffsetBuffer& input, int stream_id, int obj_id)
1337 1317 {
1338   - auto [object, empty] = QPDFParser::parse(input, stream_id, obj_id, m->tokenizer, *this);
  1318 + auto [object, empty] = QPDFParser::parse(input, stream_id, obj_id, m->tokenizer, qpdf);
1339 1319 if (empty) {
1340 1320 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1341 1321 // actual PDF files and Adobe Reader appears to ignore them.
1342   - warn(QPDFExc(
  1322 + qpdf.warn(QPDFExc(
1343 1323 qpdf_e_damaged_pdf,
1344 1324 m->file->getName() + " object stream " + std::to_string(stream_id),
1345 1325 +"object " + std::to_string(obj_id) + " 0, offset " +
... ... @@ -1354,7 +1334,7 @@ bool
1354 1334 QPDF::findEndstream()
1355 1335 {
1356 1336 // Find endstream or endobj. Position the input at that token.
1357   - auto t = readToken(*m->file, 20);
  1337 + auto t = m->objects.readToken(*m->file, 20);
1358 1338 if (t.isWord("endobj") || t.isWord("endstream")) {
1359 1339 m->file->seek(m->file->getLastOffset(), SEEK_SET);
1360 1340 return true;
... ... @@ -1363,13 +1343,13 @@ QPDF::findEndstream()
1363 1343 }
1364 1344  
1365 1345 size_t
1366   -QPDF::recoverStreamLength(
  1346 +Objects::recoverStreamLength(
1367 1347 std::shared_ptr<InputSource> input, QPDFObjGen og, qpdf_offset_t stream_offset)
1368 1348 {
1369 1349 // Try to reconstruct stream length by looking for endstream or endobj
1370   - warn(damagedPDF(*input, stream_offset, "attempting to recover stream length"));
  1350 + qpdf.warn(qpdf.damagedPDF(*input, stream_offset, "attempting to recover stream length"));
1371 1351  
1372   - PatternFinder ef(*this, &QPDF::findEndstream);
  1352 + PatternFinder ef(qpdf, &QPDF::findEndstream);
1373 1353 size_t length = 0;
1374 1354 if (m->file->findFirst("end", stream_offset, 0, ef)) {
1375 1355 length = toS(m->file->tell() - stream_offset);
... ... @@ -1401,65 +1381,58 @@ QPDF::recoverStreamLength(
1401 1381 // found endstream\nendobj within the space allowed for this object, so we're probably
1402 1382 // in good shape.
1403 1383 } else {
1404   - QTC::TC("qpdf", "QPDF found wrong endstream in recovery");
1405 1384 length = 0;
1406 1385 }
1407 1386 }
1408 1387  
1409 1388 if (length == 0) {
1410   - warn(damagedPDF(
  1389 + qpdf.warn(qpdf.damagedPDF(
1411 1390 *input, stream_offset, "unable to recover stream data; treating stream as empty"));
1412 1391 } else {
1413   - warn(damagedPDF(
  1392 + qpdf.warn(qpdf.damagedPDF(
1414 1393 *input, stream_offset, "recovered stream length: " + std::to_string(length)));
1415 1394 }
1416 1395  
1417   - QTC::TC("qpdf", "QPDF recovered stream length");
1418 1396 return length;
1419 1397 }
1420 1398  
1421 1399 QPDFTokenizer::Token
1422   -QPDF::readToken(InputSource& input, size_t max_len)
  1400 +Objects::readToken(InputSource& input, size_t max_len)
1423 1401 {
1424 1402 return m->tokenizer.readToken(input, m->last_object_description, true, max_len);
1425 1403 }
1426 1404  
1427 1405 QPDFObjGen
1428   -QPDF::read_object_start(qpdf_offset_t offset)
  1406 +Objects::read_object_start(qpdf_offset_t offset)
1429 1407 {
1430 1408 m->file->seek(offset, SEEK_SET);
1431 1409 QPDFTokenizer::Token tobjid = readToken(*m->file);
1432 1410 bool objidok = tobjid.isInteger();
1433   - QTC::TC("qpdf", "QPDF check objid", objidok ? 1 : 0);
1434 1411 if (!objidok) {
1435   - QTC::TC("qpdf", "QPDF expected n n obj");
1436   - throw damagedPDF(offset, "expected n n obj");
  1412 + throw qpdf.damagedPDF(offset, "expected n n obj");
1437 1413 }
1438 1414 QPDFTokenizer::Token tgen = readToken(*m->file);
1439 1415 bool genok = tgen.isInteger();
1440   - QTC::TC("qpdf", "QPDF check generation", genok ? 1 : 0);
1441 1416 if (!genok) {
1442   - throw damagedPDF(offset, "expected n n obj");
  1417 + throw qpdf.damagedPDF(offset, "expected n n obj");
1443 1418 }
1444 1419 QPDFTokenizer::Token tobj = readToken(*m->file);
1445 1420  
1446 1421 bool objok = tobj.isWord("obj");
1447   - QTC::TC("qpdf", "QPDF check obj", objok ? 1 : 0);
1448 1422  
1449 1423 if (!objok) {
1450   - throw damagedPDF(offset, "expected n n obj");
  1424 + throw qpdf.damagedPDF(offset, "expected n n obj");
1451 1425 }
1452 1426 int objid = QUtil::string_to_int(tobjid.getValue().c_str());
1453 1427 int generation = QUtil::string_to_int(tgen.getValue().c_str());
1454 1428 if (objid == 0) {
1455   - QTC::TC("qpdf", "QPDF object id 0");
1456   - throw damagedPDF(offset, "object with ID 0");
  1429 + throw qpdf.damagedPDF(offset, "object with ID 0");
1457 1430 }
1458 1431 return {objid, generation};
1459 1432 }
1460 1433  
1461 1434 void
1462   -QPDF::readObjectAtOffset(
  1435 +Objects::readObjectAtOffset(
1463 1436 bool try_recovery, qpdf_offset_t offset, std::string const& description, QPDFObjGen exp_og)
1464 1437 {
1465 1438 QPDFObjGen og;
... ... @@ -1474,22 +1447,20 @@ QPDF::readObjectAtOffset(
1474 1447 // "0000000000 00000 n", which is not correct, but it won't hurt anything for us to ignore
1475 1448 // these.
1476 1449 if (offset == 0) {
1477   - QTC::TC("qpdf", "QPDF bogus 0 offset", 0);
1478   - warn(damagedPDF(-1, "object has offset 0"));
  1450 + qpdf.warn(qpdf.damagedPDF(-1, "object has offset 0"));
1479 1451 return;
1480 1452 }
1481 1453  
1482 1454 try {
1483 1455 og = read_object_start(offset);
1484 1456 if (exp_og != og) {
1485   - QTC::TC("qpdf", "QPDF err wrong objid/generation");
1486   - QPDFExc e = damagedPDF(offset, "expected " + exp_og.unparse(' ') + " obj");
  1457 + QPDFExc e = qpdf.damagedPDF(offset, "expected " + exp_og.unparse(' ') + " obj");
1487 1458 if (try_recovery) {
1488 1459 // Will be retried below
1489 1460 throw e;
1490 1461 } else {
1491 1462 // We can try reading the object anyway even if the ID doesn't match.
1492   - warn(e);
  1463 + qpdf.warn(e);
1493 1464 }
1494 1465 }
1495 1466 } catch (QPDFExc& e) {
... ... @@ -1501,11 +1472,9 @@ QPDF::readObjectAtOffset(
1501 1472 if (m->xref_table.contains(exp_og) && m->xref_table[exp_og].getType() == 1) {
1502 1473 qpdf_offset_t new_offset = m->xref_table[exp_og].getOffset();
1503 1474 readObjectAtOffset(false, new_offset, description, exp_og);
1504   - QTC::TC("qpdf", "QPDF recovered in readObjectAtOffset");
1505 1475 return;
1506 1476 }
1507   - QTC::TC("qpdf", "QPDF object gone after xref reconstruction");
1508   - warn(damagedPDF(
  1477 + qpdf.warn(qpdf.damagedPDF(
1509 1478 "",
1510 1479 -1,
1511 1480 ("object " + exp_og.unparse(' ') +
... ... @@ -1524,24 +1493,24 @@ QPDF::readObjectAtOffset(
1524 1493 while (true) {
1525 1494 char ch;
1526 1495 if (!m->file->read(&ch, 1)) {
1527   - throw damagedPDF(m->file->tell(), "EOF after endobj");
  1496 + throw qpdf.damagedPDF(m->file->tell(), "EOF after endobj");
1528 1497 }
1529 1498 if (!isspace(static_cast<unsigned char>(ch))) {
1530 1499 m->file->seek(-1, SEEK_CUR);
1531 1500 break;
1532 1501 }
1533 1502 }
1534   - updateCache(og, oh.getObj(), end_before_space, m->file->tell());
  1503 + m->objects.updateCache(og, oh.getObj(), end_before_space, m->file->tell());
1535 1504 }
1536 1505  
1537 1506 QPDFObjectHandle
1538   -QPDF::readObjectAtOffset(
  1507 +Objects::readObjectAtOffset(
1539 1508 qpdf_offset_t offset, std::string const& description, bool skip_cache_if_in_xref)
1540 1509 {
1541 1510 auto og = read_object_start(offset);
1542 1511 auto oh = readObject(description, og);
1543 1512  
1544   - if (!isUnresolved(og)) {
  1513 + if (!m->objects.isUnresolved(og)) {
1545 1514 return oh;
1546 1515 }
1547 1516  
... ... @@ -1583,20 +1552,20 @@ QPDF::readObjectAtOffset(
1583 1552 while (true) {
1584 1553 char ch;
1585 1554 if (!m->file->read(&ch, 1)) {
1586   - throw damagedPDF(m->file->tell(), "EOF after endobj");
  1555 + throw qpdf.damagedPDF(m->file->tell(), "EOF after endobj");
1587 1556 }
1588 1557 if (!isspace(static_cast<unsigned char>(ch))) {
1589 1558 m->file->seek(-1, SEEK_CUR);
1590 1559 break;
1591 1560 }
1592 1561 }
1593   - updateCache(og, oh.getObj(), end_before_space, m->file->tell());
  1562 + m->objects.updateCache(og, oh.getObj(), end_before_space, m->file->tell());
1594 1563  
1595 1564 return oh;
1596 1565 }
1597 1566  
1598 1567 std::shared_ptr<QPDFObject> const&
1599   -QPDF::resolve(QPDFObjGen og)
  1568 +Objects::resolve(QPDFObjGen og)
1600 1569 {
1601 1570 if (!isUnresolved(og)) {
1602 1571 return m->obj_cache[og].object;
... ... @@ -1605,12 +1574,11 @@ QPDF::resolve(QPDFObjGen og)
1605 1574 if (m->resolving.contains(og)) {
1606 1575 // This can happen if an object references itself directly or indirectly in some key that
1607 1576 // has to be resolved during object parsing, such as stream length.
1608   - QTC::TC("qpdf", "QPDF recursion loop in resolve");
1609   - warn(damagedPDF("", "loop detected resolving object " + og.unparse(' ')));
  1577 + qpdf.warn(qpdf.damagedPDF("", "loop detected resolving object " + og.unparse(' ')));
1610 1578 updateCache(og, QPDFObject::create<QPDF_Null>(), -1, -1);
1611 1579 return m->obj_cache[og].object;
1612 1580 }
1613   - ResolveRecorder rr(*this, og);
  1581 + ResolveRecorder rr(qpdf, og);
1614 1582  
1615 1583 if (m->xref_table.contains(og)) {
1616 1584 QPDFXRefEntry const& entry = m->xref_table[og];
... ... @@ -1626,30 +1594,29 @@ QPDF::resolve(QPDFObjGen og)
1626 1594 break;
1627 1595  
1628 1596 default:
1629   - throw damagedPDF(
  1597 + throw qpdf.damagedPDF(
1630 1598 "", -1, ("object " + og.unparse('/') + " has unexpected xref entry type"));
1631 1599 }
1632 1600 } catch (QPDFExc& e) {
1633   - warn(e);
  1601 + qpdf.warn(e);
1634 1602 } catch (std::exception& e) {
1635   - warn(damagedPDF(
  1603 + qpdf.warn(qpdf.damagedPDF(
1636 1604 "", -1, ("object " + og.unparse('/') + ": error reading object: " + e.what())));
1637 1605 }
1638 1606 }
1639 1607  
1640 1608 if (isUnresolved(og)) {
1641 1609 // PDF spec says unknown objects resolve to the null object.
1642   - QTC::TC("qpdf", "QPDF resolve failure to null");
1643 1610 updateCache(og, QPDFObject::create<QPDF_Null>(), -1, -1);
1644 1611 }
1645 1612  
1646 1613 auto& result(m->obj_cache[og].object);
1647   - result->setDefaultDescription(this, og);
  1614 + result->setDefaultDescription(&qpdf, og);
1648 1615 return result;
1649 1616 }
1650 1617  
1651 1618 void
1652   -QPDF::resolveObjectsInStream(int obj_stream_number)
  1619 +Objects::resolveObjectsInStream(int obj_stream_number)
1653 1620 {
1654 1621 auto damaged =
1655 1622 [this, obj_stream_number](int id, qpdf_offset_t offset, std::string const& msg) -> QPDFExc {
... ... @@ -1667,9 +1634,9 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1667 1634 }
1668 1635 m->resolved_object_streams.insert(obj_stream_number);
1669 1636 // Force resolution of object stream
1670   - auto obj_stream = getObject(obj_stream_number, 0).as_stream();
  1637 + auto obj_stream = qpdf.getObject(obj_stream_number, 0).as_stream();
1671 1638 if (!obj_stream) {
1672   - throw damagedPDF(
  1639 + throw qpdf.damagedPDF(
1673 1640 "object " + std::to_string(obj_stream_number) + " 0",
1674 1641 "supposed object stream " + std::to_string(obj_stream_number) + " is not a stream");
1675 1642 }
... ... @@ -1682,8 +1649,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1682 1649  
1683 1650 QPDFObjectHandle dict = obj_stream.getDict();
1684 1651 if (!dict.isDictionaryOfType("/ObjStm")) {
1685   - QTC::TC("qpdf", "QPDF ERR object stream with wrong type");
1686   - warn(damagedPDF(
  1652 + qpdf.warn(qpdf.damagedPDF(
1687 1653 "object " + std::to_string(obj_stream_number) + " 0",
1688 1654 "supposed object stream " + std::to_string(obj_stream_number) + " has wrong type"));
1689 1655 }
... ... @@ -1691,7 +1657,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1691 1657 unsigned int n{0};
1692 1658 int first{0};
1693 1659 if (!(dict.getKey("/N").getValueAsUInt(n) && dict.getKey("/First").getValueAsInt(first))) {
1694   - throw damagedPDF(
  1660 + throw qpdf.damagedPDF(
1695 1661 "object " + std::to_string(obj_stream_number) + " 0",
1696 1662 "object stream " + std::to_string(obj_stream_number) + " has incorrect keys");
1697 1663 }
... ... @@ -1708,7 +1674,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1708 1674 auto b_start = stream_data.data();
1709 1675  
1710 1676 if (first >= end_offset) {
1711   - throw damagedPDF(
  1677 + throw qpdf.damagedPDF(
1712 1678 "object " + std::to_string(obj_stream_number) + " 0",
1713 1679 "object stream " + std::to_string(obj_stream_number) + " has invalid /First entry");
1714 1680 }
... ... @@ -1728,20 +1694,17 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1728 1694 long long offset = QUtil::string_to_int(toffset.getValue().c_str());
1729 1695  
1730 1696 if (num == obj_stream_number) {
1731   - QTC::TC("qpdf", "QPDF ignore self-referential object stream");
1732   - warn(damaged(num, id_offset, "object stream claims to contain itself"));
  1697 + qpdf.warn(damaged(num, id_offset, "object stream claims to contain itself"));
1733 1698 continue;
1734 1699 }
1735 1700  
1736 1701 if (num < 1) {
1737   - QTC::TC("qpdf", "QPDF object stream contains id < 1");
1738   - warn(damaged(num, id_offset, "object id is invalid"s));
  1702 + qpdf.warn(damaged(num, id_offset, "object id is invalid"s));
1739 1703 continue;
1740 1704 }
1741 1705  
1742 1706 if (offset <= last_offset) {
1743   - QTC::TC("qpdf", "QPDF object stream offsets not increasing");
1744   - warn(damaged(
  1707 + qpdf.warn(damaged(
1745 1708 num,
1746 1709 input.getLastOffset(),
1747 1710 "offset " + std::to_string(offset) +
... ... @@ -1755,7 +1718,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1755 1718 }
1756 1719  
1757 1720 if (first + offset >= end_offset) {
1758   - warn(damaged(
  1721 + qpdf.warn(damaged(
1759 1722 num, input.getLastOffset(), "offset " + std::to_string(offset) + " is too large"));
1760 1723 continue;
1761 1724 }
... ... @@ -1796,21 +1759,21 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1796 1759 }
1797 1760  
1798 1761 QPDFObjectHandle
1799   -QPDF::newIndirect(QPDFObjGen og, std::shared_ptr<QPDFObject> const& obj)
  1762 +Objects::newIndirect(QPDFObjGen og, std::shared_ptr<QPDFObject> const& obj)
1800 1763 {
1801   - obj->setDefaultDescription(this, og);
  1764 + obj->setDefaultDescription(&qpdf, og);
1802 1765 return {obj};
1803 1766 }
1804 1767  
1805 1768 void
1806   -QPDF::updateCache(
  1769 +Objects::updateCache(
1807 1770 QPDFObjGen og,
1808 1771 std::shared_ptr<QPDFObject> const& object,
1809 1772 qpdf_offset_t end_before_space,
1810 1773 qpdf_offset_t end_after_space,
1811 1774 bool destroy)
1812 1775 {
1813   - object->setObjGen(this, og);
  1776 + object->setObjGen(&qpdf, og);
1814 1777 if (isCached(og)) {
1815 1778 auto& cache = m->obj_cache[og];
1816 1779 object->move_to(cache.object, destroy);
... ... @@ -1822,21 +1785,21 @@ QPDF::updateCache(
1822 1785 }
1823 1786  
1824 1787 bool
1825   -QPDF::isCached(QPDFObjGen og)
  1788 +Objects::isCached(QPDFObjGen og)
1826 1789 {
1827 1790 return m->obj_cache.contains(og);
1828 1791 }
1829 1792  
1830 1793 bool
1831   -QPDF::isUnresolved(QPDFObjGen og)
  1794 +Objects::isUnresolved(QPDFObjGen og)
1832 1795 {
1833 1796 return !isCached(og) || m->obj_cache[og].object->isUnresolved();
1834 1797 }
1835 1798  
1836 1799 QPDFObjGen
1837   -QPDF::nextObjGen()
  1800 +Objects::nextObjGen()
1838 1801 {
1839   - int max_objid = toI(getObjectCount());
  1802 + int max_objid = toI(qpdf.getObjectCount());
1840 1803 if (max_objid == std::numeric_limits<int>::max()) {
1841 1804 throw std::range_error("max object id is too high to create new objects");
1842 1805 }
... ... @@ -1844,7 +1807,7 @@ QPDF::nextObjGen()
1844 1807 }
1845 1808  
1846 1809 QPDFObjectHandle
1847   -QPDF::makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj)
  1810 +Objects::makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj)
1848 1811 {
1849 1812 QPDFObjGen next{nextObjGen()};
1850 1813 m->obj_cache[next] = ObjCache(obj, -1, -1);
... ... @@ -1857,11 +1820,11 @@ QPDF::makeIndirectObject(QPDFObjectHandle oh)
1857 1820 if (!oh) {
1858 1821 throw std::logic_error("attempted to make an uninitialized QPDFObjectHandle indirect");
1859 1822 }
1860   - return makeIndirectFromQPDFObject(oh.getObj());
  1823 + return m->objects.makeIndirectFromQPDFObject(oh.getObj());
1861 1824 }
1862 1825  
1863 1826 std::shared_ptr<QPDFObject>
1864   -QPDF::getObjectForParser(int id, int gen, bool parse_pdf)
  1827 +Objects::getObjectForParser(int id, int gen, bool parse_pdf)
1865 1828 {
1866 1829 // This method is called by the parser and therefore must not resolve any objects.
1867 1830 auto og = QPDFObjGen(id, gen);
... ... @@ -1869,25 +1832,25 @@ QPDF::getObjectForParser(int id, int gen, bool parse_pdf)
1869 1832 return iter->second.object;
1870 1833 }
1871 1834 if (m->xref_table.contains(og) || (!m->parsed && og.getObj() < m->xref_table_max_id)) {
1872   - return m->obj_cache.insert({og, QPDFObject::create<QPDF_Unresolved>(this, og)})
  1835 + return m->obj_cache.insert({og, QPDFObject::create<QPDF_Unresolved>(&qpdf, og)})
1873 1836 .first->second.object;
1874 1837 }
1875 1838 if (parse_pdf) {
1876 1839 return QPDFObject::create<QPDF_Null>();
1877 1840 }
1878   - return m->obj_cache.insert({og, QPDFObject::create<QPDF_Null>(this, og)}).first->second.object;
  1841 + return m->obj_cache.insert({og, QPDFObject::create<QPDF_Null>(&qpdf, og)}).first->second.object;
1879 1842 }
1880 1843  
1881 1844 std::shared_ptr<QPDFObject>
1882   -QPDF::getObjectForJSON(int id, int gen)
  1845 +Objects::getObjectForJSON(int id, int gen)
1883 1846 {
1884 1847 auto og = QPDFObjGen(id, gen);
1885 1848 auto [it, inserted] = m->obj_cache.try_emplace(og);
1886 1849 auto& obj = it->second.object;
1887 1850 if (inserted) {
1888 1851 obj = (m->parsed && !m->xref_table.contains(og))
1889   - ? QPDFObject::create<QPDF_Null>(this, og)
1890   - : QPDFObject::create<QPDF_Unresolved>(this, og);
  1852 + ? QPDFObject::create<QPDF_Null>(&qpdf, og)
  1853 + : QPDFObject::create<QPDF_Unresolved>(&qpdf, og);
1891 1854 }
1892 1855 return obj;
1893 1856 }
... ... @@ -1916,10 +1879,9 @@ void
1916 1879 QPDF::replaceObject(QPDFObjGen og, QPDFObjectHandle oh)
1917 1880 {
1918 1881 if (!oh || (oh.isIndirect() && !(oh.isStream() && oh.getObjGen() == og))) {
1919   - QTC::TC("qpdf", "QPDF replaceObject called with indirect object");
1920 1882 throw std::logic_error("QPDF::replaceObject called with indirect object handle");
1921 1883 }
1922   - updateCache(og, oh.getObj(), -1, -1, false);
  1884 + m->objects.updateCache(og, oh.getObj(), -1, -1, false);
1923 1885 }
1924 1886  
1925 1887 void
... ... @@ -1955,13 +1917,13 @@ void
1955 1917 QPDF::swapObjects(QPDFObjGen og1, QPDFObjGen og2)
1956 1918 {
1957 1919 // Force objects to be read from the input source if needed, then swap them in the cache.
1958   - resolve(og1);
1959   - resolve(og2);
  1920 + m->objects.resolve(og1);
  1921 + m->objects.resolve(og2);
1960 1922 m->obj_cache[og1].object->swapWith(m->obj_cache[og2].object);
1961 1923 }
1962 1924  
1963 1925 size_t
1964   -QPDF::tableSize()
  1926 +Objects::tableSize()
1965 1927 {
1966 1928 // If obj_cache is dense, accommodate all object in tables,else accommodate only original
1967 1929 // objects.
... ... @@ -1972,7 +1934,7 @@ QPDF::tableSize()
1972 1934 // Temporary fix. Long-term solution is
1973 1935 // - QPDFObjGen to enforce objgens are valid and sensible
1974 1936 // - xref table and obj cache to protect against insertion of impossibly large obj ids
1975   - stopOnError("Impossibly large object id encountered.");
  1937 + qpdf.stopOnError("Impossibly large object id encountered.");
1976 1938 }
1977 1939 if (max_obj < 1.1 * std::max(toI(m->obj_cache.size()), max_xref)) {
1978 1940 return toS(++max_obj);
... ... @@ -1981,20 +1943,20 @@ QPDF::tableSize()
1981 1943 }
1982 1944  
1983 1945 std::vector<QPDFObjGen>
1984   -QPDF::getCompressibleObjVector()
  1946 +Objects::getCompressibleObjVector()
1985 1947 {
1986 1948 return getCompressibleObjGens<QPDFObjGen>();
1987 1949 }
1988 1950  
1989 1951 std::vector<bool>
1990   -QPDF::getCompressibleObjSet()
  1952 +Objects::getCompressibleObjSet()
1991 1953 {
1992 1954 return getCompressibleObjGens<bool>();
1993 1955 }
1994 1956  
1995 1957 template <typename T>
1996 1958 std::vector<T>
1997   -QPDF::getCompressibleObjGens()
  1959 +Objects::getCompressibleObjGens()
1998 1960 {
1999 1961 // Return a list of objects that are allowed to be in object streams. Walk through the objects
2000 1962 // by traversing the document from the root, including a traversal of the pages tree. This
... ... @@ -2006,11 +1968,11 @@ QPDF::getCompressibleObjGens()
2006 1968 QPDFObjectHandle encryption_dict = m->trailer.getKey("/Encrypt");
2007 1969 QPDFObjGen encryption_dict_og = encryption_dict.getObjGen();
2008 1970  
2009   - const size_t max_obj = getObjectCount();
  1971 + const size_t max_obj = qpdf.getObjectCount();
2010 1972 std::vector<bool> visited(max_obj, false);
2011 1973 std::vector<QPDFObjectHandle> queue;
2012 1974 queue.reserve(512);
2013   - queue.push_back(m->trailer);
  1975 + queue.emplace_back(m->trailer);
2014 1976 std::vector<T> result;
2015 1977 if constexpr (std::is_same_v<T, QPDFObjGen>) {
2016 1978 result.reserve(m->obj_cache.size());
... ... @@ -2030,7 +1992,6 @@ QPDF::getCompressibleObjGens()
2030 1992 "unexpected object id encountered in getCompressibleObjGens");
2031 1993 }
2032 1994 if (visited[id]) {
2033   - QTC::TC("qpdf", "QPDF loop detected traversing objects");
2034 1995 continue;
2035 1996 }
2036 1997  
... ... @@ -2039,7 +2000,7 @@ QPDF::getCompressibleObjGens()
2039 2000 // in the queue.
2040 2001 auto upper = m->obj_cache.upper_bound(og);
2041 2002 if (upper != m->obj_cache.end() && upper->first.getObj() == og.getObj()) {
2042   - removeObject(og);
  2003 + qpdf.removeObject(og);
2043 2004 continue;
2044 2005 }
2045 2006  
... ...
libqpdf/QPDF_optimization.cc
... ... @@ -220,8 +220,7 @@ QPDF::pushInheritedAttributesToPageInternal(
220 220 // Warn when flattening, but not if the key is at the top level (i.e. "/Parent" not
221 221 // set), as we don't change these; but flattening removes intermediate /Pages nodes.
222 222 if ((warn_skipped_keys) && (cur_pages.hasKey("/Parent"))) {
223   - QTC::TC("qpdf", "QPDF unknown key not inherited");
224   - setLastObjectDescription("Pages object", cur_pages.getObjGen());
  223 + m->objects.setLastObjectDescription("Pages object", cur_pages.getObjGen());
225 224 warn(
226 225 qpdf_e_pages,
227 226 m->last_object_description,
... ...
libqpdf/QPDF_pages.cc
... ... @@ -288,7 +288,8 @@ QPDF::insertPageobjToPage(QPDFObjectHandle const&amp; obj, int pos, bool check_dupli
288 288 if (check_duplicate) {
289 289 if (!m->pageobj_to_pages_pos.insert(std::make_pair(og, pos)).second) {
290 290 // The library never calls insertPageobjToPage in a way that causes this to happen.
291   - setLastObjectDescription("page " + std::to_string(pos) + " (numbered from zero)", og);
  291 + m->objects.setLastObjectDescription(
  292 + "page " + std::to_string(pos) + " (numbered from zero)", og);
292 293 throw QPDFExc(
293 294 qpdf_e_pages,
294 295 m->file->getName(),
... ... @@ -406,8 +407,7 @@ QPDF::findPage(QPDFObjGen og)
406 407 flattenPagesTree();
407 408 auto it = m->pageobj_to_pages_pos.find(og);
408 409 if (it == m->pageobj_to_pages_pos.end()) {
409   - QTC::TC("qpdf", "QPDF_pages findPage not found");
410   - setLastObjectDescription("page object", og);
  410 + m->objects.setLastObjectDescription("page object", og);
411 411 throw QPDFExc(
412 412 qpdf_e_pages,
413 413 m->file->getName(),
... ...
libqpdf/qpdf/QPDF_private.hh
... ... @@ -453,7 +453,99 @@ class QPDF::Doc
453 453 std::string Perms;
454 454 std::string id1;
455 455 bool encrypt_metadata;
456   - };
  456 + }; // class Encryption
  457 +
  458 + class Objects
  459 + {
  460 + public:
  461 + Objects() = delete;
  462 + Objects(Objects const&) = delete;
  463 + Objects(Objects&&) = delete;
  464 + Objects& operator=(Objects const&) = delete;
  465 + Objects& operator=(Objects&&) = delete;
  466 + ~Objects() = default;
  467 +
  468 + Objects(QPDF& qpdf, QPDF::Members* m) :
  469 + qpdf(qpdf),
  470 + m(m)
  471 + {
  472 + }
  473 +
  474 + void parse(char const* password);
  475 + std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og);
  476 + void inParse(bool);
  477 + QPDFObjGen nextObjGen();
  478 + QPDFObjectHandle newIndirect(QPDFObjGen, std::shared_ptr<QPDFObject> const&);
  479 + void updateCache(
  480 + QPDFObjGen og,
  481 + std::shared_ptr<QPDFObject> const& object,
  482 + qpdf_offset_t end_before_space,
  483 + qpdf_offset_t end_after_space,
  484 + bool destroy = true);
  485 + bool resolveXRefTable();
  486 + QPDFObjectHandle readObjectAtOffset(
  487 + qpdf_offset_t offset, std::string const& description, bool skip_cache_if_in_xref);
  488 + QPDFTokenizer::Token readToken(InputSource& input, size_t max_len = 0);
  489 + QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj);
  490 + std::shared_ptr<QPDFObject> getObjectForParser(int id, int gen, bool parse_pdf);
  491 + std::shared_ptr<QPDFObject> getObjectForJSON(int id, int gen);
  492 + size_t tableSize();
  493 + void setLastObjectDescription(std::string const& description, QPDFObjGen og);
  494 +
  495 + // For QPDFWriter:
  496 +
  497 + // Get a list of objects that would be permitted in an object stream.
  498 + template <typename T>
  499 + std::vector<T> getCompressibleObjGens();
  500 + std::vector<QPDFObjGen> getCompressibleObjVector();
  501 + std::vector<bool> getCompressibleObjSet();
  502 +
  503 + private:
  504 + void setTrailer(QPDFObjectHandle obj);
  505 + void reconstruct_xref(QPDFExc& e, bool found_startxref = true);
  506 + void read_xref(qpdf_offset_t offset, bool in_stream_recovery = false);
  507 + bool parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes);
  508 + bool read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
  509 + bool read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
  510 + qpdf_offset_t read_xrefTable(qpdf_offset_t offset);
  511 + qpdf_offset_t read_xrefStream(qpdf_offset_t offset, bool in_stream_recovery = false);
  512 + qpdf_offset_t processXRefStream(
  513 + qpdf_offset_t offset, QPDFObjectHandle& xref_stream, bool in_stream_recovery = false);
  514 + std::pair<int, std::array<int, 3>>
  515 + processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged);
  516 + int processXRefSize(
  517 + QPDFObjectHandle& dict,
  518 + int entry_size,
  519 + std::function<QPDFExc(std::string_view)> damaged);
  520 + std::pair<int, std::vector<std::pair<int, int>>> processXRefIndex(
  521 + QPDFObjectHandle& dict,
  522 + int max_num_entries,
  523 + std::function<QPDFExc(std::string_view)> damaged);
  524 + void insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2);
  525 + void insertFreeXrefEntry(QPDFObjGen);
  526 + QPDFObjectHandle readTrailer();
  527 + QPDFObjectHandle readObject(std::string const& description, QPDFObjGen og);
  528 + void readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
  529 + void validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
  530 + QPDFObjectHandle
  531 + readObjectInStream(qpdf::is::OffsetBuffer& input, int stream_id, int obj_id);
  532 + size_t recoverStreamLength(
  533 + std::shared_ptr<InputSource> input, QPDFObjGen og, qpdf_offset_t stream_offset);
  534 +
  535 + QPDFObjGen read_object_start(qpdf_offset_t offset);
  536 + void readObjectAtOffset(
  537 + bool attempt_recovery,
  538 + qpdf_offset_t offset,
  539 + std::string const& description,
  540 + QPDFObjGen exp_og);
  541 + void resolveObjectsInStream(int obj_stream_number);
  542 + bool isCached(QPDFObjGen og);
  543 + bool isUnresolved(QPDFObjGen og);
  544 +
  545 + private:
  546 + QPDF& qpdf;
  547 + QPDF::Members* m;
  548 + }; // class Objects
457 549  
458 550 // StreamCopier class is restricted to QPDFObjectHandle so it can copy stream data.
459 551 class StreamCopier
... ... @@ -477,10 +569,17 @@ class QPDF::Doc
477 569  
478 570 Doc(QPDF& qpdf, QPDF::Members& m) :
479 571 qpdf(qpdf),
480   - m(m)
  572 + m(m),
  573 + objects_(qpdf, &m)
481 574 {
482 575 }
483 576  
  577 + Objects&
  578 + objects()
  579 + {
  580 + return objects_;
  581 + };
  582 +
484 583 bool reconstructed_xref() const;
485 584  
486 585 QPDFAcroFormDocumentHelper&
... ... @@ -532,6 +631,8 @@ class QPDF::Doc
532 631 QPDF& qpdf;
533 632 QPDF::Members& m;
534 633  
  634 + Objects objects_;
  635 +
535 636 // Document Helpers;
536 637 std::unique_ptr<QPDFAcroFormDocumentHelper> acroform_;
537 638 std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files_;
... ... @@ -552,6 +653,7 @@ class QPDF::Members
552 653  
553 654 private:
554 655 Doc doc;
  656 + Doc::Objects& objects;
555 657 std::shared_ptr<QPDFLogger> log;
556 658 unsigned long long unique_id{0};
557 659 qpdf::Tokenizer tokenizer;
... ... @@ -637,7 +739,7 @@ class QPDF::Doc::Resolver
637 739 static std::shared_ptr<QPDFObject> const&
638 740 resolved(QPDF* qpdf, QPDFObjGen og)
639 741 {
640   - return qpdf->resolve(og);
  742 + return qpdf->m->objects.resolve(og);
641 743 }
642 744 };
643 745  
... ...
qpdf/qpdf.testcov
1 1 ignored-scope: libtests
2   -QPDF err expected endobj 0
3   -QPDF err wrong objid/generation 0
4   -QPDF check objid 1
5   -QPDF check generation 1
6   -QPDF check obj 1
7   -QPDF object stream offsets not increasing 0
8   -QPDF ignore self-referential object stream 0
9   -QPDF object stream contains id < 1 0
10 2 QPDF hint table length direct 0
11 3 QPDF P absent in lindict 1
12   -QPDF expected n n obj 0
13 4 QPDF opt direct pages resource 1
14 5 QPDF opt inheritable keys 0
15 6 QPDF opt no inheritable keys 0
... ... @@ -40,25 +31,13 @@ main QTest stream 0
40 31 QPDF lin write nshared_total > nshared_first_page 1
41 32 QPDFWriter encrypted hint stream 0
42 33 QPDF opt inherited scalar 0
43   -QPDF xref reused object 0
44 34 QPDF xref gen > 0 1
45   -QPDF xref size mismatch 0
46   -QPDF not a pdf file 0
47   -QPDF can't find startxref 0
48 35 QPDF startxref more than 1024 before end 0
49   -QPDF invalid xref 0
50   -QPDF invalid xref entry 0
51   -QPDF missing trailer 0
52   -QPDF trailer lacks size 0
53   -QPDF trailer size not integer 0
54   -QPDF trailer prev not integer 0
55 36 QPDFParser bad brace 0
56 37 QPDFParser bad brace in parseRemainder 0
57 38 QPDFParser bad array close 0
58 39 QPDFParser bad array close in parseRemainder 0
59 40 QPDFParser bad dictionary close 0
60   -QPDFParser bad dictionary close in parseRemainder 0
61   -QPDF can't find xref 0
62 41 QPDFTokenizer bad ) 0
63 42 QPDFTokenizer bad > 0
64 43 QPDFTokenizer bad hexstring character 0
... ... @@ -69,26 +48,15 @@ QPDFTokenizer bad name 2 0
69 48 QPDF UseOutlines but no Outlines 0
70 49 QPDFObjectHandle makeDirect loop 0
71 50 QPDFObjectHandle copy stream 1
72   -QPDF default for xref stream field 0 0
73   -QPDF prev key in xref stream dictionary 0
74   -QPDF prev key in trailer dictionary 0
75   -QPDF found xref stream 0
76 51 QPDF ignoring XRefStm in trailer 0
77   -QPDF xref deleted object 0
78 52 SF_FlateLzwDecode PNG filter 0
79 53 QPDF xref /Index is array 1
80 54 QPDFWriter encrypt object stream 0
81 55 QPDF exclude indirect length 0
82 56 QPDF exclude encryption dictionary 0
83   -QPDF loop detected traversing objects 0
84   -QPDF reconstructed xref table 0
85   -QPDF recovered in readObjectAtOffset 0
86   -QPDF recovered stream length 0
87   -QPDF found wrong endstream in recovery 0
88 57 QPDF_Stream pipeStreamData with null pipeline 0
89 58 QPDFJob unable to filter 0
90 59 QUtil non-trivial UTF-16 0
91   -QPDF xref overwrite invalid objgen 0
92 60 QPDF decoding error warning 0
93 61 qpdf-c called qpdf_init 0
94 62 qpdf-c called qpdf_cleanup 0
... ... @@ -133,8 +101,6 @@ QPDF_encryption aes decode string 0
133 101 QPDFWriter forced version disabled encryption 0
134 102 qpdf-c called qpdf_set_r4_encryption_parameters_insecure 0
135 103 qpdf-c called qpdf_set_static_aes_IV 0
136   -QPDF ERR object stream with wrong type 0
137   -QPDF object gone after xref reconstruction 0
138 104 qpdf-c called qpdf_has_error 0
139 105 qpdf-c called qpdf_get_qpdf_version 0
140 106 QPDF_Stream pipe original stream data 0
... ... @@ -148,11 +114,7 @@ QPDFObjectHandle append page contents 0
148 114 QPDF_Stream getRawStreamData 0
149 115 QPDF_Stream getStreamData 0
150 116 qpdf-c called qpdf_read_memory 0
151   -QPDF stream without newline 0
152   -QPDF stream with CR only 0
153 117 QPDF stream with CRNL 0
154   -QPDF stream with NL only 0
155   -QPDF replaceObject called with indirect object 0
156 118 QPDFWriter copy encrypt metadata 1
157 119 qpdf-c get_info_key 1
158 120 qpdf-c set_info_key to value 0
... ... @@ -170,7 +132,6 @@ QPDF insert non-indirect page 0
170 132 QPDF insert indirect page 0
171 133 QPDF_Stream ERR shallow copy stream 0
172 134 QPDFObjectHandle newStream with string 0
173   -QPDF unknown key not inherited 0
174 135 QPDF_Stream provider length not provided 0
175 136 QPDF_Stream unknown stream length 0
176 137 QPDF replaceReserved 0
... ... @@ -188,7 +149,6 @@ QPDFObjectHandle trailing data in parse 0
188 149 QPDFTokenizer EOF reading token 0
189 150 QPDFTokenizer EOF reading appendable token 0
190 151 QPDFWriter extra header text no newline 0
191   -QPDF bogus 0 offset 0
192 152 QPDF global offset 0
193 153 QPDFWriter make Extensions direct 0
194 154 QPDFWriter make ADBE direct 1
... ... @@ -209,22 +169,15 @@ QPDFWriter standard deterministic ID 1
209 169 QPDFWriter linearized deterministic ID 1
210 170 qpdf-c called qpdf_set_deterministic_ID 0
211 171 QPDFParser invalid objgen 0
212   -QPDF object id 0 0
213   -QPDF recursion loop in resolve 0
214 172 QPDFParser treat word as string 0
215 173 QPDFParser treat word as string in parseRemainder 0
216 174 QPDFParser found fake 1
217 175 QPDFParser no val for last key 0
218   -QPDF resolve failure to null 0
219 176 QPDFObjectHandle errors in parsecontent 0
220 177 QPDFJob split-pages %d 0
221 178 QPDFJob split-pages .pdf 0
222 179 QPDFJob split-pages other 0
223 180 QPDFTokenizer allowing bad token 0
224   -QPDF ignore first space in xref entry 0
225   -QPDF ignore first extra space in xref entry 0
226   -QPDF ignore second extra space in xref entry 0
227   -QPDF ignore length error xref entry 0
228 181 QPDF_encryption pad short parameter 0
229 182 QPDFObjectHandle found old angle 1
230 183 QPDFTokenizer block long token 0
... ... @@ -261,7 +214,6 @@ QPDFObjectHandle dictionary ignoring replaceKey 0
261 214 QPDFObjectHandle numeric non-numeric 0
262 215 QPDFObjectHandle erase array bounds 0
263 216 qpdf-c called qpdf_check_pdf 0
264   -QPDF xref loop 0
265 217 QPDFParser too deep 0
266 218 QPDFFormFieldObjectHelper TU present 0
267 219 QPDFFormFieldObjectHelper TM present 0
... ... @@ -341,7 +293,6 @@ QPDFPageObjectHelper externalize inline image 0
341 293 QPDFPageObjectHelper keep inline image 0
342 294 QPDFJob image optimize colorspace 0
343 295 QPDFJob image optimize bits per component 0
344   -QPDF xref skipped space 0
345 296 QPDF eof skipping spaces before xref 1
346 297 QPDF_encryption user matches owner V < 5 0
347 298 QPDF_encryption same password 1
... ... @@ -453,7 +404,6 @@ QPDFJob copy fields not this file 0
453 404 QPDFJob copy fields non-first from orig 0
454 405 QPDF resolve duplicated page in insert 0
455 406 QPDFWriter exclude from object stream 0
456   -QPDF_pages findPage not found 0
457 407 QPDFJob weak crypto error 0
458 408 qpdf-c called qpdf_oh_is_initialized 0
459 409 qpdf-c registered progress reporter 0
... ... @@ -533,7 +483,6 @@ QPDF_json bad calledgetallpages 0
533 483 QPDF_json bad pushedinheritedpageresources 0
534 484 QPDFPageObjectHelper used fallback without copying 0
535 485 QPDF skipping cache for known unchecked object 0
536   -QPDF fix dangling triggered xref reconstruction 0
537 486 QPDF recover xref stream 0
538 487 QPDFJob json over/under no file 0
539 488 QPDF_Array copy 1
... ...