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