Commit 6324d43835f27ac3f8d53a9692582ef9a3e28836

Authored by m-holger
Committed by GitHub
2 parents afb09d2e a8e30477

Merge pull request #1555 from m-holger/qpdf_hh

Remove implementation detail from QPDF header file
include/qpdf/QPDF.hh
... ... @@ -46,27 +46,7 @@
46 46 #include <qpdf/QPDFWriter.hh>
47 47 #include <qpdf/QPDFXRefEntry.hh>
48 48  
49   -namespace qpdf
50   -{
51   - class Dictionary;
52   -
53   - namespace is
54   - {
55   - class OffsetBuffer;
56   - }
57   -} // namespace qpdf
58   -
59   -class QPDF_Stream;
60   -class BitStream;
61   -class BitWriter;
62   -class BufferInputSource;
63 49 class QPDFLogger;
64   -class QPDFParser;
65   -class QPDFAcroFormDocumentHelper;
66   -class QPDFEmbeddedFileDocumentHelper;
67   -class QPDFOutlineDocumentHelper;
68   -class QPDFPageDocumentHelper;
69   -class QPDFPageLabelDocumentHelper;
70 50  
71 51 class QPDF
72 52 {
... ... @@ -477,7 +457,7 @@ class QPDF
477 457 V(V),
478 458 R(R),
479 459 Length_bytes(Length_bytes),
480   - P(static_cast<unsigned long long>(P)),
  460 + P(P),
481 461 O(O),
482 462 U(U),
483 463 OE(OE),
... ... @@ -487,20 +467,11 @@ class QPDF
487 467 encrypt_metadata(encrypt_metadata)
488 468 {
489 469 }
490   - EncryptionData(int V, int R, int Length_bytes, bool encrypt_metadata) :
491   - V(V),
492   - R(R),
493   - Length_bytes(Length_bytes),
494   - encrypt_metadata(encrypt_metadata)
495   - {
496   - }
497 470  
498 471 int getV() const;
499 472 int getR() const;
500 473 int getLengthBytes() const;
501 474 int getP() const;
502   - // Bits in P are numbered from 1 as in the PDF spec.
503   - bool getP(size_t bit) const;
504 475 std::string const& getO() const;
505 476 std::string const& getU() const;
506 477 std::string const& getOE() const;
... ... @@ -508,12 +479,9 @@ class QPDF
508 479 std::string const& getPerms() const;
509 480 std::string const& getId1() const;
510 481 bool getEncryptMetadata() const;
511   - // Bits in P are numbered from 1 as in the PDF spec.
512   - void setP(size_t bit, bool val);
513   - void setP(unsigned long val);
  482 +
514 483 void setO(std::string const&);
515 484 void setU(std::string const&);
516   - void setId1(std::string const& val);
517 485 void setV5EncryptionParameters(
518 486 std::string const& O,
519 487 std::string const& OE,
... ... @@ -521,51 +489,14 @@ class QPDF
521 489 std::string const& UE,
522 490 std::string const& Perms);
523 491  
524   - std::string compute_encryption_key(std::string const& password) const;
525   -
526   - bool
527   - check_owner_password(std::string& user_password, std::string const& owner_password) const;
528   -
529   - bool check_user_password(std::string const& user_password) const;
530   -
531   - std::string
532   - recover_encryption_key_with_password(std::string const& password, bool& perms_valid) const;
533   -
534   - void compute_encryption_O_U(char const* user_password, char const* owner_password);
535   -
536   - std::string
537   - compute_encryption_parameters_V5(char const* user_password, char const* owner_password);
538   -
539   - std::string compute_parameters(char const* user_password, char const* owner_password);
540   -
541 492 private:
542   - static constexpr unsigned int OU_key_bytes_V4 = 16; // ( == sizeof(MD5::Digest)
543   -
544 493 EncryptionData(EncryptionData const&) = delete;
545 494 EncryptionData& operator=(EncryptionData const&) = delete;
546 495  
547   - std::string hash_V5(
548   - std::string const& password, std::string const& salt, std::string const& udata) const;
549   - std::string
550   - compute_O_value(std::string const& user_password, std::string const& owner_password) const;
551   - std::string compute_U_value(std::string const& user_password) const;
552   - std::string compute_encryption_key_from_password(std::string const& password) const;
553   - std::string recover_encryption_key_with_password(std::string const& password) const;
554   - bool check_owner_password_V4(
555   - std::string& user_password, std::string const& owner_password) const;
556   - bool check_owner_password_V5(std::string const& owner_passworda) const;
557   - std::string compute_Perms_value_V5_clear() const;
558   - std::string compute_O_rc4_key(
559   - std::string const& user_password, std::string const& owner_password) const;
560   - std::string compute_U_value_R2(std::string const& user_password) const;
561   - std::string compute_U_value_R3(std::string const& user_password) const;
562   - bool check_user_password_V4(std::string const& user_password) const;
563   - bool check_user_password_V5(std::string const& user_password) const;
564   -
565 496 int V;
566 497 int R;
567 498 int Length_bytes;
568   - std::bitset<32> P{0xfffffffc}; // Specification always requires bits 1 and 2 to be cleared.
  499 + int P;
569 500 std::string O;
570 501 std::string U;
571 502 std::string OE;
... ... @@ -574,7 +505,6 @@ class QPDF
574 505 std::string id1;
575 506 bool encrypt_metadata;
576 507 };
577   -
578 508 QPDF_DLL
579 509 bool isEncrypted() const;
580 510  
... ... @@ -794,19 +724,9 @@ class QPDF
794 724  
795 725 // End of the public API. The following classes and methods are for qpdf internal use only.
796 726  
797   - class Writer;
798   - class Resolver;
799   - class StreamCopier;
800   - class ParseGuard;
801   - class Pipe;
802   - class JobSetter;
  727 + class Doc;
803 728  
804   - inline bool reconstructed_xref() const;
805   - inline QPDFAcroFormDocumentHelper& acroform();
806   - inline QPDFEmbeddedFileDocumentHelper& embedded_files();
807   - inline QPDFOutlineDocumentHelper& outlines();
808   - inline QPDFPageDocumentHelper& pages();
809   - inline QPDFPageLabelDocumentHelper& page_labels();
  729 + inline Doc& doc();
810 730  
811 731 // For testing only -- do not add to DLL
812 732 static bool test_json_validators();
... ... @@ -830,66 +750,10 @@ class QPDF
830 750 class ResolveRecorder;
831 751 class JSONReactor;
832 752  
833   - void parse(char const* password);
834   - void inParse(bool);
835   - void setTrailer(QPDFObjectHandle obj);
836   - void read_xref(qpdf_offset_t offset, bool in_stream_recovery = false);
837   - bool resolveXRefTable();
838   - void reconstruct_xref(QPDFExc& e, bool found_startxref = true);
839   - bool parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes);
840   - bool read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
841   - bool read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
842   - qpdf_offset_t read_xrefTable(qpdf_offset_t offset);
843   - qpdf_offset_t read_xrefStream(qpdf_offset_t offset, bool in_stream_recovery = false);
844   - qpdf_offset_t processXRefStream(
845   - qpdf_offset_t offset, QPDFObjectHandle& xref_stream, bool in_stream_recovery = false);
846   - std::pair<int, std::array<int, 3>>
847   - processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged);
848   - int processXRefSize(
849   - QPDFObjectHandle& dict, int entry_size, std::function<QPDFExc(std::string_view)> damaged);
850   - std::pair<int, std::vector<std::pair<int, int>>> processXRefIndex(
851   - QPDFObjectHandle& dict,
852   - int max_num_entries,
853   - std::function<QPDFExc(std::string_view)> damaged);
854   - void insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2);
855   - void insertFreeXrefEntry(QPDFObjGen);
856   - void setLastObjectDescription(std::string const& description, QPDFObjGen og);
857   - QPDFObjectHandle readTrailer();
858   - QPDFObjectHandle readObject(std::string const& description, QPDFObjGen og);
859   - void readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
860   - void validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
861   - QPDFObjectHandle readObjectInStream(qpdf::is::OffsetBuffer& input, int stream_id, int obj_id);
862   - size_t recoverStreamLength(
863   - std::shared_ptr<InputSource> input, QPDFObjGen og, qpdf_offset_t stream_offset);
864   - QPDFTokenizer::Token readToken(InputSource&, size_t max_len = 0);
865   -
866   - QPDFObjGen read_object_start(qpdf_offset_t offset);
867   - void readObjectAtOffset(
868   - bool attempt_recovery,
869   - qpdf_offset_t offset,
870   - std::string const& description,
871   - QPDFObjGen exp_og);
872   - QPDFObjectHandle readObjectAtOffset(
873   - qpdf_offset_t offset, std::string const& description, bool skip_cache_if_in_xref);
874   - std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og);
875   - void resolveObjectsInStream(int obj_stream_number);
876 753 void stopOnError(std::string const& message);
877 754 inline void
878 755 no_ci_stop_if(bool condition, std::string const& message, std::string const& context = {});
879   - QPDFObjGen nextObjGen();
880   - QPDFObjectHandle newIndirect(QPDFObjGen, std::shared_ptr<QPDFObject> const&);
881   - QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj);
882   - bool isCached(QPDFObjGen og);
883   - bool isUnresolved(QPDFObjGen og);
884   - std::shared_ptr<QPDFObject> getObjectForParser(int id, int gen, bool parse_pdf);
885   - std::shared_ptr<QPDFObject> getObjectForJSON(int id, int gen);
886 756 void removeObject(QPDFObjGen og);
887   - void updateCache(
888   - QPDFObjGen og,
889   - std::shared_ptr<QPDFObject> const& object,
890   - qpdf_offset_t end_before_space,
891   - qpdf_offset_t end_after_space,
892   - bool destroy = true);
893 757 static QPDFExc damagedPDF(
894 758 InputSource& input,
895 759 std::string const& object,
... ... @@ -929,34 +793,6 @@ class QPDF
929 793 // For QPDFWriter:
930 794  
931 795 std::map<QPDFObjGen, QPDFXRefEntry> const& getXRefTableInternal();
932   - template <typename T>
933   - void optimize_internal(
934   - T const& object_stream_data,
935   - bool allow_changes = true,
936   - std::function<int(QPDFObjectHandle&)> skip_stream_parameters = nullptr);
937   - void optimize(
938   - QPDFWriter::ObjTable const& obj,
939   - std::function<int(QPDFObjectHandle&)> skip_stream_parameters);
940   - size_t tableSize();
941   -
942   - // Get lists of all objects in order according to the part of a linearized file that they belong
943   - // to.
944   - void getLinearizedParts(
945   - QPDFWriter::ObjTable const& obj,
946   - std::vector<QPDFObjectHandle>& part4,
947   - std::vector<QPDFObjectHandle>& part6,
948   - std::vector<QPDFObjectHandle>& part7,
949   - std::vector<QPDFObjectHandle>& part8,
950   - std::vector<QPDFObjectHandle>& part9);
951   -
952   - void generateHintStream(
953   - QPDFWriter::NewObjTable const& new_obj,
954   - QPDFWriter::ObjTable const& obj,
955   - std::string& hint_stream,
956   - int& S,
957   - int& O,
958   - bool compressed);
959   -
960 796 // Get a list of objects that would be permitted in an object stream.
961 797 template <typename T>
962 798 std::vector<T> getCompressibleObjGens();
... ... @@ -1016,66 +852,6 @@ class QPDF
1016 852 bool findStartxref();
1017 853 bool findEndstream();
1018 854  
1019   - // methods to support linearization checking -- implemented in QPDF_linearization.cc
1020   - void readLinearizationData();
1021   - void checkLinearizationInternal();
1022   - void dumpLinearizationDataInternal();
1023   - void linearizationWarning(std::string_view);
1024   - qpdf::Dictionary readHintStream(Pipeline&, qpdf_offset_t offset, size_t length);
1025   - void readHPageOffset(BitStream);
1026   - void readHSharedObject(BitStream);
1027   - void readHGeneric(BitStream, HGeneric&);
1028   - qpdf_offset_t maxEnd(ObjUser const& ou);
1029   - qpdf_offset_t getLinearizationOffset(QPDFObjGen);
1030   - QPDFObjectHandle
1031   - getUncompressedObject(QPDFObjectHandle&, std::map<int, int> const& object_stream_data);
1032   - QPDFObjectHandle getUncompressedObject(QPDFObjectHandle&, QPDFWriter::ObjTable const& obj);
1033   - int lengthNextN(int first_object, int n);
1034   - void
1035   - checkHPageOffset(std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& idx_to_obj);
1036   - void
1037   - checkHSharedObject(std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& idx_to_obj);
1038   - void checkHOutlines();
1039   - void dumpHPageOffset();
1040   - void dumpHSharedObject();
1041   - void dumpHGeneric(HGeneric&);
1042   - qpdf_offset_t adjusted_offset(qpdf_offset_t offset);
1043   - template <typename T>
1044   - void calculateLinearizationData(T const& object_stream_data);
1045   - template <typename T>
1046   - void pushOutlinesToPart(
1047   - std::vector<QPDFObjectHandle>& part,
1048   - std::set<QPDFObjGen>& lc_outlines,
1049   - T const& object_stream_data);
1050   - int outputLengthNextN(
1051   - int in_object,
1052   - int n,
1053   - QPDFWriter::NewObjTable const& new_obj,
1054   - QPDFWriter::ObjTable const& obj);
1055   - void
1056   - calculateHPageOffset(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
1057   - void
1058   - calculateHSharedObject(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
1059   - void calculateHOutline(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
1060   - void writeHPageOffset(BitWriter&);
1061   - void writeHSharedObject(BitWriter&);
1062   - void writeHGeneric(BitWriter&, HGeneric&);
1063   -
1064   - // Methods to support optimization
1065   -
1066   - void pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys);
1067   - void pushInheritedAttributesToPageInternal(
1068   - QPDFObjectHandle,
1069   - std::map<std::string, std::vector<QPDFObjectHandle>>&,
1070   - bool allow_changes,
1071   - bool warn_skipped_keys);
1072   - void updateObjectMaps(
1073   - ObjUser const& ou,
1074   - QPDFObjectHandle oh,
1075   - std::function<int(QPDFObjectHandle&)> skip_stream_parameters);
1076   - void filterCompressedObjects(std::map<int, int> const& object_stream_data);
1077   - void filterCompressedObjects(QPDFWriter::ObjTable const& object_stream_data);
1078   -
1079 855 // JSON import
1080 856 void importJSON(std::shared_ptr<InputSource>, bool must_be_complete);
1081 857  
... ...
libqpdf/NNTree.cc
... ... @@ -30,7 +30,7 @@ void
30 30 NNTreeImpl::warn(QPDFObjectHandle const& node, std::string const& msg)
31 31 {
32 32 qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg);
33   - if (++error_count > 5 && qpdf.reconstructed_xref()) {
  33 + if (++error_count > 5 && qpdf.doc().reconstructed_xref()) {
34 34 error(node, "too many errors - giving up");
35 35 }
36 36 }
... ...
libqpdf/QPDF.cc
... ... @@ -178,7 +178,11 @@ QPDF::QPDFVersion()
178 178 return QPDF::qpdf_version;
179 179 }
180 180  
181   -QPDF::Members::Members() :
  181 +QPDF::Members::Members(QPDF& qpdf) :
  182 + doc(qpdf, *this),
  183 + lin(doc.linearization()),
  184 + objects(doc.objects()),
  185 + pages(doc.pages()),
182 186 log(QPDFLogger::defaultLogger()),
183 187 file(new InvalidInputSource()),
184 188 encp(new EncryptionParameters)
... ... @@ -186,7 +190,7 @@ QPDF::Members::Members() :
186 190 }
187 191  
188 192 QPDF::QPDF() :
189   - m(std::make_unique<Members>())
  193 + m(std::make_unique<Members>(*this))
190 194 {
191 195 m->tokenizer.allowEOF();
192 196 // Generate a unique ID. It just has to be unique among all QPDF objects allocated throughout
... ... @@ -266,7 +270,7 @@ void
266 270 QPDF::processInputSource(std::shared_ptr<InputSource> source, char const* password)
267 271 {
268 272 m->file = source;
269   - parse(password);
  273 + m->objects.parse(password);
270 274 }
271 275  
272 276 void
... ... @@ -434,20 +438,20 @@ QPDF::warn(
434 438 QPDFObjectHandle
435 439 QPDF::newReserved()
436 440 {
437   - return makeIndirectFromQPDFObject(QPDFObject::create<QPDF_Reserved>());
  441 + return m->objects.makeIndirectFromQPDFObject(QPDFObject::create<QPDF_Reserved>());
438 442 }
439 443  
440 444 QPDFObjectHandle
441 445 QPDF::newIndirectNull()
442 446 {
443   - return makeIndirectFromQPDFObject(QPDFObject::create<QPDF_Null>());
  447 + return m->objects.makeIndirectFromQPDFObject(QPDFObject::create<QPDF_Null>());
444 448 }
445 449  
446 450 QPDFObjectHandle
447 451 QPDF::newStream()
448 452 {
449 453 return makeIndirectObject(
450   - qpdf::Stream(*this, nextObjGen(), QPDFObjectHandle::newDictionary(), 0, 0));
  454 + qpdf::Stream(*this, m->objects.nextObjGen(), Dictionary::empty(), 0, 0));
451 455 }
452 456  
453 457 QPDFObjectHandle
... ...
libqpdf/QPDFAcroFormDocumentHelper.cc
... ... @@ -45,7 +45,7 @@ QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF&amp; qpdf) :
45 45 QPDFAcroFormDocumentHelper&
46 46 QPDFAcroFormDocumentHelper::get(QPDF& qpdf)
47 47 {
48   - return qpdf.acroform();
  48 + return qpdf.doc().acroform();
49 49 }
50 50  
51 51 void
... ...
libqpdf/QPDFEmbeddedFileDocumentHelper.cc
... ... @@ -53,7 +53,7 @@ QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF&amp; qpdf) :
53 53 QPDFEmbeddedFileDocumentHelper&
54 54 QPDFEmbeddedFileDocumentHelper::get(QPDF& qpdf)
55 55 {
56   - return qpdf.embedded_files();
  56 + return qpdf.doc().embedded_files();
57 57 }
58 58  
59 59 void
... ...
libqpdf/QPDFFormFieldObjectHelper.cc
... ... @@ -337,7 +337,7 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances)
337 337 QPDF& qpdf = oh().getQPDF(
338 338 "QPDFFormFieldObjectHelper::setV called with need_appearances = "
339 339 "true on an object that is not associated with an owning QPDF");
340   - qpdf.acroform().setNeedAppearances(true);
  340 + qpdf.doc().acroform().setNeedAppearances(true);
341 341 }
342 342 }
343 343  
... ...
libqpdf/QPDFJob.cc
... ... @@ -30,6 +30,18 @@
30 30  
31 31 using namespace qpdf;
32 32  
  33 +// JobSetter class is restricted to QPDFJob.
  34 +class QPDF::Doc::JobSetter
  35 +{
  36 + public:
  37 + // Enable enhanced warnings for pdf file checking.
  38 + static void
  39 + setCheckMode(QPDF& qpdf, bool val)
  40 + {
  41 + qpdf.m->check_mode = val;
  42 + }
  43 +};
  44 +
33 45 namespace
34 46 {
35 47 class ImageOptimizer final: public QPDFObjectHandle::StreamDataProvider
... ... @@ -734,7 +746,7 @@ QPDFJob::doCheck(QPDF&amp; pdf)
734 746 bool okay = true;
735 747 auto& cout = *m->log->getInfo();
736 748 cout << "checking " << m->infile_name() << "\n";
737   - QPDF::JobSetter::setCheckMode(pdf, true);
  749 + QPDF::Doc::JobSetter::setCheckMode(pdf, true);
738 750 try {
739 751 int extension_level = pdf.getExtensionLevel();
740 752 cout << "PDF Version: " << pdf.getPDFVersion();
... ... @@ -751,12 +763,13 @@ QPDFJob::doCheck(QPDF&amp; pdf)
751 763 }
752 764  
753 765 // Create all document helper to trigger any validations they carry out.
754   - auto& pages = pdf.pages();
755   - (void)pdf.acroform();
756   - (void)pdf.embedded_files();
757   - (void)pdf.page_labels();
758   - (void)pdf.outlines().resolveNamedDest(QPDFObjectHandle::newString("dummy"));
759   - (void)pdf.outlines().getOutlinesForPage(pages.getAllPages().at(0));
  766 + auto& doc = pdf.doc();
  767 + auto& pages = doc.page_dh();
  768 + (void)doc.acroform();
  769 + (void)doc.embedded_files();
  770 + (void)doc.page_labels();
  771 + (void)doc.outlines().resolveNamedDest(QPDFObjectHandle::newString("dummy"));
  772 + (void)doc.outlines().getOutlinesForPage(pages.getAllPages().at(0));
760 773  
761 774 // Write the file to nowhere, uncompressing streams. This causes full file traversal and
762 775 // decoding of all streams we can decode.
... ... @@ -839,8 +852,8 @@ QPDFJob::doShowPages(QPDF&amp; pdf)
839 852 {
840 853 int pageno = 0;
841 854 auto& cout = *m->log->getInfo();
842   - for (auto& ph: pdf.pages().getAllPages()) {
843   - QPDFObjectHandle page = ph.getObjectHandle();
  855 + for (auto& page: pdf.getAllPages()) {
  856 + QPDFPageObjectHelper ph(page);
844 857 ++pageno;
845 858  
846 859 cout << "page " << pageno << ": " << page.getObjectID() << " " << page.getGeneration()
... ... @@ -871,7 +884,7 @@ QPDFJob::doShowPages(QPDF&amp; pdf)
871 884 void
872 885 QPDFJob::doListAttachments(QPDF& pdf)
873 886 {
874   - auto& efdh = pdf.embedded_files();
  887 + auto& efdh = pdf.doc().embedded_files();
875 888 if (efdh.hasEmbeddedFiles()) {
876 889 for (auto const& i: efdh.getEmbeddedFiles()) {
877 890 std::string const& key = i.first;
... ... @@ -911,7 +924,7 @@ QPDFJob::doListAttachments(QPDF&amp; pdf)
911 924 void
912 925 QPDFJob::doShowAttachment(QPDF& pdf)
913 926 {
914   - auto& efdh = pdf.embedded_files();
  927 + auto& efdh = pdf.doc().embedded_files();
915 928 auto fs = efdh.getEmbeddedFile(m->attachment_to_show);
916 929 if (!fs) {
917 930 throw std::runtime_error("attachment " + m->attachment_to_show + " not found");
... ... @@ -1030,13 +1043,13 @@ QPDFJob::doJSONPages(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1030 1043 JSON::writeDictionaryKey(p, first, "pages", 1);
1031 1044 bool first_page = true;
1032 1045 JSON::writeArrayOpen(p, first_page, 2);
1033   - auto& pldh = pdf.page_labels();
1034   - auto& odh = pdf.outlines();
  1046 + auto& pldh = pdf.doc().page_labels();
  1047 + auto& odh = pdf.doc().outlines();
1035 1048 int pageno = -1;
1036   - for (auto& ph: pdf.pages().getAllPages()) {
  1049 + for (auto& page: pdf.getAllPages()) {
1037 1050 ++pageno;
1038 1051 JSON j_page = JSON::makeDictionary();
1039   - QPDFObjectHandle page = ph.getObjectHandle();
  1052 + QPDFPageObjectHelper ph(page);
1040 1053 j_page.addDictionaryMember("object", page.getJSON(m->json_version));
1041 1054 JSON j_images = j_page.addDictionaryMember("images", JSON::makeArray());
1042 1055 for (auto const& iter2: ph.getImages()) {
... ... @@ -1093,8 +1106,8 @@ void
1093 1106 QPDFJob::doJSONPageLabels(Pipeline* p, bool& first, QPDF& pdf)
1094 1107 {
1095 1108 JSON j_labels = JSON::makeArray();
1096   - auto& pldh = pdf.page_labels();
1097   - long long npages = QIntC::to_longlong(pdf.pages().getAllPages().size());
  1109 + auto& pldh = pdf.doc().page_labels();
  1110 + long long npages = QIntC::to_longlong(pdf.getAllPages().size());
1098 1111 if (pldh.hasPageLabels()) {
1099 1112 std::vector<QPDFObjectHandle> labels;
1100 1113 pldh.getLabelsForPageRange(0, npages - 1, 0, labels);
... ... @@ -1142,13 +1155,12 @@ QPDFJob::doJSONOutlines(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1142 1155 {
1143 1156 std::map<QPDFObjGen, int> page_numbers;
1144 1157 int n = 0;
1145   - for (auto const& ph: pdf.pages().getAllPages()) {
1146   - QPDFObjectHandle oh = ph.getObjectHandle();
1147   - page_numbers[oh.getObjGen()] = ++n;
  1158 + for (auto const& oh: pdf.getAllPages()) {
  1159 + page_numbers[oh] = ++n;
1148 1160 }
1149 1161  
1150 1162 JSON j_outlines = JSON::makeArray();
1151   - addOutlinesToJson(pdf.outlines().getTopLevelOutlines(), j_outlines, page_numbers);
  1163 + addOutlinesToJson(pdf.doc().outlines().getTopLevelOutlines(), j_outlines, page_numbers);
1152 1164 JSON::writeDictionaryItem(p, first, "outlines", j_outlines, 1);
1153 1165 }
1154 1166  
... ... @@ -1156,14 +1168,14 @@ void
1156 1168 QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf)
1157 1169 {
1158 1170 JSON j_acroform = JSON::makeDictionary();
1159   - auto& afdh = pdf.acroform();
  1171 + auto& afdh = pdf.doc().acroform();
1160 1172 j_acroform.addDictionaryMember("hasacroform", JSON::makeBool(afdh.hasAcroForm()));
1161 1173 j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances()));
1162 1174 JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray());
1163 1175 int pagepos1 = 0;
1164   - for (auto const& page: pdf.pages().getAllPages()) {
  1176 + for (auto const& page: pdf.getAllPages()) {
1165 1177 ++pagepos1;
1166   - for (auto& aoh: afdh.getWidgetAnnotationsForPage(page)) {
  1178 + for (auto& aoh: afdh.getWidgetAnnotationsForPage({page})) {
1167 1179 QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(aoh);
1168 1180 if (!ffh.getObjectHandle().isDictionary()) {
1169 1181 continue;
... ... @@ -1297,7 +1309,7 @@ QPDFJob::doJSONAttachments(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1297 1309 };
1298 1310  
1299 1311 JSON j_attachments = JSON::makeDictionary();
1300   - auto& efdh = pdf.embedded_files();
  1312 + auto& efdh = pdf.doc().embedded_files();
1301 1313 for (auto const& iter: efdh.getEmbeddedFiles()) {
1302 1314 std::string const& key = iter.first;
1303 1315 auto fsoh = iter.second;
... ... @@ -1873,7 +1885,7 @@ QPDFJob::doUnderOverlayForPage(
1873 1885 if (!(uo.pdf && pagenos[pageno.idx].contains(uo_idx))) {
1874 1886 return "";
1875 1887 }
1876   - auto& dest_afdh = dest_page.qpdf()->acroform();
  1888 + auto& dest_afdh = dest_page.qpdf()->doc().acroform();
1877 1889  
1878 1890 auto const& pages = uo.pdf->getAllPages();
1879 1891 std::string content;
... ... @@ -1894,7 +1906,7 @@ QPDFJob::doUnderOverlayForPage(
1894 1906 QPDFMatrix cm;
1895 1907 std::string new_content = dest_page.placeFormXObject(
1896 1908 fo[from_no.no][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm);
1897   - dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->acroform());
  1909 + dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->doc().acroform());
1898 1910 if (!new_content.empty()) {
1899 1911 resources.mergeResources("<< /XObject << >> >>"_qpdf);
1900 1912 auto xobject = resources.getKey("/XObject");
... ... @@ -2019,7 +2031,7 @@ void
2019 2031 QPDFJob::addAttachments(QPDF& pdf)
2020 2032 {
2021 2033 maybe_set_pagemode(pdf, "/UseAttachments");
2022   - auto& efdh = pdf.embedded_files();
  2034 + auto& efdh = pdf.doc().embedded_files();
2023 2035 std::vector<std::string> duplicated_keys;
2024 2036 for (auto const& to_add: m->attachments_to_add) {
2025 2037 if ((!to_add.replace) && efdh.getEmbeddedFile(to_add.key)) {
... ... @@ -2063,7 +2075,7 @@ void
2063 2075 QPDFJob::copyAttachments(QPDF& pdf)
2064 2076 {
2065 2077 maybe_set_pagemode(pdf, "/UseAttachments");
2066   - auto& efdh = pdf.embedded_files();
  2078 + auto& efdh = pdf.doc().embedded_files();
2067 2079 std::vector<std::string> duplicates;
2068 2080 for (auto const& to_copy: m->attachments_to_copy) {
2069 2081 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
... ... @@ -2071,7 +2083,7 @@ QPDFJob::copyAttachments(QPDF&amp; pdf)
2071 2083 });
2072 2084 std::unique_ptr<QPDF> other;
2073 2085 processFile(other, to_copy.path.c_str(), to_copy.password.c_str(), false, false);
2074   - auto& other_efdh = other->embedded_files();
  2086 + auto& other_efdh = other->doc().embedded_files();
2075 2087 auto other_attachments = other_efdh.getEmbeddedFiles();
2076 2088 for (auto const& iter: other_attachments) {
2077 2089 std::string new_key = to_copy.prefix + iter.first;
... ... @@ -2114,7 +2126,7 @@ QPDFJob::handleTransformations(QPDF&amp; pdf)
2114 2126 QPDFAcroFormDocumentHelper* afdh_ptr = nullptr;
2115 2127 auto afdh = [&]() -> QPDFAcroFormDocumentHelper& {
2116 2128 if (!afdh_ptr) {
2117   - afdh_ptr = &pdf.acroform();
  2129 + afdh_ptr = &pdf.doc().acroform();
2118 2130 }
2119 2131 return *afdh_ptr;
2120 2132 };
... ... @@ -2205,7 +2217,7 @@ QPDFJob::handleTransformations(QPDF&amp; pdf)
2205 2217 pdf.getRoot().replaceKey("/PageLabels", page_labels);
2206 2218 }
2207 2219 if (!m->attachments_to_remove.empty()) {
2208   - auto& efdh = pdf.embedded_files();
  2220 + auto& efdh = pdf.doc().embedded_files();
2209 2221 for (auto const& key: m->attachments_to_remove) {
2210 2222 if (efdh.removeEmbeddedFile(key)) {
2211 2223 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
... ... @@ -2344,7 +2356,7 @@ QPDFJob::Input::initialize(Inputs&amp; in, QPDF* a_qpdf)
2344 2356 if (in.job.m->remove_unreferenced_page_resources != QPDFJob::re_no) {
2345 2357 remove_unreferenced = in.job.shouldRemoveUnreferencedResources(*qpdf);
2346 2358 }
2347   - if (qpdf->page_labels().hasPageLabels()) {
  2359 + if (qpdf->doc().page_labels().hasPageLabels()) {
2348 2360 in.any_page_labels = true;
2349 2361 }
2350 2362 }
... ... @@ -2574,15 +2586,15 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf)
2574 2586 // original file that we are selecting.
2575 2587 std::vector<QPDFObjectHandle> new_labels;
2576 2588 int out_pageno = 0;
2577   - auto& this_afdh = pdf.acroform();
  2589 + auto& this_afdh = pdf.doc().acroform();
2578 2590 std::set<QPDFObjGen> referenced_fields;
2579 2591 for (auto& selection: new_specs.empty() ? m->inputs.selections : new_specs) {
2580 2592 auto& input = selection.input();
2581 2593 if (input.cfis) {
2582 2594 input.cfis->stayOpen(true);
2583 2595 }
2584   - auto* pldh = m->inputs.any_page_labels ? &input.qpdf->page_labels() : nullptr;
2585   - auto& other_afdh = input.qpdf->acroform();
  2596 + auto* pldh = m->inputs.any_page_labels ? &input.qpdf->doc().page_labels() : nullptr;
  2597 + auto& other_afdh = input.qpdf->doc().acroform();
2586 2598 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
2587 2599 v << prefix << ": adding pages from " << selection.filename() << "\n";
2588 2600 });
... ... @@ -3012,8 +3024,8 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3012 3024 QPDFPageDocumentHelper dh(pdf);
3013 3025 dh.removeUnreferencedResources();
3014 3026 }
3015   - auto& pldh = pdf.page_labels();
3016   - auto& afdh = pdf.acroform();
  3027 + auto& pldh = pdf.doc().page_labels();
  3028 + auto& afdh = pdf.doc().acroform();
3017 3029 std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
3018 3030 size_t pageno_len = std::to_string(pages.size()).length();
3019 3031 size_t num_pages = pages.size();
... ... @@ -3025,7 +3037,8 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3025 3037 }
3026 3038 QPDF outpdf;
3027 3039 outpdf.emptyPDF();
3028   - QPDFAcroFormDocumentHelper* out_afdh = afdh.hasAcroForm() ? &outpdf.acroform() : nullptr;
  3040 + QPDFAcroFormDocumentHelper* out_afdh =
  3041 + afdh.hasAcroForm() ? &outpdf.doc().acroform() : nullptr;
3029 3042 if (m->suppress_warnings) {
3030 3043 outpdf.setSuppressWarnings(true);
3031 3044 }
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -1949,7 +1949,7 @@ QPDFObjectHandle::copyStream()
1949 1949 dict.replaceKey(iter.first, iter.second.shallowCopy());
1950 1950 }
1951 1951 }
1952   - QPDF::StreamCopier::copyStreamData(getOwningQPDF(), result, *this);
  1952 + QPDF::Doc::StreamCopier::copyStreamData(getOwningQPDF(), result, *this);
1953 1953 return result;
1954 1954 }
1955 1955  
... ...
libqpdf/QPDFOutlineDocumentHelper.cc
... ... @@ -34,7 +34,7 @@ QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF&amp; qpdf) :
34 34 QPDFOutlineDocumentHelper&
35 35 QPDFOutlineDocumentHelper::get(QPDF& qpdf)
36 36 {
37   - return qpdf.outlines();
  37 + return qpdf.doc().outlines();
38 38 }
39 39  
40 40 void
... ...
libqpdf/QPDFPageDocumentHelper.cc
... ... @@ -18,7 +18,7 @@ QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF&amp; qpdf) :
18 18 QPDFPageDocumentHelper&
19 19 QPDFPageDocumentHelper::get(QPDF& qpdf)
20 20 {
21   - return qpdf.pages();
  21 + return qpdf.doc().page_dh();
22 22 }
23 23  
24 24 void
... ... @@ -72,7 +72,7 @@ QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page)
72 72 void
73 73 QPDFPageDocumentHelper::flattenAnnotations(int required_flags, int forbidden_flags)
74 74 {
75   - auto& afdh = qpdf.acroform();
  75 + auto& afdh = qpdf.doc().acroform();
76 76 if (afdh.getNeedAppearances()) {
77 77 qpdf.getRoot()
78 78 .getKey("/AcroForm")
... ...
libqpdf/QPDFPageLabelDocumentHelper.cc
... ... @@ -26,7 +26,7 @@ QPDFPageLabelDocumentHelper::QPDFPageLabelDocumentHelper(QPDF&amp; qpdf) :
26 26 QPDFPageLabelDocumentHelper&
27 27 QPDFPageLabelDocumentHelper::get(QPDF& qpdf)
28 28 {
29   - return qpdf.page_labels();
  29 + return qpdf.doc().page_labels();
30 30 }
31 31  
32 32 void
... ...
libqpdf/QPDFParser.cc
... ... @@ -15,6 +15,36 @@ using namespace qpdf;
15 15  
16 16 using ObjectPtr = std::shared_ptr<QPDFObject>;
17 17  
  18 +// The ParseGuard class allows QPDFParser to detect re-entrant parsing. It also provides
  19 +// special access to allow the parser to create unresolved objects and dangling references.
  20 +class QPDF::Doc::ParseGuard
  21 +{
  22 + public:
  23 + ParseGuard(QPDF* qpdf) :
  24 + objects(qpdf ? &qpdf->m->objects : nullptr)
  25 + {
  26 + if (objects) {
  27 + objects->inParse(true);
  28 + }
  29 + }
  30 +
  31 + static std::shared_ptr<QPDFObject>
  32 + getObject(QPDF* qpdf, int id, int gen, bool parse_pdf)
  33 + {
  34 + return qpdf->m->objects.getObjectForParser(id, gen, parse_pdf);
  35 + }
  36 +
  37 + ~ParseGuard()
  38 + {
  39 + if (objects) {
  40 + objects->inParse(false);
  41 + }
  42 + }
  43 + QPDF::Doc::Objects* objects;
  44 +};
  45 +
  46 +using ParseGuard = QPDF::Doc::ParseGuard;
  47 +
18 48 QPDFObjectHandle
19 49 QPDFParser::parse(InputSource& input, std::string const& object_description, QPDF* context)
20 50 {
... ... @@ -49,7 +79,7 @@ QPDFParser::parse_content(
49 79 true,
50 80 0,
51 81 0,
52   - context && context->reconstructed_xref())
  82 + context && context->doc().reconstructed_xref())
53 83 .parse(empty, true);
54 84 }
55 85  
... ... @@ -126,7 +156,7 @@ QPDFParser::parse(bool&amp; empty, bool content_stream)
126 156 // effect of reading the object and changing the file pointer. If you do this, it will cause a
127 157 // logic error to be thrown from QPDF::inParse().
128 158  
129   - QPDF::ParseGuard pg(context);
  159 + ParseGuard pg(context);
130 160 empty = false;
131 161 start = input.tell();
132 162  
... ... @@ -262,7 +292,7 @@ QPDFParser::parseRemainder(bool content_stream)
262 292 auto id = QIntC::to_int(int_buffer[(int_count - 1) % 2]);
263 293 auto gen = QIntC::to_int(int_buffer[(int_count) % 2]);
264 294 if (!(id < 1 || gen < 0 || gen >= 65535)) {
265   - add(QPDF::ParseGuard::getObject(context, id, gen, parse_pdf));
  295 + add(ParseGuard::getObject(context, id, gen, parse_pdf));
266 296 } else {
267 297 QTC::TC("qpdf", "QPDFParser invalid objgen");
268 298 addNull();
... ... @@ -392,7 +422,6 @@ QPDFParser::parseRemainder(bool content_stream)
392 422 frame = &stack.back();
393 423 add(std::move(object));
394 424 } else {
395   - QTC::TC("qpdf", "QPDFParser bad dictionary close in parseRemainder");
396 425 if (sanity_checks) {
397 426 // During sanity checks, assume nesting of containers is corrupt and object is
398 427 // unusable.
... ...
libqpdf/QPDFWriter.cc
... ... @@ -27,6 +27,8 @@
27 27 using namespace std::literals;
28 28 using namespace qpdf;
29 29  
  30 +using Encryption = QPDF::Doc::Encryption;
  31 +
30 32 QPDFWriter::ProgressReporter::~ProgressReporter() // NOLINT (modernize-use-equals-default)
31 33 {
32 34 // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
... ... @@ -260,11 +262,13 @@ Pl_stack::Popper::pop()
260 262 }
261 263  
262 264 // Writer class is restricted to QPDFWriter so that only it can call certain methods.
263   -class QPDF::Writer
  265 +class QPDF::Doc::Writer
264 266 {
265 267 friend class QPDFWriter;
266 268 Writer(QPDF& pdf) :
267   - pdf(pdf)
  269 + pdf(pdf),
  270 + lin(pdf.m->lin),
  271 + objects(pdf.m->objects)
268 272 {
269 273 }
270 274  
... ... @@ -274,7 +278,7 @@ class QPDF::Writer
274 278 QPDFWriter::ObjTable const& obj,
275 279 std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
276 280 {
277   - pdf.optimize(obj, skip_stream_parameters);
  281 + lin.optimize(obj, skip_stream_parameters);
278 282 }
279 283  
280 284 void
... ... @@ -286,7 +290,7 @@ class QPDF::Writer
286 290 std::vector<QPDFObjectHandle>& part8,
287 291 std::vector<QPDFObjectHandle>& part9)
288 292 {
289   - pdf.getLinearizedParts(obj, part4, part6, part7, part8, part9);
  293 + lin.getLinearizedParts(obj, part4, part6, part7, part8, part9);
290 294 }
291 295  
292 296 void
... ... @@ -298,19 +302,19 @@ class QPDF::Writer
298 302 int& O,
299 303 bool compressed)
300 304 {
301   - pdf.generateHintStream(new_obj, obj, hint_stream, S, O, compressed);
  305 + lin.generateHintStream(new_obj, obj, hint_stream, S, O, compressed);
302 306 }
303 307  
304 308 std::vector<QPDFObjGen>
305 309 getCompressibleObjGens()
306 310 {
307   - return pdf.getCompressibleObjVector();
  311 + return objects.getCompressibleObjVector();
308 312 }
309 313  
310 314 std::vector<bool>
311 315 getCompressibleObjSet()
312 316 {
313   - return pdf.getCompressibleObjSet();
  317 + return objects.getCompressibleObjSet();
314 318 }
315 319  
316 320 std::map<QPDFObjGen, QPDFXRefEntry> const&
... ... @@ -322,13 +326,15 @@ class QPDF::Writer
322 326 size_t
323 327 tableSize()
324 328 {
325   - return pdf.tableSize();
  329 + return pdf.m->objects.tableSize();
326 330 }
327 331  
328 332 QPDF& pdf;
  333 + QPDF::Doc::Linearization& lin;
  334 + QPDF::Doc::Objects& objects;
329 335 };
330 336  
331   -class QPDFWriter::Members: QPDF::Writer
  337 +class QPDFWriter::Members: QPDF::Doc::Writer
332 338 {
333 339 friend class QPDFWriter;
334 340  
... ... @@ -343,7 +349,7 @@ class QPDFWriter::Members: QPDF::Writer
343 349 enum trailer_e { t_normal, t_lin_first, t_lin_second };
344 350  
345 351 Members(QPDFWriter& w, QPDF& pdf) :
346   - QPDF::Writer(pdf),
  352 + QPDF::Doc::Writer(pdf),
347 353 w(w),
348 354 root_og(
349 355 pdf.getRoot().getObjGen().isIndirect() ? pdf.getRoot().getObjGen() : QPDFObjGen(-1, 0)),
... ... @@ -504,7 +510,7 @@ class QPDFWriter::Members: QPDF::Writer
504 510 bool pclm{false};
505 511 qpdf_object_stream_e object_stream_mode{qpdf_o_preserve};
506 512  
507   - std::unique_ptr<QPDF::EncryptionData> encryption;
  513 + std::unique_ptr<QPDF::Doc::Encryption> encryption;
508 514 std::string encryption_key;
509 515 bool encrypt_use_aes{false};
510 516  
... ... @@ -829,7 +835,7 @@ QPDFWriter::setR2EncryptionParametersInsecure(
829 835 bool allow_extract,
830 836 bool allow_annotate)
831 837 {
832   - m->encryption = std::make_unique<QPDF::EncryptionData>(1, 2, 5, true);
  838 + m->encryption = std::make_unique<Encryption>(1, 2, 5, true);
833 839 if (!allow_print) {
834 840 m->encryption->setP(3, false);
835 841 }
... ... @@ -857,7 +863,7 @@ QPDFWriter::setR3EncryptionParametersInsecure(
857 863 bool allow_modify_other,
858 864 qpdf_r3_print_e print)
859 865 {
860   - m->encryption = std::make_unique<QPDF::EncryptionData>(2, 3, 16, true);
  866 + m->encryption = std::make_unique<Encryption>(2, 3, 16, true);
861 867 m->interpretR3EncryptionParameters(
862 868 allow_accessibility,
863 869 allow_extract,
... ... @@ -884,7 +890,7 @@ QPDFWriter::setR4EncryptionParametersInsecure(
884 890 bool encrypt_metadata,
885 891 bool use_aes)
886 892 {
887   - m->encryption = std::make_unique<QPDF::EncryptionData>(4, 4, 16, encrypt_metadata);
  893 + m->encryption = std::make_unique<Encryption>(4, 4, 16, encrypt_metadata);
888 894 m->encrypt_use_aes = use_aes;
889 895 m->interpretR3EncryptionParameters(
890 896 allow_accessibility,
... ... @@ -911,7 +917,7 @@ QPDFWriter::setR5EncryptionParameters(
911 917 qpdf_r3_print_e print,
912 918 bool encrypt_metadata)
913 919 {
914   - m->encryption = std::make_unique<QPDF::EncryptionData>(5, 5, 32, encrypt_metadata);
  920 + m->encryption = std::make_unique<Encryption>(5, 5, 32, encrypt_metadata);
915 921 m->encrypt_use_aes = true;
916 922 m->interpretR3EncryptionParameters(
917 923 allow_accessibility,
... ... @@ -938,7 +944,7 @@ QPDFWriter::setR6EncryptionParameters(
938 944 qpdf_r3_print_e print,
939 945 bool encrypt_metadata)
940 946 {
941   - m->encryption = std::make_unique<QPDF::EncryptionData>(5, 6, 32, encrypt_metadata);
  947 + m->encryption = std::make_unique<Encryption>(5, 6, 32, encrypt_metadata);
942 948 m->interpretR3EncryptionParameters(
943 949 allow_accessibility,
944 950 allow_extract,
... ... @@ -1094,7 +1100,7 @@ QPDFWriter::Members::copyEncryptionParameters(QPDF&amp; qpdf)
1094 1100 QTC::TC("qpdf", "QPDFWriter copy encrypt metadata", encrypt_metadata ? 0 : 1);
1095 1101 QTC::TC("qpdf", "QPDFWriter copy use_aes", encrypt_use_aes ? 0 : 1);
1096 1102  
1097   - encryption = std::make_unique<QPDF::EncryptionData>(
  1103 + encryption = std::make_unique<Encryption>(
1098 1104 V,
1099 1105 encrypt.getKey("/R").getIntValueAsInt(),
1100 1106 key_len,
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -26,6 +26,27 @@
26 26 using namespace std::literals;
27 27 using namespace qpdf;
28 28  
  29 +// Pipe class is restricted to QPDF_Stream.
  30 +class QPDF::Doc::Streams
  31 +{
  32 + public:
  33 + static bool
  34 + pipeStreamData(
  35 + QPDF* qpdf,
  36 + QPDFObjGen og,
  37 + qpdf_offset_t offset,
  38 + size_t length,
  39 + QPDFObjectHandle dict,
  40 + bool is_root_metadata,
  41 + Pipeline* pipeline,
  42 + bool suppress_warnings,
  43 + bool will_retry)
  44 + {
  45 + return qpdf->pipeStreamData(
  46 + og, offset, length, dict, is_root_metadata, pipeline, suppress_warnings, will_retry);
  47 + }
  48 +};
  49 +
29 50 namespace
30 51 {
31 52 class SF_Crypt final: public QPDFStreamFilter
... ... @@ -563,7 +584,7 @@ Stream::pipeStreamData(
563 584 throw std::logic_error("pipeStreamData called for stream with no data");
564 585 }
565 586 QTC::TC("qpdf", "QPDF_Stream pipe original stream data");
566   - if (!QPDF::Pipe::pipeStreamData(
  587 + if (!QPDF::Doc::Streams::pipeStreamData(
567 588 obj->getQPDF(),
568 589 obj->getObjGen(),
569 590 obj->getParsedOffset(),
... ...
libqpdf/QPDF_encryption.cc
... ... @@ -21,6 +21,8 @@
21 21 using namespace qpdf;
22 22 using namespace std::literals;
23 23  
  24 +using Encryption = QPDF::Doc::Encryption;
  25 +
24 26 static std::string padding_string =
25 27 "\x28\xbf\x4e\x5e\x4e\x75\x8a\x41\x64\x00\x4e\x56\xff\xfa\x01\x08"
26 28 "\x2e\x2e\x00\xb6\xd0\x68\x3e\x80\x2f\x0c\xa9\xfe\x64\x53\x69\x7a"s;
... ... @@ -52,11 +54,104 @@ QPDF::EncryptionData::getLengthBytes() const
52 54 int
53 55 QPDF::EncryptionData::getP() const
54 56 {
  57 + return this->P;
  58 +}
  59 +
  60 +std::string const&
  61 +QPDF::EncryptionData::getO() const
  62 +{
  63 + return this->O;
  64 +}
  65 +
  66 +std::string const&
  67 +QPDF::EncryptionData::getU() const
  68 +{
  69 + return this->U;
  70 +}
  71 +
  72 +std::string const&
  73 +QPDF::EncryptionData::getOE() const
  74 +{
  75 + return this->OE;
  76 +}
  77 +
  78 +std::string const&
  79 +QPDF::EncryptionData::getUE() const
  80 +{
  81 + return this->UE;
  82 +}
  83 +
  84 +std::string const&
  85 +QPDF::EncryptionData::getPerms() const
  86 +{
  87 + return this->Perms;
  88 +}
  89 +
  90 +std::string const&
  91 +QPDF::EncryptionData::getId1() const
  92 +{
  93 + return this->id1;
  94 +}
  95 +
  96 +bool
  97 +QPDF::EncryptionData::getEncryptMetadata() const
  98 +{
  99 + return this->encrypt_metadata;
  100 +}
  101 +
  102 +void
  103 +QPDF::EncryptionData::setO(std::string const& O)
  104 +{
  105 + this->O = O;
  106 +}
  107 +
  108 +void
  109 +QPDF::EncryptionData::setU(std::string const& U)
  110 +{
  111 + this->U = U;
  112 +}
  113 +
  114 +void
  115 +QPDF::EncryptionData::setV5EncryptionParameters(
  116 + std::string const& O,
  117 + std::string const& OE,
  118 + std::string const& U,
  119 + std::string const& UE,
  120 + std::string const& Perms)
  121 +{
  122 + this->O = O;
  123 + this->OE = OE;
  124 + this->U = U;
  125 + this->UE = UE;
  126 + this->Perms = Perms;
  127 +}
  128 +
  129 +int
  130 +Encryption::getV() const
  131 +{
  132 + return this->V;
  133 +}
  134 +
  135 +int
  136 +Encryption::getR() const
  137 +{
  138 + return this->R;
  139 +}
  140 +
  141 +int
  142 +Encryption::getLengthBytes() const
  143 +{
  144 + return this->Length_bytes;
  145 +}
  146 +
  147 +int
  148 +Encryption::getP() const
  149 +{
55 150 return static_cast<int>(P.to_ulong());
56 151 }
57 152  
58 153 bool
59   -QPDF::EncryptionData::getP(size_t bit) const
  154 +Encryption::getP(size_t bit) const
60 155 {
61 156 qpdf_assert_debug(bit);
62 157 return P.test(bit - 1);
... ... @@ -70,80 +165,80 @@ QPDF::EncryptionParameters::P(size_t bit) const
70 165 }
71 166  
72 167 std::string const&
73   -QPDF::EncryptionData::getO() const
  168 +Encryption::getO() const
74 169 {
75 170 return this->O;
76 171 }
77 172  
78 173 std::string const&
79   -QPDF::EncryptionData::getU() const
  174 +Encryption::getU() const
80 175 {
81 176 return this->U;
82 177 }
83 178  
84 179 std::string const&
85   -QPDF::EncryptionData::getOE() const
  180 +Encryption::getOE() const
86 181 {
87 182 return this->OE;
88 183 }
89 184  
90 185 std::string const&
91   -QPDF::EncryptionData::getUE() const
  186 +Encryption::getUE() const
92 187 {
93 188 return this->UE;
94 189 }
95 190  
96 191 std::string const&
97   -QPDF::EncryptionData::getPerms() const
  192 +Encryption::getPerms() const
98 193 {
99 194 return this->Perms;
100 195 }
101 196  
102 197 std::string const&
103   -QPDF::EncryptionData::getId1() const
  198 +Encryption::getId1() const
104 199 {
105 200 return this->id1;
106 201 }
107 202  
108 203 bool
109   -QPDF::EncryptionData::getEncryptMetadata() const
  204 +Encryption::getEncryptMetadata() const
110 205 {
111 206 return this->encrypt_metadata;
112 207 }
113 208  
114 209 void
115   -QPDF::EncryptionData::setO(std::string const& O)
  210 +Encryption::setO(std::string const& O)
116 211 {
117 212 this->O = O;
118 213 }
119 214  
120 215 void
121   -QPDF::EncryptionData::setU(std::string const& U)
  216 +Encryption::setU(std::string const& U)
122 217 {
123 218 this->U = U;
124 219 }
125 220  
126 221 void
127   -QPDF::EncryptionData::setP(size_t bit, bool val)
  222 +Encryption::setP(size_t bit, bool val)
128 223 {
129 224 qpdf_assert_debug(bit);
130 225 P.set(bit - 1, val);
131 226 }
132 227  
133 228 void
134   -QPDF::EncryptionData::setP(unsigned long val)
  229 +Encryption::setP(unsigned long val)
135 230 {
136 231 P = std::bitset<32>(val);
137 232 }
138 233  
139 234 void
140   -QPDF::EncryptionData::setId1(std::string const& val)
  235 +Encryption::setId1(std::string const& val)
141 236 {
142 237 id1 = val;
143 238 }
144 239  
145 240 void
146   -QPDF::EncryptionData::setV5EncryptionParameters(
  241 +Encryption::setV5EncryptionParameters(
147 242 std::string const& O,
148 243 std::string const& OE,
149 244 std::string const& U,
... ... @@ -246,7 +341,7 @@ process_with_aes(
246 341 }
247 342  
248 343 std::string
249   -QPDF::EncryptionData::hash_V5(
  344 +Encryption::hash_V5(
250 345 std::string const& password, std::string const& salt, std::string const& udata) const
251 346 {
252 347 Pl_SHA2 hash(256);
... ... @@ -358,13 +453,25 @@ QPDF::compute_data_key(
358 453 }
359 454  
360 455 std::string
361   -QPDF::compute_encryption_key(std::string const& password, EncryptionData const& data)
362   -{
363   - return data.compute_encryption_key(password);
  456 +QPDF::compute_encryption_key(std::string const& password, EncryptionData const& ed)
  457 +{
  458 + return Encryption(
  459 + ed.getV(),
  460 + ed.getR(),
  461 + ed.getLengthBytes(),
  462 + ed.getP(),
  463 + ed.getO(),
  464 + ed.getU(),
  465 + ed.getOE(),
  466 + ed.getUE(),
  467 + ed.getPerms(),
  468 + ed.getId1(),
  469 + ed.getEncryptMetadata())
  470 + .compute_encryption_key(password);
364 471 }
365 472  
366 473 std::string
367   -QPDF::EncryptionData::compute_encryption_key(std::string const& password) const
  474 +Encryption::compute_encryption_key(std::string const& password) const
368 475 {
369 476 if (getV() >= 5) {
370 477 // For V >= 5, the encryption key is generated and stored in the file, encrypted separately
... ... @@ -378,7 +485,7 @@ QPDF::EncryptionData::compute_encryption_key(std::string const&amp; password) const
378 485 }
379 486  
380 487 std::string
381   -QPDF::EncryptionData::compute_encryption_key_from_password(std::string const& password) const
  488 +Encryption::compute_encryption_key_from_password(std::string const& password) const
382 489 {
383 490 // Algorithm 3.2 from the PDF 1.7 Reference Manual
384 491  
... ... @@ -405,7 +512,7 @@ QPDF::EncryptionData::compute_encryption_key_from_password(std::string const&amp; pa
405 512 }
406 513  
407 514 std::string
408   -QPDF::EncryptionData::compute_O_rc4_key(
  515 +Encryption::compute_O_rc4_key(
409 516 std::string const& user_password, std::string const& owner_password) const
410 517 {
411 518 if (getV() >= 5) {
... ... @@ -418,7 +525,7 @@ QPDF::EncryptionData::compute_O_rc4_key(
418 525 }
419 526  
420 527 std::string
421   -QPDF::EncryptionData::compute_O_value(
  528 +Encryption::compute_O_value(
422 529 std::string const& user_password, std::string const& owner_password) const
423 530 {
424 531 // Algorithm 3.3 from the PDF 1.7 Reference Manual
... ... @@ -431,7 +538,7 @@ QPDF::EncryptionData::compute_O_value(
431 538 }
432 539  
433 540 std::string
434   -QPDF::EncryptionData::compute_U_value_R2(std::string const& user_password) const
  541 +Encryption::compute_U_value_R2(std::string const& user_password) const
435 542 {
436 543 // Algorithm 3.4 from the PDF 1.7 Reference Manual
437 544  
... ... @@ -443,7 +550,7 @@ QPDF::EncryptionData::compute_U_value_R2(std::string const&amp; user_password) const
443 550 }
444 551  
445 552 std::string
446   -QPDF::EncryptionData::compute_U_value_R3(std::string const& user_password) const
  553 +Encryption::compute_U_value_R3(std::string const& user_password) const
447 554 {
448 555 // Algorithm 3.5 from the PDF 1.7 Reference Manual
449 556  
... ... @@ -460,7 +567,7 @@ QPDF::EncryptionData::compute_U_value_R3(std::string const&amp; user_password) const
460 567 }
461 568  
462 569 std::string
463   -QPDF::EncryptionData::compute_U_value(std::string const& user_password) const
  570 +Encryption::compute_U_value(std::string const& user_password) const
464 571 {
465 572 if (getR() >= 3) {
466 573 return compute_U_value_R3(user_password);
... ... @@ -470,7 +577,7 @@ QPDF::EncryptionData::compute_U_value(std::string const&amp; user_password) const
470 577 }
471 578  
472 579 bool
473   -QPDF::EncryptionData::check_user_password_V4(std::string const& user_password) const
  580 +Encryption::check_user_password_V4(std::string const& user_password) const
474 581 {
475 582 // Algorithm 3.6 from the PDF 1.7 Reference Manual
476 583  
... ... @@ -480,7 +587,7 @@ QPDF::EncryptionData::check_user_password_V4(std::string const&amp; user_password) c
480 587 }
481 588  
482 589 bool
483   -QPDF::EncryptionData::check_user_password_V5(std::string const& user_password) const
  590 +Encryption::check_user_password_V5(std::string const& user_password) const
484 591 {
485 592 // Algorithm 3.11 from the PDF 1.7 extension level 3
486 593  
... ... @@ -491,7 +598,7 @@ QPDF::EncryptionData::check_user_password_V5(std::string const&amp; user_password) c
491 598 }
492 599  
493 600 bool
494   -QPDF::EncryptionData::check_user_password(std::string const& user_password) const
  601 +Encryption::check_user_password(std::string const& user_password) const
495 602 {
496 603 if (getV() < 5) {
497 604 return check_user_password_V4(user_password);
... ... @@ -501,7 +608,7 @@ QPDF::EncryptionData::check_user_password(std::string const&amp; user_password) cons
501 608 }
502 609  
503 610 bool
504   -QPDF::EncryptionData::check_owner_password_V4(
  611 +Encryption::check_owner_password_V4(
505 612 std::string& user_password, std::string const& owner_password) const
506 613 {
507 614 // Algorithm 3.7 from the PDF 1.7 Reference Manual
... ... @@ -518,7 +625,7 @@ QPDF::EncryptionData::check_owner_password_V4(
518 625 }
519 626  
520 627 bool
521   -QPDF::EncryptionData::check_owner_password_V5(std::string const& owner_password) const
  628 +Encryption::check_owner_password_V5(std::string const& owner_password) const
522 629 {
523 630 // Algorithm 3.12 from the PDF 1.7 extension level 3
524 631  
... ... @@ -529,7 +636,7 @@ QPDF::EncryptionData::check_owner_password_V5(std::string const&amp; owner_password)
529 636 }
530 637  
531 638 bool
532   -QPDF::EncryptionData::check_owner_password(
  639 +Encryption::check_owner_password(
533 640 std::string& user_password, std::string const& owner_password) const
534 641 {
535 642 if (getV() < 5) {
... ... @@ -540,7 +647,7 @@ QPDF::EncryptionData::check_owner_password(
540 647 }
541 648  
542 649 std::string
543   -QPDF::EncryptionData::recover_encryption_key_with_password(std::string const& password) const
  650 +Encryption::recover_encryption_key_with_password(std::string const& password) const
544 651 {
545 652 // Disregard whether Perms is valid.
546 653 bool disregard;
... ... @@ -548,7 +655,7 @@ QPDF::EncryptionData::recover_encryption_key_with_password(std::string const&amp; pa
548 655 }
549 656  
550 657 std::string
551   -QPDF::EncryptionData::compute_Perms_value_V5_clear() const
  658 +Encryption::compute_Perms_value_V5_clear() const
552 659 {
553 660 // From algorithm 3.10 from the PDF 1.7 extension level 3
554 661 std::string k = " \xff\xff\xff\xffTadb ";
... ... @@ -565,7 +672,7 @@ QPDF::EncryptionData::compute_Perms_value_V5_clear() const
565 672 }
566 673  
567 674 std::string
568   -QPDF::EncryptionData::recover_encryption_key_with_password(
  675 +Encryption::recover_encryption_key_with_password(
569 676 std::string const& password, bool& perms_valid) const
570 677 {
571 678 // Algorithm 3.2a from the PDF 1.7 extension level 3
... ... @@ -795,7 +902,7 @@ QPDF::EncryptionParameters::initialize(QPDF&amp; qpdf)
795 902 }
796 903 }
797 904  
798   - EncryptionData data(V, R, Length / 8, p, O, U, OE, UE, Perms, id1, encrypt_metadata);
  905 + Encryption data(V, R, Length / 8, p, O, U, OE, UE, Perms, id1, encrypt_metadata);
799 906 if (qm.provided_password_is_hex_key) {
800 907 // ignore passwords in file
801 908 encryption_key = QUtil::hex_decode(provided_password);
... ... @@ -1023,14 +1130,14 @@ QPDF::compute_encryption_O_U(
1023 1130 std::string& out_O,
1024 1131 std::string& out_U)
1025 1132 {
1026   - EncryptionData data(V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata);
  1133 + Encryption data(V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata);
1027 1134 data.compute_encryption_O_U(user_password, owner_password);
1028 1135 out_O = data.getO();
1029 1136 out_U = data.getU();
1030 1137 }
1031 1138  
1032 1139 void
1033   -QPDF::EncryptionData::compute_encryption_O_U(char const* user_password, char const* owner_password)
  1140 +Encryption::compute_encryption_O_U(char const* user_password, char const* owner_password)
1034 1141 {
1035 1142 if (V >= 5) {
1036 1143 throw std::logic_error("compute_encryption_O_U called for file with V >= 5");
... ... @@ -1056,7 +1163,7 @@ QPDF::compute_encryption_parameters_V5(
1056 1163 std::string& out_UE,
1057 1164 std::string& out_Perms)
1058 1165 {
1059   - EncryptionData data(V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata);
  1166 + Encryption data(V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata);
1060 1167 encryption_key = data.compute_encryption_parameters_V5(user_password, owner_password);
1061 1168  
1062 1169 out_O = data.getO();
... ... @@ -1067,8 +1174,7 @@ QPDF::compute_encryption_parameters_V5(
1067 1174 }
1068 1175  
1069 1176 std::string
1070   -QPDF::EncryptionData::compute_encryption_parameters_V5(
1071   - char const* user_password, char const* owner_password)
  1177 +Encryption::compute_encryption_parameters_V5(char const* user_password, char const* owner_password)
1072 1178 {
1073 1179 auto out_encryption_key = util::random_string(key_bytes);
1074 1180 // Algorithm 8 from the PDF 2.0
... ... @@ -1089,7 +1195,7 @@ QPDF::EncryptionData::compute_encryption_parameters_V5(
1089 1195 }
1090 1196  
1091 1197 std::string
1092   -QPDF::EncryptionData::compute_parameters(char const* user_password, char const* owner_password)
  1198 +Encryption::compute_parameters(char const* user_password, char const* owner_password)
1093 1199 {
1094 1200 if (V < 5) {
1095 1201 compute_encryption_O_U(user_password, owner_password);
... ...
libqpdf/QPDF_json.cc
... ... @@ -277,6 +277,7 @@ class QPDF::JSONReactor: public JSON::Reactor
277 277 void replaceObject(QPDFObjectHandle&& replacement, JSON const& value);
278 278  
279 279 QPDF& pdf;
  280 + QPDF::Doc::Objects& objects = pdf.m->objects;
280 281 std::shared_ptr<InputSource> is;
281 282 bool must_be_complete{true};
282 283 std::shared_ptr<QPDFObject::Description> descr;
... ... @@ -541,7 +542,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const&amp; key, JSON const&amp; value)
541 542 } else if (is_obj_key(key, obj, gen)) {
542 543 this->cur_object = key;
543 544 if (setNextStateIfDictionary(key, value, st_object_top)) {
544   - next_obj = pdf.getObjectForJSON(obj, gen);
  545 + next_obj = objects.getObjectForJSON(obj, gen);
545 546 }
546 547 } else {
547 548 QTC::TC("qpdf", "QPDF_json bad object key");
... ... @@ -743,7 +744,7 @@ QPDF::JSONReactor::makeObject(JSON const&amp; value)
743 744 int gen = 0;
744 745 std::string str;
745 746 if (is_indirect_object(str_v, obj, gen)) {
746   - result = pdf.getObjectForJSON(obj, gen);
  747 + result = objects.getObjectForJSON(obj, gen);
747 748 } else if (is_unicode_string(str_v, str)) {
748 749 result = QPDFObjectHandle::newUnicodeString(str);
749 750 } else if (is_binary_string(str_v, str)) {
... ...
libqpdf/QPDF_linearization.cc
... ... @@ -24,6 +24,8 @@
24 24 using namespace qpdf;
25 25 using namespace std::literals;
26 26  
  27 +using Lin = QPDF::Doc::Linearization;
  28 +
27 29 template <class T, class int_type>
28 30 static void
29 31 load_vector_int(
... ... @@ -68,21 +70,21 @@ load_vector_vector(
68 70 }
69 71  
70 72 void
71   -QPDF::linearizationWarning(std::string_view msg)
  73 +Lin::linearizationWarning(std::string_view msg)
72 74 {
73 75 m->linearization_warnings = true;
74   - warn(qpdf_e_linearization, "", 0, std::string(msg));
  76 + qpdf.warn(qpdf_e_linearization, "", 0, std::string(msg));
75 77 }
76 78  
77 79 bool
78 80 QPDF::checkLinearization()
79 81 {
80 82 try {
81   - readLinearizationData();
82   - checkLinearizationInternal();
  83 + m->lin.readLinearizationData();
  84 + m->lin.checkLinearizationInternal();
83 85 return !m->linearization_warnings;
84 86 } catch (std::runtime_error& e) {
85   - linearizationWarning(
  87 + m->lin.linearizationWarning(
86 88 "error encountered while checking linearization data: " + std::string(e.what()));
87 89 return false;
88 90 }
... ... @@ -112,9 +114,9 @@ QPDF::isLinearized()
112 114 // next iteration.
113 115 m->file->seek(toO(pos), SEEK_SET);
114 116  
115   - auto t1 = readToken(*m->file, 20);
116   - if (!(t1.isInteger() && readToken(*m->file, 6).isInteger() &&
117   - readToken(*m->file, 4).isWord("obj"))) {
  117 + auto t1 = m->objects.readToken(*m->file, 20);
  118 + if (!(t1.isInteger() && m->objects.readToken(*m->file, 6).isInteger() &&
  119 + m->objects.readToken(*m->file, 4).isWord("obj"))) {
118 120 pos = buffer.find_first_not_of("0123456789"sv, pos);
119 121 if (pos == std::string::npos) {
120 122 return false;
... ... @@ -140,10 +142,10 @@ QPDF::isLinearized()
140 142 }
141 143  
142 144 void
143   -QPDF::readLinearizationData()
  145 +Lin::readLinearizationData()
144 146 {
145 147 util::assertion(
146   - isLinearized(), "called readLinearizationData for file that is not linearized" //
  148 + qpdf.isLinearized(), "called readLinearizationData for file that is not linearized" //
147 149 );
148 150  
149 151 // This function throws an exception (which is trapped by checkLinearization()) for any errors
... ... @@ -164,19 +166,19 @@ QPDF::readLinearizationData()
164 166 Integer P = P_oh; // first page number
165 167 QTC::TC("qpdf", "QPDF P absent in lindict", P ? 0 : 1);
166 168  
167   - no_ci_stop_if(
  169 + qpdf.no_ci_stop_if(
168 170 !(H && O && E && N && T && (P || P_oh.null())),
169 171 "some keys in linearization dictionary are of the wrong type",
170 172 "linearization dictionary" //
171 173 );
172 174  
173   - no_ci_stop_if(
  175 + qpdf.no_ci_stop_if(
174 176 !(H_size == 2 || H_size == 4),
175 177 "H has the wrong number of items",
176 178 "linearization dictionary" //
177 179 );
178 180  
179   - no_ci_stop_if(
  181 + qpdf.no_ci_stop_if(
180 182 !(H_0 && H_1 && (H_size == 2 || (H_2 && H_3))),
181 183 "some H items are of the wrong type",
182 184 "linearization dictionary" //
... ... @@ -186,8 +188,8 @@ QPDF::readLinearizationData()
186 188  
187 189 // Various places in the code use linp.npages, which is initialized from N, to pre-allocate
188 190 // memory, so make sure it's accurate and bail right now if it's not.
189   - no_ci_stop_if(
190   - N != getAllPages().size(),
  191 + qpdf.no_ci_stop_if(
  192 + N != qpdf.getAllPages().size(),
191 193 "/N does not match number of pages",
192 194 "linearization dictionary" //
193 195 );
... ... @@ -232,12 +234,13 @@ QPDF::readLinearizationData()
232 234  
233 235 size_t HSi = HS;
234 236 if (HSi < 0 || HSi >= h_size) {
235   - throw damagedPDF("linearization hint table", "/S (shared object) offset is out of bounds");
  237 + throw qpdf.damagedPDF(
  238 + "linearization hint table", "/S (shared object) offset is out of bounds");
236 239 }
237 240 readHSharedObject(BitStream(h_buf + HSi, h_size - HSi));
238 241  
239 242 if (HO) {
240   - no_ci_stop_if(
  243 + qpdf.no_ci_stop_if(
241 244 HO < 0 || HO >= h_size,
242 245 "/O (outline) offset is out of bounds",
243 246 "linearization dictionary" //
... ... @@ -248,13 +251,13 @@ QPDF::readLinearizationData()
248 251 }
249 252  
250 253 Dictionary
251   -QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length)
  254 +Lin::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length)
252 255 {
253   - auto H = readObjectAtOffset(offset, "linearization hint stream", false);
  256 + auto H = m->objects.readObjectAtOffset(offset, "linearization hint stream", false);
254 257 ObjCache& oc = m->obj_cache[H];
255 258 qpdf_offset_t min_end_offset = oc.end_before_space;
256 259 qpdf_offset_t max_end_offset = oc.end_after_space;
257   - no_ci_stop_if(
  260 + qpdf.no_ci_stop_if(
258 261 !H.isStream(), "hint table is not a stream", "linearization dictionary" //
259 262 );
260 263  
... ... @@ -272,7 +275,7 @@ QPDF::readHintStream(Pipeline&amp; pl, qpdf_offset_t offset, size_t length)
272 275 QTC::TC("qpdf", "QPDF hint table length direct");
273 276 }
274 277 qpdf_offset_t computed_end = offset + toO(length);
275   - no_ci_stop_if(
  278 + qpdf.no_ci_stop_if(
276 279 computed_end < min_end_offset || computed_end > max_end_offset,
277 280 "hint table length mismatch (expected = " + std::to_string(computed_end) + "; actual = " +
278 281 std::to_string(min_end_offset) + ".." + std::to_string(max_end_offset) + ")",
... ... @@ -283,7 +286,7 @@ QPDF::readHintStream(Pipeline&amp; pl, qpdf_offset_t offset, size_t length)
283 286 }
284 287  
285 288 void
286   -QPDF::readHPageOffset(BitStream h)
  289 +Lin::readHPageOffset(BitStream h)
287 290 {
288 291 // All comments referring to the PDF spec refer to the spec for version 1.4.
289 292  
... ... @@ -332,7 +335,7 @@ QPDF::readHPageOffset(BitStream h)
332 335 }
333 336  
334 337 void
335   -QPDF::readHSharedObject(BitStream h)
  338 +Lin::readHSharedObject(BitStream h)
336 339 {
337 340 HSharedObject& t = m->shared_object_hints;
338 341  
... ... @@ -368,7 +371,7 @@ QPDF::readHSharedObject(BitStream h)
368 371 }
369 372  
370 373 void
371   -QPDF::readHGeneric(BitStream h, HGeneric& t)
  374 +Lin::readHGeneric(BitStream h, HGeneric& t)
372 375 {
373 376 t.first_object = h.getBitsInt(32); // 1
374 377 t.first_object_offset = h.getBitsInt(32); // 2
... ... @@ -377,7 +380,7 @@ QPDF::readHGeneric(BitStream h, HGeneric&amp; t)
377 380 }
378 381  
379 382 void
380   -QPDF::checkLinearizationInternal()
  383 +Lin::checkLinearizationInternal()
381 384 {
382 385 // All comments referring to the PDF spec refer to the spec for version 1.4.
383 386  
... ... @@ -388,7 +391,7 @@ QPDF::checkLinearizationInternal()
388 391 // L: file size in bytes -- checked by isLinearized
389 392  
390 393 // O: object number of first page
391   - std::vector<QPDFObjectHandle> const& pages = getAllPages();
  394 + std::vector<QPDFObjectHandle> const& pages = qpdf.getAllPages();
392 395 if (p.first_page_object != pages.at(0).getObjectID()) {
393 396 linearizationWarning("first page object (/O) mismatch");
394 397 }
... ... @@ -461,7 +464,7 @@ QPDF::checkLinearizationInternal()
461 464 // are present. In that case, it would probably agree with pdlin. As of this writing, the test
462 465 // suite doesn't contain any files with threads.
463 466  
464   - no_ci_stop_if(
  467 + qpdf.no_ci_stop_if(
465 468 m->part6.empty(), "linearization part 6 unexpectedly empty" //
466 469 );
467 470 qpdf_offset_t min_E = -1;
... ... @@ -489,16 +492,16 @@ QPDF::checkLinearizationInternal()
489 492 }
490 493  
491 494 qpdf_offset_t
492   -QPDF::maxEnd(ObjUser const& ou)
  495 +Lin::maxEnd(ObjUser const& ou)
493 496 {
494   - no_ci_stop_if(
  497 + qpdf.no_ci_stop_if(
495 498 !m->obj_user_to_objects.contains(ou),
496 499 "no entry in object user table for requested object user" //
497 500 );
498 501  
499 502 qpdf_offset_t end = 0;
500 503 for (auto const& og: m->obj_user_to_objects[ou]) {
501   - no_ci_stop_if(
  504 + qpdf.no_ci_stop_if(
502 505 !m->obj_cache.contains(og), "unknown object referenced in object user table" //
503 506 );
504 507 end = std::max(end, m->obj_cache[og].end_after_space);
... ... @@ -507,14 +510,14 @@ QPDF::maxEnd(ObjUser const&amp; ou)
507 510 }
508 511  
509 512 qpdf_offset_t
510   -QPDF::getLinearizationOffset(QPDFObjGen og)
  513 +Lin::getLinearizationOffset(QPDFObjGen og)
511 514 {
512 515 QPDFXRefEntry const& entry = m->xref_table[og];
513 516 auto typ = entry.getType();
514 517 if (typ == 1) {
515 518 return entry.getOffset();
516 519 }
517   - no_ci_stop_if(
  520 + qpdf.no_ci_stop_if(
518 521 typ != 2, "getLinearizationOffset called for xref entry not of type 1 or 2" //
519 522 );
520 523 // For compressed objects, return the offset of the object stream that contains them.
... ... @@ -522,33 +525,33 @@ QPDF::getLinearizationOffset(QPDFObjGen og)
522 525 }
523 526  
524 527 QPDFObjectHandle
525   -QPDF::getUncompressedObject(QPDFObjectHandle& obj, std::map<int, int> const& object_stream_data)
  528 +Lin::getUncompressedObject(QPDFObjectHandle& obj, std::map<int, int> const& object_stream_data)
526 529 {
527 530 if (obj.null() || !object_stream_data.contains(obj.getObjectID())) {
528 531 return obj;
529 532 }
530   - return getObject((*(object_stream_data.find(obj.getObjectID()))).second, 0);
  533 + return qpdf.getObject((*(object_stream_data.find(obj.getObjectID()))).second, 0);
531 534 }
532 535  
533 536 QPDFObjectHandle
534   -QPDF::getUncompressedObject(QPDFObjectHandle& oh, QPDFWriter::ObjTable const& obj)
  537 +Lin::getUncompressedObject(QPDFObjectHandle& oh, QPDFWriter::ObjTable const& obj)
535 538 {
536 539 if (obj.contains(oh)) {
537 540 if (auto id = obj[oh].object_stream; id > 0) {
538   - return oh.null() ? oh : getObject(id, 0);
  541 + return oh.null() ? oh : qpdf.getObject(id, 0);
539 542 }
540 543 }
541 544 return oh;
542 545 }
543 546  
544 547 int
545   -QPDF::lengthNextN(int first_object, int n)
  548 +Lin::lengthNextN(int first_object, int n)
546 549 {
547 550 int length = 0;
548 551 for (int i = 0; i < n; ++i) {
549 552 QPDFObjGen og(first_object + i, 0);
550 553 if (m->xref_table.contains(og)) {
551   - no_ci_stop_if(
  554 + qpdf.no_ci_stop_if(
552 555 !m->obj_cache.contains(og),
553 556 "found unknown object while calculating length for linearization data" //
554 557 );
... ... @@ -563,7 +566,7 @@ QPDF::lengthNextN(int first_object, int n)
563 566 }
564 567  
565 568 void
566   -QPDF::checkHPageOffset(
  569 +Lin::checkHPageOffset(
567 570 std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& shared_idx_to_obj)
568 571 {
569 572 // Implementation note 126 says Acrobat always sets delta_content_offset and
... ... @@ -582,7 +585,7 @@ QPDF::checkHPageOffset(
582 585 qpdf_offset_t table_offset = adjusted_offset(m->page_offset_hints.first_page_offset);
583 586 QPDFObjGen first_page_og(pages.at(0).getObjGen());
584 587 if (!m->xref_table.contains(first_page_og)) {
585   - stopOnError("supposed first page object is not known");
  588 + qpdf.stopOnError("supposed first page object is not known");
586 589 }
587 590 qpdf_offset_t offset = getLinearizationOffset(first_page_og);
588 591 if (table_offset != offset) {
... ... @@ -593,7 +596,7 @@ QPDF::checkHPageOffset(
593 596 QPDFObjGen page_og(pages.at(pageno).getObjGen());
594 597 int first_object = page_og.getObj();
595 598 if (!m->xref_table.contains(page_og)) {
596   - stopOnError("unknown object in page offset hint table");
  599 + qpdf.stopOnError("unknown object in page offset hint table");
597 600 }
598 601 offset = getLinearizationOffset(page_og);
599 602  
... ... @@ -633,7 +636,7 @@ QPDF::checkHPageOffset(
633 636  
634 637 for (size_t i = 0; i < toS(he.nshared_objects); ++i) {
635 638 int idx = he.shared_identifiers.at(i);
636   - no_ci_stop_if(
  639 + qpdf.no_ci_stop_if(
637 640 !shared_idx_to_obj.contains(idx),
638 641 "unable to get object for item in shared objects hint table");
639 642  
... ... @@ -642,7 +645,7 @@ QPDF::checkHPageOffset(
642 645  
643 646 for (size_t i = 0; i < toS(ce.nshared_objects); ++i) {
644 647 int idx = ce.shared_identifiers.at(i);
645   - no_ci_stop_if(
  648 + qpdf.no_ci_stop_if(
646 649 idx >= m->c_shared_object_data.nshared_total,
647 650 "index out of bounds for shared object hint table" //
648 651 );
... ... @@ -673,7 +676,7 @@ QPDF::checkHPageOffset(
673 676 }
674 677  
675 678 void
676   -QPDF::checkHSharedObject(std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& idx_to_obj)
  679 +Lin::checkHSharedObject(std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& idx_to_obj)
677 680 {
678 681 // Implementation note 125 says shared object groups always contain only one object.
679 682 // Implementation note 128 says that Acrobat always nbits_nobjects to zero. Implementation note
... ... @@ -715,7 +718,7 @@ QPDF::checkHSharedObject(std::vector&lt;QPDFObjectHandle&gt; const&amp; pages, std::map&lt;in
715 718  
716 719 QPDFObjGen og(cur_object, 0);
717 720 if (!m->xref_table.contains(og)) {
718   - stopOnError("unknown object in shared object hint table");
  721 + qpdf.stopOnError("unknown object in shared object hint table");
719 722 }
720 723 qpdf_offset_t offset = getLinearizationOffset(og);
721 724 qpdf_offset_t h_offset = adjusted_offset(so.first_shared_offset);
... ... @@ -742,7 +745,7 @@ QPDF::checkHSharedObject(std::vector&lt;QPDFObjectHandle&gt; const&amp; pages, std::map&lt;in
742 745 }
743 746  
744 747 void
745   -QPDF::checkHOutlines()
  748 +Lin::checkHOutlines()
746 749 {
747 750 // Empirically, Acrobat generates the correct value for the object number but incorrectly stores
748 751 // the next object number's offset as the offset, at least when outlines appear in part 6. It
... ... @@ -757,7 +760,7 @@ QPDF::checkHOutlines()
757 760  
758 761 if (m->c_outline_data.first_object == m->outline_hints.first_object) {
759 762 // Check length and offset. Acrobat gets these wrong.
760   - QPDFObjectHandle outlines = getRoot().getKey("/Outlines");
  763 + QPDFObjectHandle outlines = qpdf.getRoot().getKey("/Outlines");
761 764 if (!outlines.isIndirect()) {
762 765 // This case is not exercised in test suite since not permitted by the spec, but if
763 766 // this does occur, the code below would fail.
... ... @@ -765,7 +768,7 @@ QPDF::checkHOutlines()
765 768 return;
766 769 }
767 770 QPDFObjGen og(outlines.getObjGen());
768   - no_ci_stop_if(
  771 + qpdf.no_ci_stop_if(
769 772 !m->xref_table.contains(og), "unknown object in outlines hint table" //
770 773 );
771 774 qpdf_offset_t offset = getLinearizationOffset(og);
... ... @@ -795,16 +798,16 @@ void
795 798 QPDF::showLinearizationData()
796 799 {
797 800 try {
798   - readLinearizationData();
799   - checkLinearizationInternal();
800   - dumpLinearizationDataInternal();
  801 + m->lin.readLinearizationData();
  802 + m->lin.checkLinearizationInternal();
  803 + m->lin.dumpLinearizationDataInternal();
801 804 } catch (QPDFExc& e) {
802   - linearizationWarning(e.what());
  805 + m->lin.linearizationWarning(e.what());
803 806 }
804 807 }
805 808  
806 809 void
807   -QPDF::dumpLinearizationDataInternal()
  810 +Lin::dumpLinearizationDataInternal()
808 811 {
809 812 *m->log->getInfo() << m->file->getName() << ": linearization data:\n\n";
810 813  
... ... @@ -830,7 +833,7 @@ QPDF::dumpLinearizationDataInternal()
830 833 }
831 834  
832 835 qpdf_offset_t
833   -QPDF::adjusted_offset(qpdf_offset_t offset)
  836 +Lin::adjusted_offset(qpdf_offset_t offset)
834 837 {
835 838 // All offsets >= H_offset have to be increased by H_length since all hint table location values
836 839 // disregard the hint table itself.
... ... @@ -841,7 +844,7 @@ QPDF::adjusted_offset(qpdf_offset_t offset)
841 844 }
842 845  
843 846 void
844   -QPDF::dumpHPageOffset()
  847 +Lin::dumpHPageOffset()
845 848 {
846 849 HPageOffset& t = m->page_offset_hints;
847 850 *m->log->getInfo() << "min_nobjects: " << t.min_nobjects << "\n"
... ... @@ -880,7 +883,7 @@ QPDF::dumpHPageOffset()
880 883 }
881 884  
882 885 void
883   -QPDF::dumpHSharedObject()
  886 +Lin::dumpHSharedObject()
884 887 {
885 888 HSharedObject& t = m->shared_object_hints;
886 889 *m->log->getInfo() << "first_shared_obj: " << t.first_shared_obj << "\n"
... ... @@ -908,7 +911,7 @@ QPDF::dumpHSharedObject()
908 911 }
909 912  
910 913 void
911   -QPDF::dumpHGeneric(HGeneric& t)
  914 +Lin::dumpHGeneric(HGeneric& t)
912 915 {
913 916 *m->log->getInfo() << "first_object: " << t.first_object << "\n"
914 917 << "first_object_offset: " << adjusted_offset(t.first_object_offset) << "\n"
... ... @@ -918,7 +921,7 @@ QPDF::dumpHGeneric(HGeneric&amp; t)
918 921  
919 922 template <typename T>
920 923 void
921   -QPDF::calculateLinearizationData(T const& object_stream_data)
  924 +Lin::calculateLinearizationData(T const& object_stream_data)
922 925 {
923 926 // This function calculates the ordering of objects, divides them into the appropriate parts,
924 927 // and computes some values for the linearization parameter dictionary and hint tables. The
... ... @@ -985,7 +988,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
985 988 m->c_shared_object_data = CHSharedObject();
986 989 m->c_outline_data = HGeneric();
987 990  
988   - QPDFObjectHandle root = getRoot();
  991 + QPDFObjectHandle root = qpdf.getRoot();
989 992 bool outlines_in_first_page = false;
990 993 QPDFObjectHandle pagemode = root.getKey("/PageMode");
991 994 QTC::TC("qpdf", "QPDF categorize pagemode present", pagemode.isName() ? 1 : 0);
... ... @@ -1106,7 +1109,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1106 1109 { // local scope
1107 1110 // Map all page objects to the containing object stream. This should be a no-op in a
1108 1111 // properly linearized file.
1109   - for (auto oh: getAllPages()) {
  1112 + for (auto oh: qpdf.getAllPages()) {
1110 1113 pages.emplace_back(getUncompressedObject(oh, object_stream_data));
1111 1114 }
1112 1115 }
... ... @@ -1125,13 +1128,13 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1125 1128  
1126 1129 // Part 4: open document objects. We don't care about the order.
1127 1130  
1128   - no_ci_stop_if(
  1131 + qpdf.no_ci_stop_if(
1129 1132 lc_root.size() != 1, "found other than one root while calculating linearization data" //
1130 1133 );
1131 1134  
1132   - m->part4.emplace_back(getObject(*(lc_root.begin())));
  1135 + m->part4.emplace_back(qpdf.getObject(*(lc_root.begin())));
1133 1136 for (auto const& og: lc_open_document) {
1134   - m->part4.emplace_back(getObject(og));
  1137 + m->part4.emplace_back(qpdf.getObject(og));
1135 1138 }
1136 1139  
1137 1140 // Part 6: first page objects. Note: implementation note 124 states that Acrobat always treats
... ... @@ -1139,11 +1142,11 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1139 1142 // any option to set this and also disregards /OpenAction. We will do the same.
1140 1143  
1141 1144 // First, place the actual first page object itself.
1142   - no_ci_stop_if(
  1145 + qpdf.no_ci_stop_if(
1143 1146 pages.empty(), "no pages found while calculating linearization data" //
1144 1147 );
1145 1148 QPDFObjGen first_page_og(pages.at(0).getObjGen());
1146   - no_ci_stop_if(
  1149 + qpdf.no_ci_stop_if(
1147 1150 !lc_first_page_private.erase(first_page_og), "unable to linearize first page" //
1148 1151 );
1149 1152 m->c_linp.first_page_object = pages.at(0).getObjectID();
... ... @@ -1154,11 +1157,11 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1154 1157 // of hint tables.
1155 1158  
1156 1159 for (auto const& og: lc_first_page_private) {
1157   - m->part6.emplace_back(getObject(og));
  1160 + m->part6.emplace_back(qpdf.getObject(og));
1158 1161 }
1159 1162  
1160 1163 for (auto const& og: lc_first_page_shared) {
1161   - m->part6.emplace_back(getObject(og));
  1164 + m->part6.emplace_back(qpdf.getObject(og));
1162 1165 }
1163 1166  
1164 1167 // Place the outline dictionary if it goes in the first page section.
... ... @@ -1179,7 +1182,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1179 1182 // Place this page's page object
1180 1183  
1181 1184 QPDFObjGen page_og(pages.at(i).getObjGen());
1182   - no_ci_stop_if(
  1185 + qpdf.no_ci_stop_if(
1183 1186 !lc_other_page_private.erase(page_og),
1184 1187 "unable to linearize page " + std::to_string(i) //
1185 1188 );
... ... @@ -1192,14 +1195,14 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1192 1195 m->c_page_offset_data.entries.at(i).nobjects = 1;
1193 1196  
1194 1197 ObjUser ou(ObjUser::ou_page, i);
1195   - no_ci_stop_if(
  1198 + qpdf.no_ci_stop_if(
1196 1199 !m->obj_user_to_objects.contains(ou),
1197 1200 "found unreferenced page while calculating linearization data" //
1198 1201 );
1199 1202  
1200 1203 for (auto const& og: m->obj_user_to_objects[ou]) {
1201 1204 if (lc_other_page_private.erase(og)) {
1202   - m->part7.emplace_back(getObject(og));
  1205 + m->part7.emplace_back(qpdf.getObject(og));
1203 1206 ++m->c_page_offset_data.entries.at(i).nobjects;
1204 1207 }
1205 1208 }
... ... @@ -1215,7 +1218,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1215 1218  
1216 1219 // Order is unimportant.
1217 1220 for (auto const& og: lc_other_page_shared) {
1218   - m->part8.emplace_back(getObject(og));
  1221 + m->part8.emplace_back(qpdf.getObject(og));
1219 1222 }
1220 1223  
1221 1224 // Part 9: other objects
... ... @@ -1228,12 +1231,12 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1228 1231 // Place the pages tree.
1229 1232 std::set<QPDFObjGen> pages_ogs =
1230 1233 m->obj_user_to_objects[ObjUser(ObjUser::ou_root_key, "/Pages")];
1231   - no_ci_stop_if(
  1234 + qpdf.no_ci_stop_if(
1232 1235 pages_ogs.empty(), "found empty pages tree while calculating linearization data" //
1233 1236 );
1234 1237 for (auto const& og: pages_ogs) {
1235 1238 if (lc_other.erase(og)) {
1236   - m->part9.emplace_back(getObject(og));
  1239 + m->part9.emplace_back(qpdf.getObject(og));
1237 1240 }
1238 1241 }
1239 1242  
... ... @@ -1255,7 +1258,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1255 1258 std::set<QPDFObjGen>& ogs = m->obj_user_to_objects[ObjUser(ObjUser::ou_thumb, i)];
1256 1259 for (auto const& og: ogs) {
1257 1260 if (lc_thumbnail_private.erase(og)) {
1258   - m->part9.emplace_back(getObject(og));
  1261 + m->part9.emplace_back(qpdf.getObject(og));
1259 1262 }
1260 1263 }
1261 1264 }
... ... @@ -1267,7 +1270,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1267 1270  
1268 1271 // Place shared thumbnail objects
1269 1272 for (auto const& og: lc_thumbnail_shared) {
1270   - m->part9.emplace_back(getObject(og));
  1273 + m->part9.emplace_back(qpdf.getObject(og));
1271 1274 }
1272 1275  
1273 1276 // Place outlines unless in first page
... ... @@ -1277,7 +1280,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1277 1280  
1278 1281 // Place all remaining objects
1279 1282 for (auto const& og: lc_other) {
1280   - m->part9.emplace_back(getObject(og));
  1283 + m->part9.emplace_back(qpdf.getObject(og));
1281 1284 }
1282 1285  
1283 1286 // Make sure we got everything exactly once.
... ... @@ -1285,7 +1288,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1285 1288 size_t num_placed =
1286 1289 m->part4.size() + m->part6.size() + m->part7.size() + m->part8.size() + m->part9.size();
1287 1290 size_t num_wanted = m->object_to_obj_users.size();
1288   - no_ci_stop_if(
  1291 + qpdf.no_ci_stop_if(
1289 1292 // This can happen with damaged files, e.g. if the root is part of the the pages tree.
1290 1293 num_placed != num_wanted,
1291 1294 "QPDF::calculateLinearizationData: wrong number of objects placed (num_placed = " +
... ... @@ -1323,7 +1326,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1323 1326 shared.emplace_back(obj);
1324 1327 }
1325 1328 }
1326   - no_ci_stop_if(
  1329 + qpdf.no_ci_stop_if(
1327 1330 std::cmp_not_equal(
1328 1331 m->c_shared_object_data.nshared_total, m->c_shared_object_data.entries.size()),
1329 1332 "shared object hint table has wrong number of entries" //
... ... @@ -1334,7 +1337,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1334 1337 for (size_t i = 1; i < npages; ++i) {
1335 1338 CHPageOffsetEntry& pe = m->c_page_offset_data.entries.at(i);
1336 1339 ObjUser ou(ObjUser::ou_page, i);
1337   - no_ci_stop_if(
  1340 + qpdf.no_ci_stop_if(
1338 1341 !m->obj_user_to_objects.contains(ou),
1339 1342 "found unreferenced page while calculating linearization data" //
1340 1343 );
... ... @@ -1351,12 +1354,12 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1351 1354  
1352 1355 template <typename T>
1353 1356 void
1354   -QPDF::pushOutlinesToPart(
  1357 +Lin::pushOutlinesToPart(
1355 1358 std::vector<QPDFObjectHandle>& part,
1356 1359 std::set<QPDFObjGen>& lc_outlines,
1357 1360 T const& object_stream_data)
1358 1361 {
1359   - QPDFObjectHandle root = getRoot();
  1362 + QPDFObjectHandle root = qpdf.getRoot();
1360 1363 QPDFObjectHandle outlines = root.getKey("/Outlines");
1361 1364 if (outlines.null()) {
1362 1365 return;
... ... @@ -1380,13 +1383,13 @@ QPDF::pushOutlinesToPart(
1380 1383 if (!m->c_outline_data.first_object) {
1381 1384 m->c_outline_data.first_object = og.getObj();
1382 1385 }
1383   - part.emplace_back(getObject(og));
  1386 + part.emplace_back(qpdf.getObject(og));
1384 1387 ++m->c_outline_data.nobjects;
1385 1388 }
1386 1389 }
1387 1390  
1388 1391 void
1389   -QPDF::getLinearizedParts(
  1392 +Lin::getLinearizedParts(
1390 1393 QPDFWriter::ObjTable const& obj,
1391 1394 std::vector<QPDFObjectHandle>& part4,
1392 1395 std::vector<QPDFObjectHandle>& part6,
... ... @@ -1409,7 +1412,7 @@ nbits(int val)
1409 1412 }
1410 1413  
1411 1414 int
1412   -QPDF::outputLengthNextN(
  1415 +Lin::outputLengthNextN(
1413 1416 int in_object, int n, QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj)
1414 1417 {
1415 1418 // Figure out the length of a series of n consecutive objects in the output file starting with
... ... @@ -1417,12 +1420,12 @@ QPDF::outputLengthNextN(
1417 1420  
1418 1421 int first = obj[in_object].renumber;
1419 1422 int last = first + n;
1420   - no_ci_stop_if(
  1423 + qpdf.no_ci_stop_if(
1421 1424 first <= 0, "found object that is not renumbered while writing linearization data");
1422 1425 qpdf_offset_t length = 0;
1423 1426 for (int i = first; i < last; ++i) {
1424 1427 auto l = new_obj[i].length;
1425   - no_ci_stop_if(
  1428 + qpdf.no_ci_stop_if(
1426 1429 l == 0, "found item with unknown length while writing linearization data" //
1427 1430 );
1428 1431 length += l;
... ... @@ -1431,13 +1434,13 @@ QPDF::outputLengthNextN(
1431 1434 }
1432 1435  
1433 1436 void
1434   -QPDF::calculateHPageOffset(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj)
  1437 +Lin::calculateHPageOffset(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj)
1435 1438 {
1436 1439 // Page Offset Hint Table
1437 1440  
1438 1441 // We are purposely leaving some values set to their initial zero values.
1439 1442  
1440   - std::vector<QPDFObjectHandle> const& pages = getAllPages();
  1443 + std::vector<QPDFObjectHandle> const& pages = qpdf.getAllPages();
1441 1444 size_t npages = pages.size();
1442 1445 CHPageOffset& cph = m->c_page_offset_data;
1443 1446 std::vector<CHPageOffsetEntry>& cphe = cph.entries;
... ... @@ -1499,7 +1502,7 @@ QPDF::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::O
1499 1502 for (auto& phe_i: phe) {
1500 1503 // Adjust delta entries
1501 1504 if (phe_i.delta_nobjects < min_nobjects || phe_i.delta_page_length < min_length) {
1502   - stopOnError(
  1505 + qpdf.stopOnError(
1503 1506 "found too small delta nobjects or delta page length while writing "
1504 1507 "linearization data");
1505 1508 }
... ... @@ -1515,8 +1518,7 @@ QPDF::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::O
1515 1518 }
1516 1519  
1517 1520 void
1518   -QPDF::calculateHSharedObject(
1519   - QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj)
  1521 +Lin::calculateHSharedObject(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj)
1520 1522 {
1521 1523 CHSharedObject& cso = m->c_shared_object_data;
1522 1524 std::vector<CHSharedObjectEntry>& csoe = cso.entries;
... ... @@ -1535,7 +1537,7 @@ QPDF::calculateHSharedObject(
1535 1537 soe.emplace_back();
1536 1538 soe.at(i).delta_group_length = length;
1537 1539 }
1538   - no_ci_stop_if(
  1540 + qpdf.no_ci_stop_if(
1539 1541 soe.size() != toS(cso.nshared_total), "soe has wrong size after initialization" //
1540 1542 );
1541 1543  
... ... @@ -1551,7 +1553,7 @@ QPDF::calculateHSharedObject(
1551 1553  
1552 1554 for (size_t i = 0; i < toS(cso.nshared_total); ++i) {
1553 1555 // Adjust deltas
1554   - no_ci_stop_if(
  1556 + qpdf.no_ci_stop_if(
1555 1557 soe.at(i).delta_group_length < min_length,
1556 1558 "found too small group length while writing linearization data" //
1557 1559 );
... ... @@ -1561,7 +1563,7 @@ QPDF::calculateHSharedObject(
1561 1563 }
1562 1564  
1563 1565 void
1564   -QPDF::calculateHOutline(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj)
  1566 +Lin::calculateHOutline(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj)
1565 1567 {
1566 1568 HGeneric& cho = m->c_outline_data;
1567 1569  
... ... @@ -1612,7 +1614,7 @@ write_vector_vector(
1612 1614 }
1613 1615  
1614 1616 void
1615   -QPDF::writeHPageOffset(BitWriter& w)
  1617 +Lin::writeHPageOffset(BitWriter& w)
1616 1618 {
1617 1619 HPageOffset& t = m->page_offset_hints;
1618 1620  
... ... @@ -1630,7 +1632,7 @@ QPDF::writeHPageOffset(BitWriter&amp; w)
1630 1632 w.writeBitsInt(t.nbits_shared_numerator, 16); // 12
1631 1633 w.writeBitsInt(t.shared_denominator, 16); // 13
1632 1634  
1633   - int nitems = toI(getAllPages().size());
  1635 + int nitems = toI(qpdf.getAllPages().size());
1634 1636 std::vector<HPageOffsetEntry>& entries = t.entries;
1635 1637  
1636 1638 write_vector_int(w, nitems, entries, t.nbits_delta_nobjects, &HPageOffsetEntry::delta_nobjects);
... ... @@ -1659,7 +1661,7 @@ QPDF::writeHPageOffset(BitWriter&amp; w)
1659 1661 }
1660 1662  
1661 1663 void
1662   -QPDF::writeHSharedObject(BitWriter& w)
  1664 +Lin::writeHSharedObject(BitWriter& w)
1663 1665 {
1664 1666 HSharedObject& t = m->shared_object_hints;
1665 1667  
... ... @@ -1685,14 +1687,14 @@ QPDF::writeHSharedObject(BitWriter&amp; w)
1685 1687 for (size_t i = 0; i < toS(nitems); ++i) {
1686 1688 // If signature were present, we'd have to write a 128-bit hash.
1687 1689 if (entries.at(i).signature_present != 0) {
1688   - stopOnError("found unexpected signature present while writing linearization data");
  1690 + qpdf.stopOnError("found unexpected signature present while writing linearization data");
1689 1691 }
1690 1692 }
1691 1693 write_vector_int(w, nitems, entries, t.nbits_nobjects, &HSharedObjectEntry::nobjects_minus_one);
1692 1694 }
1693 1695  
1694 1696 void
1695   -QPDF::writeHGeneric(BitWriter& w, HGeneric& t)
  1697 +Lin::writeHGeneric(BitWriter& w, HGeneric& t)
1696 1698 {
1697 1699 w.writeBitsInt(t.first_object, 32); // 1
1698 1700 w.writeBits(toULL(t.first_object_offset), 32); // 2
... ... @@ -1701,7 +1703,7 @@ QPDF::writeHGeneric(BitWriter&amp; w, HGeneric&amp; t)
1701 1703 }
1702 1704  
1703 1705 void
1704   -QPDF::generateHintStream(
  1706 +Lin::generateHintStream(
1705 1707 QPDFWriter::NewObjTable const& new_obj,
1706 1708 QPDFWriter::ObjTable const& obj,
1707 1709 std::string& hint_buffer,
... ...
libqpdf/QPDF_objects.cc
... ... @@ -23,6 +23,8 @@
23 23 using namespace qpdf;
24 24 using namespace std::literals;
25 25  
  26 +using Objects = QPDF::Doc::Objects;
  27 +
26 28 namespace
27 29 {
28 30 class InvalidInputSource: public InputSource
... ... @@ -102,7 +104,8 @@ class QPDF::ResolveRecorder final
102 104 bool
103 105 QPDF::findStartxref()
104 106 {
105   - if (readToken(*m->file).isWord("startxref") && readToken(*m->file).isInteger()) {
  107 + if (m->objects.readToken(*m->file).isWord("startxref") &&
  108 + m->objects.readToken(*m->file).isInteger()) {
106 109 // Position in front of offset token
107 110 m->file->seek(m->file->getLastOffset(), SEEK_SET);
108 111 return true;
... ... @@ -111,17 +114,16 @@ QPDF::findStartxref()
111 114 }
112 115  
113 116 void
114   -QPDF::parse(char const* password)
  117 +Objects::parse(char const* password)
115 118 {
116 119 if (password) {
117 120 m->encp->provided_password = password;
118 121 }
119 122  
120 123 // Find the header anywhere in the first 1024 bytes of the file.
121   - PatternFinder hf(*this, &QPDF::findHeader);
  124 + PatternFinder hf(qpdf, &QPDF::findHeader);
122 125 if (!m->file->findFirst("%PDF-", 0, 1024, hf)) {
123   - QTC::TC("qpdf", "QPDF not a pdf file");
124   - warn(damagedPDF("", -1, "can't find PDF header"));
  126 + qpdf.warn(qpdf.damagedPDF("", -1, "can't find PDF header"));
125 127 // QPDFWriter writes files that usually require at least version 1.2 for /FlateDecode
126 128 m->pdf_version = "1.2";
127 129 }
... ... @@ -137,7 +139,7 @@ QPDF::parse(char const* password)
137 139 m->xref_table_max_id = static_cast<int>(m->xref_table_max_offset / 3);
138 140 }
139 141 qpdf_offset_t start_offset = (end_offset > 1054 ? end_offset - 1054 : 0);
140   - PatternFinder sf(*this, &QPDF::findStartxref);
  142 + PatternFinder sf(qpdf, &QPDF::findStartxref);
141 143 qpdf_offset_t xref_offset = 0;
142 144 if (m->file->findLast("startxref", start_offset, 0, sf)) {
143 145 xref_offset = QUtil::string_to_ll(readToken(*m->file).getValue().c_str());
... ... @@ -145,35 +147,33 @@ QPDF::parse(char const* password)
145 147  
146 148 try {
147 149 if (xref_offset == 0) {
148   - QTC::TC("qpdf", "QPDF can't find startxref");
149   - throw damagedPDF("", -1, "can't find startxref");
  150 + throw qpdf.damagedPDF("", -1, "can't find startxref");
150 151 }
151 152 try {
152 153 read_xref(xref_offset);
153 154 } catch (QPDFExc&) {
154 155 throw;
155 156 } catch (std::exception& e) {
156   - throw damagedPDF("", -1, std::string("error reading xref: ") + e.what());
  157 + throw qpdf.damagedPDF("", -1, std::string("error reading xref: ") + e.what());
157 158 }
158 159 } catch (QPDFExc& e) {
159 160 if (m->attempt_recovery) {
160 161 reconstruct_xref(e, xref_offset > 0);
161   - QTC::TC("qpdf", "QPDF reconstructed xref table");
162 162 } else {
163 163 throw;
164 164 }
165 165 }
166 166  
167   - initializeEncryption();
  167 + qpdf.initializeEncryption();
168 168 m->parsed = true;
169   - if (!m->xref_table.empty() && !getRoot().getKey("/Pages").isDictionary()) {
  169 + if (!m->xref_table.empty() && !qpdf.getRoot().getKey("/Pages").isDictionary()) {
170 170 // QPDFs created from JSON have an empty xref table and no root object yet.
171   - throw damagedPDF("", -1, "unable to find page tree");
  171 + throw qpdf.damagedPDF("", -1, "unable to find page tree");
172 172 }
173 173 }
174 174  
175 175 void
176   -QPDF::inParse(bool v)
  176 +Objects::inParse(bool v)
177 177 {
178 178 if (m->in_parse == v) {
179 179 // This happens if QPDFParser::parse tries to resolve an indirect object while it is
... ... @@ -186,7 +186,7 @@ QPDF::inParse(bool v)
186 186 }
187 187  
188 188 void
189   -QPDF::setTrailer(QPDFObjectHandle obj)
  189 +Objects::setTrailer(QPDFObjectHandle obj)
190 190 {
191 191 if (m->trailer) {
192 192 return;
... ... @@ -195,7 +195,7 @@ QPDF::setTrailer(QPDFObjectHandle obj)
195 195 }
196 196  
197 197 void
198   -QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref)
  198 +Objects::reconstruct_xref(QPDFExc& e, bool found_startxref)
199 199 {
200 200 if (m->reconstructed_xref) {
201 201 // Avoid xref reconstruction infinite loops. This is getting very hard to reproduce because
... ... @@ -208,7 +208,8 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
208 208 const auto max_warnings = m->warnings.size() + 1000U;
209 209 auto check_warnings = [this, max_warnings]() {
210 210 if (m->warnings.size() > max_warnings) {
211   - throw damagedPDF("", -1, "too many errors while reconstructing cross-reference table");
  211 + throw qpdf.damagedPDF(
  212 + "", -1, "too many errors while reconstructing cross-reference table");
212 213 }
213 214 };
214 215  
... ... @@ -216,9 +217,9 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
216 217 // We may find more objects, which may contain dangling references.
217 218 m->fixed_dangling_refs = false;
218 219  
219   - warn(damagedPDF("", -1, "file is damaged"));
220   - warn(e);
221   - warn(damagedPDF("", -1, "Attempting to reconstruct cross-reference table"));
  220 + qpdf.warn(qpdf.damagedPDF("", -1, "file is damaged"));
  221 + qpdf.warn(e);
  222 + qpdf.warn(qpdf.damagedPDF("", -1, "Attempting to reconstruct cross-reference table"));
222 223  
223 224 // Delete all references to type 1 (uncompressed) objects
224 225 std::vector<QPDFObjGen> to_delete;
... ... @@ -241,18 +242,18 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
241 242 // Don't allow very long tokens here during recovery. All the interesting tokens are covered.
242 243 static size_t const MAX_LEN = 10;
243 244 while (m->file->tell() < eof) {
244   - QPDFTokenizer::Token t1 = readToken(*m->file, MAX_LEN);
  245 + QPDFTokenizer::Token t1 = m->objects.readToken(*m->file, MAX_LEN);
245 246 qpdf_offset_t token_start = m->file->tell() - toO(t1.getValue().length());
246 247 if (t1.isInteger()) {
247 248 auto pos = m->file->tell();
248   - auto t2 = readToken(*m->file, MAX_LEN);
249   - if (t2.isInteger() && readToken(*m->file, MAX_LEN).isWord("obj")) {
  249 + auto t2 = m->objects.readToken(*m->file, MAX_LEN);
  250 + if (t2.isInteger() && m->objects.readToken(*m->file, MAX_LEN).isWord("obj")) {
250 251 int obj = QUtil::string_to_int(t1.getValue().c_str());
251 252 int gen = QUtil::string_to_int(t2.getValue().c_str());
252 253 if (obj <= m->xref_table_max_id) {
253 254 found_objects.emplace_back(obj, gen, token_start);
254 255 } else {
255   - warn(damagedPDF(
  256 + qpdf.warn(qpdf.damagedPDF(
256 257 "", -1, "ignoring object with impossibly large id " + std::to_string(obj)));
257 258 }
258 259 }
... ... @@ -271,14 +272,15 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
271 272 auto xref_backup{m->xref_table};
272 273 try {
273 274 m->file->seek(startxrefs.back(), SEEK_SET);
274   - if (auto offset = QUtil::string_to_ll(readToken(*m->file).getValue().data())) {
275   - read_xref(offset);
  275 + if (auto offset =
  276 + QUtil::string_to_ll(m->objects.readToken(*m->file).getValue().data())) {
  277 + m->objects.read_xref(offset);
276 278  
277   - if (getRoot().getKey("/Pages").isDictionary()) {
  279 + if (qpdf.getRoot().getKey("/Pages").isDictionary()) {
278 280 QTC::TC("qpdf", "QPDF startxref more than 1024 before end");
279   - warn(damagedPDF(
  281 + qpdf.warn(qpdf.damagedPDF(
280 282 "", -1, "startxref was more than 1024 bytes before end of file"));
281   - initializeEncryption();
  283 + qpdf.initializeEncryption();
282 284 m->parsed = true;
283 285 m->reconstructed_xref = false;
284 286 return;
... ... @@ -311,7 +313,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
311 313 m->trailer = t;
312 314 break;
313 315 }
314   - warn(damagedPDF("trailer", *it, "recovered trailer has no /Root entry"));
  316 + qpdf.warn(qpdf.damagedPDF("trailer", *it, "recovered trailer has no /Root entry"));
315 317 }
316 318 check_warnings();
317 319 }
... ... @@ -325,7 +327,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
325 327 if (entry.getType() != 1) {
326 328 continue;
327 329 }
328   - auto oh = getObject(iter.first);
  330 + auto oh = qpdf.getObject(iter.first);
329 331 try {
330 332 if (!oh.isStreamOfType("/XRef")) {
331 333 continue;
... ... @@ -345,7 +347,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
345 347 try {
346 348 read_xref(max_offset, true);
347 349 } catch (std::exception&) {
348   - warn(damagedPDF(
  350 + qpdf.warn(qpdf.damagedPDF(
349 351 "", -1, "error decoding candidate xref stream while recovering damaged file"));
350 352 }
351 353 QTC::TC("qpdf", "QPDF recover xref stream");
... ... @@ -366,7 +368,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
366 368 }
367 369 if (root) {
368 370 if (!m->trailer) {
369   - warn(damagedPDF(
  371 + qpdf.warn(qpdf.damagedPDF(
370 372 "", -1, "unable to find trailer dictionary while recovering damaged file"));
371 373 m->trailer = QPDFObjectHandle::newDictionary();
372 374 }
... ... @@ -379,21 +381,22 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
379 381 // could try to get the trailer from there. This may make it possible to recover files with
380 382 // bad startxref pointers even when they have object streams.
381 383  
382   - throw damagedPDF("", -1, "unable to find trailer dictionary while recovering damaged file");
  384 + throw qpdf.damagedPDF(
  385 + "", -1, "unable to find trailer dictionary while recovering damaged file");
383 386 }
384 387 if (m->xref_table.empty()) {
385 388 // We cannot check for an empty xref table in parse because empty tables are valid when
386 389 // creating QPDF objects from JSON.
387   - throw damagedPDF("", -1, "unable to find objects while recovering damaged file");
  390 + throw qpdf.damagedPDF("", -1, "unable to find objects while recovering damaged file");
388 391 }
389 392 check_warnings();
390 393 if (!m->parsed) {
391 394 m->parsed = true;
392   - getAllPages();
  395 + qpdf.getAllPages();
393 396 check_warnings();
394 397 if (m->all_pages.empty()) {
395 398 m->parsed = false;
396   - throw damagedPDF("", -1, "unable to find any pages while recovering damaged file");
  399 + throw qpdf.damagedPDF("", -1, "unable to find any pages while recovering damaged file");
397 400 }
398 401 }
399 402  
... ... @@ -405,7 +408,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
405 408 }
406 409  
407 410 void
408   -QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
  411 +Objects::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
409 412 {
410 413 std::map<int, int> free_table;
411 414 std::set<qpdf_offset_t> visited;
... ... @@ -440,8 +443,7 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
440 443 // where it is terminated by arbitrary whitespace.
441 444 if ((strncmp(buf, "xref", 4) == 0) && util::is_space(buf[4])) {
442 445 if (skipped_space) {
443   - QTC::TC("qpdf", "QPDF xref skipped space");
444   - warn(damagedPDF("", -1, "extraneous whitespace seen before xref"));
  446 + qpdf.warn(qpdf.damagedPDF("", -1, "extraneous whitespace seen before xref"));
445 447 }
446 448 QTC::TC(
447 449 "qpdf",
... ... @@ -460,13 +462,12 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
460 462 xref_offset = read_xrefStream(xref_offset, in_stream_recovery);
461 463 }
462 464 if (visited.contains(xref_offset)) {
463   - QTC::TC("qpdf", "QPDF xref loop");
464   - throw damagedPDF("", -1, "loop detected following xref tables");
  465 + throw qpdf.damagedPDF("", -1, "loop detected following xref tables");
465 466 }
466 467 }
467 468  
468 469 if (!m->trailer) {
469   - throw damagedPDF("", -1, "unable to find trailer while reading xref");
  470 + throw qpdf.damagedPDF("", -1, "unable to find trailer while reading xref");
470 471 }
471 472 int size = m->trailer.getKey("/Size").getIntValueAsInt();
472 473 int max_obj = 0;
... ... @@ -477,8 +478,7 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
477 478 max_obj = std::max(max_obj, *(m->deleted_objects.rbegin()));
478 479 }
479 480 if ((size < 1) || (size - 1 != max_obj)) {
480   - QTC::TC("qpdf", "QPDF xref size mismatch");
481   - warn(damagedPDF(
  481 + qpdf.warn(qpdf.damagedPDF(
482 482 "",
483 483 -1,
484 484 ("reported number of objects (" + std::to_string(size) +
... ... @@ -494,14 +494,14 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
494 494 for (auto const& item: m->xref_table) {
495 495 auto id = item.first.getObj();
496 496 if (id == last_og.getObj() && id > 0) {
497   - removeObject(last_og);
  497 + qpdf.removeObject(last_og);
498 498 }
499 499 last_og = item.first;
500 500 }
501 501 }
502 502  
503 503 bool
504   -QPDF::parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes)
  504 +Objects::parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes)
505 505 {
506 506 // is_space and is_digit both return false on '\0', so this will not overrun the null-terminated
507 507 // buffer.
... ... @@ -549,7 +549,7 @@ QPDF::parse_xrefFirst(std::string const&amp; line, int&amp; obj, int&amp; num, int&amp; bytes)
549 549 }
550 550  
551 551 bool
552   -QPDF::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type)
  552 +Objects::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type)
553 553 {
554 554 // Reposition after initial read attempt and reread.
555 555 m->file->seek(m->file->getLastOffset(), SEEK_SET);
... ... @@ -563,7 +563,6 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
563 563 bool invalid = false;
564 564 while (util::is_space(*p)) {
565 565 ++p;
566   - QTC::TC("qpdf", "QPDF ignore first space in xref entry");
567 566 invalid = true;
568 567 }
569 568 // Require digit
... ... @@ -580,7 +579,6 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
580 579 return false;
581 580 }
582 581 if (util::is_space(*(p + 1))) {
583   - QTC::TC("qpdf", "QPDF ignore first extra space in xref entry");
584 582 invalid = true;
585 583 }
586 584 // Skip spaces
... ... @@ -601,7 +599,6 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
601 599 return false;
602 600 }
603 601 if (util::is_space(*(p + 1))) {
604   - QTC::TC("qpdf", "QPDF ignore second extra space in xref entry");
605 602 invalid = true;
606 603 }
607 604 // Skip spaces
... ... @@ -614,12 +611,11 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
614 611 return false;
615 612 }
616 613 if ((f1_str.length() != 10) || (f2_str.length() != 5)) {
617   - QTC::TC("qpdf", "QPDF ignore length error xref entry");
618 614 invalid = true;
619 615 }
620 616  
621 617 if (invalid) {
622   - warn(damagedPDF("xref table", "accepting invalid xref table entry"));
  618 + qpdf.warn(qpdf.damagedPDF("xref table", "accepting invalid xref table entry"));
623 619 }
624 620  
625 621 f1 = QUtil::string_to_ll(f1_str.c_str());
... ... @@ -631,7 +627,7 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
631 627 // Optimistically read and parse xref entry. If entry is bad, call read_bad_xrefEntry and return
632 628 // result.
633 629 bool
634   -QPDF::read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type)
  630 +Objects::read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type)
635 631 {
636 632 std::array<char, 21> line;
637 633 if (m->file->read(line.data(), 20) != 20) {
... ... @@ -685,7 +681,7 @@ QPDF::read_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
685 681  
686 682 // Read a single cross-reference table section and associated trailer.
687 683 qpdf_offset_t
688   -QPDF::read_xrefTable(qpdf_offset_t xref_offset)
  684 +Objects::read_xrefTable(qpdf_offset_t xref_offset)
689 685 {
690 686 m->file->seek(xref_offset, SEEK_SET);
691 687 std::string line;
... ... @@ -696,8 +692,7 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset)
696 692 int num = 0;
697 693 int bytes = 0;
698 694 if (!parse_xrefFirst(line, obj, num, bytes)) {
699   - QTC::TC("qpdf", "QPDF invalid xref");
700   - throw damagedPDF("xref table", "xref syntax invalid");
  695 + throw qpdf.damagedPDF("xref table", "xref syntax invalid");
701 696 }
702 697 m->file->seek(m->file->getLastOffset() + bytes, SEEK_SET);
703 698 for (qpdf_offset_t i = obj; i - num < obj; ++i) {
... ... @@ -710,8 +705,7 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset)
710 705 int f2 = 0;
711 706 char type = '\0';
712 707 if (!read_xrefEntry(f1, f2, type)) {
713   - QTC::TC("qpdf", "QPDF invalid xref entry");
714   - throw damagedPDF(
  708 + throw qpdf.damagedPDF(
715 709 "xref table", "invalid xref entry (obj=" + std::to_string(i) + ")");
716 710 }
717 711 if (type == 'f') {
... ... @@ -729,22 +723,19 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset)
729 723 }
730 724  
731 725 // Set offset to previous xref table if any
732   - QPDFObjectHandle cur_trailer = readTrailer();
  726 + QPDFObjectHandle cur_trailer = m->objects.readTrailer();
733 727 if (!cur_trailer.isDictionary()) {
734   - QTC::TC("qpdf", "QPDF missing trailer");
735   - throw damagedPDF("", "expected trailer dictionary");
  728 + throw qpdf.damagedPDF("", "expected trailer dictionary");
736 729 }
737 730  
738 731 if (!m->trailer) {
739 732 setTrailer(cur_trailer);
740 733  
741 734 if (!m->trailer.hasKey("/Size")) {
742   - QTC::TC("qpdf", "QPDF trailer lacks size");
743   - throw damagedPDF("trailer", "trailer dictionary lacks /Size key");
  735 + throw qpdf.damagedPDF("trailer", "trailer dictionary lacks /Size key");
744 736 }
745 737 if (!m->trailer.getKey("/Size").isInteger()) {
746   - QTC::TC("qpdf", "QPDF trailer size not integer");
747   - throw damagedPDF("trailer", "/Size key in trailer dictionary is not an integer");
  738 + throw qpdf.damagedPDF("trailer", "/Size key in trailer dictionary is not an integer");
748 739 }
749 740 }
750 741  
... ... @@ -757,17 +748,15 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset)
757 748 // /Prev key instead of the xref stream's.
758 749 (void)read_xrefStream(cur_trailer.getKey("/XRefStm").getIntValue());
759 750 } else {
760   - throw damagedPDF("xref stream", xref_offset, "invalid /XRefStm");
  751 + throw qpdf.damagedPDF("xref stream", xref_offset, "invalid /XRefStm");
761 752 }
762 753 }
763 754 }
764 755  
765 756 if (cur_trailer.hasKey("/Prev")) {
766 757 if (!cur_trailer.getKey("/Prev").isInteger()) {
767   - QTC::TC("qpdf", "QPDF trailer prev not integer");
768   - throw damagedPDF("trailer", "/Prev key in trailer dictionary is not an integer");
  758 + throw qpdf.damagedPDF("trailer", "/Prev key in trailer dictionary is not an integer");
769 759 }
770   - QTC::TC("qpdf", "QPDF prev key in trailer dictionary");
771 760 return cur_trailer.getKey("/Prev").getIntValue();
772 761 }
773 762  
... ... @@ -776,7 +765,7 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset)
776 765  
777 766 // Read a single cross-reference stream.
778 767 qpdf_offset_t
779   -QPDF::read_xrefStream(qpdf_offset_t xref_offset, bool in_stream_recovery)
  768 +Objects::read_xrefStream(qpdf_offset_t xref_offset, bool in_stream_recovery)
780 769 {
781 770 if (!m->ignore_xref_streams) {
782 771 QPDFObjectHandle xref_obj;
... ... @@ -788,19 +777,17 @@ QPDF::read_xrefStream(qpdf_offset_t xref_offset, bool in_stream_recovery)
788 777 }
789 778 m->in_read_xref_stream = false;
790 779 if (xref_obj.isStreamOfType("/XRef")) {
791   - QTC::TC("qpdf", "QPDF found xref stream");
792 780 return processXRefStream(xref_offset, xref_obj, in_stream_recovery);
793 781 }
794 782 }
795 783  
796   - QTC::TC("qpdf", "QPDF can't find xref");
797   - throw damagedPDF("", xref_offset, "xref not found");
  784 + throw qpdf.damagedPDF("", xref_offset, "xref not found");
798 785 return 0; // unreachable
799 786 }
800 787  
801 788 // Return the entry size of the xref stream and the processed W array.
802 789 std::pair<int, std::array<int, 3>>
803   -QPDF::processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged)
  790 +Objects::processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged)
804 791 {
805 792 auto W_obj = dict.getKey("/W");
806 793 if (!(W_obj.size() >= 3 && W_obj.getArrayItem(0).isInteger() &&
... ... @@ -830,7 +817,7 @@ QPDF::processXRefW(QPDFObjectHandle&amp; dict, std::function&lt;QPDFExc(std::string_vie
830 817  
831 818 // Validate Size key and return the maximum number of entries that the xref stream can contain.
832 819 int
833   -QPDF::processXRefSize(
  820 +Objects::processXRefSize(
834 821 QPDFObjectHandle& dict, int entry_size, std::function<QPDFExc(std::string_view)> damaged)
835 822 {
836 823 // Number of entries is limited by the highest possible object id and stream size.
... ... @@ -854,7 +841,7 @@ QPDF::processXRefSize(
854 841  
855 842 // Return the number of entries of the xref stream and the processed Index array.
856 843 std::pair<int, std::vector<std::pair<int, int>>>
857   -QPDF::processXRefIndex(
  844 +Objects::processXRefIndex(
858 845 QPDFObjectHandle& dict, int max_num_entries, std::function<QPDFExc(std::string_view)> damaged)
859 846 {
860 847 auto size = dict.getKey("/Size").getIntValueAsInt();
... ... @@ -921,11 +908,11 @@ QPDF::processXRefIndex(
921 908 }
922 909  
923 910 qpdf_offset_t
924   -QPDF::processXRefStream(
  911 +Objects::processXRefStream(
925 912 qpdf_offset_t xref_offset, QPDFObjectHandle& xref_obj, bool in_stream_recovery)
926 913 {
927 914 auto damaged = [this, xref_offset](std::string_view msg) -> QPDFExc {
928   - return damagedPDF("xref stream", xref_offset, msg.data());
  915 + return qpdf.damagedPDF("xref stream", xref_offset, msg.data());
929 916 };
930 917  
931 918 auto dict = xref_obj.getDict();
... ... @@ -945,7 +932,7 @@ QPDF::processXRefStream(
945 932 if (expected_size > actual_size) {
946 933 throw x;
947 934 } else {
948   - warn(x);
  935 + qpdf.warn(x);
949 936 }
950 937 }
951 938  
... ... @@ -960,7 +947,6 @@ QPDF::processXRefStream(
960 947 // Read this entry
961 948 std::array<qpdf_offset_t, 3> fields{};
962 949 if (W[0] == 0) {
963   - QTC::TC("qpdf", "QPDF default for xref stream field 0");
964 950 fields[0] = 1;
965 951 }
966 952 for (size_t j = 0; j < 3; ++j) {
... ... @@ -1006,10 +992,9 @@ QPDF::processXRefStream(
1006 992  
1007 993 if (dict.hasKey("/Prev")) {
1008 994 if (!dict.getKey("/Prev").isInteger()) {
1009   - throw damagedPDF(
  995 + throw qpdf.damagedPDF(
1010 996 "xref stream", "/Prev key in xref stream dictionary is not an integer");
1011 997 }
1012   - QTC::TC("qpdf", "QPDF prev key in xref stream dictionary");
1013 998 return dict.getKey("/Prev").getIntValue();
1014 999 } else {
1015 1000 return 0;
... ... @@ -1017,7 +1002,7 @@ QPDF::processXRefStream(
1017 1002 }
1018 1003  
1019 1004 void
1020   -QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
  1005 +Objects::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1021 1006 {
1022 1007 // Populate the xref table in such a way that the first reference to an object that we see,
1023 1008 // which is the one in the latest xref table in which it appears, is the one that gets stored.
... ... @@ -1035,25 +1020,23 @@ QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1035 1020 // We are ignoring invalid objgens. Most will arrive here from xref reconstruction. There
1036 1021 // is probably no point having another warning but we could count invalid items in order to
1037 1022 // decide when to give up.
1038   - QTC::TC("qpdf", "QPDF xref overwrite invalid objgen");
1039 1023 // ignore impossibly large object ids or object ids > Size.
1040 1024 return;
1041 1025 }
1042 1026  
1043 1027 if (m->deleted_objects.contains(obj)) {
1044   - QTC::TC("qpdf", "QPDF xref deleted object");
1045 1028 return;
1046 1029 }
1047 1030  
1048 1031 if (f0 == 2) {
1049 1032 if (f1 == obj) {
1050   - warn(
1051   - damagedPDF("xref stream", "self-referential object stream " + std::to_string(obj)));
  1033 + qpdf.warn(qpdf.damagedPDF(
  1034 + "xref stream", "self-referential object stream " + std::to_string(obj)));
1052 1035 return;
1053 1036 }
1054 1037 if (f1 > m->xref_table_max_id) {
1055 1038 // ignore impossibly large object stream ids
1056   - warn(damagedPDF(
  1039 + qpdf.warn(qpdf.damagedPDF(
1057 1040 "xref stream",
1058 1041 "object stream id " + std::to_string(f1) + " for object " + std::to_string(obj) +
1059 1042 " is impossibly large"));
... ... @@ -1063,7 +1046,6 @@ QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1063 1046  
1064 1047 auto [iter, created] = m->xref_table.try_emplace(QPDFObjGen(obj, (f0 == 2 ? 0 : f2)));
1065 1048 if (!created) {
1066   - QTC::TC("qpdf", "QPDF xref reused object");
1067 1049 return;
1068 1050 }
1069 1051  
... ... @@ -1079,13 +1061,14 @@ QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1079 1061 break;
1080 1062  
1081 1063 default:
1082   - throw damagedPDF("xref stream", "unknown xref stream entry type " + std::to_string(f0));
  1064 + throw qpdf.damagedPDF(
  1065 + "xref stream", "unknown xref stream entry type " + std::to_string(f0));
1083 1066 break;
1084 1067 }
1085 1068 }
1086 1069  
1087 1070 void
1088   -QPDF::insertFreeXrefEntry(QPDFObjGen og)
  1071 +Objects::insertFreeXrefEntry(QPDFObjGen og)
1089 1072 {
1090 1073 if (!m->xref_table.contains(og)) {
1091 1074 m->deleted_objects.insert(og.getObj());
... ... @@ -1121,7 +1104,7 @@ QPDF::showXRefTable()
1121 1104 // Resolve all objects in the xref table. If this triggers a xref table reconstruction abort and
1122 1105 // return false. Otherwise return true.
1123 1106 bool
1124   -QPDF::resolveXRefTable()
  1107 +Objects::resolveXRefTable()
1125 1108 {
1126 1109 bool may_change = !m->reconstructed_xref;
1127 1110 for (auto& iter: m->xref_table) {
... ... @@ -1143,9 +1126,8 @@ QPDF::fixDanglingReferences(bool force)
1143 1126 if (m->fixed_dangling_refs) {
1144 1127 return;
1145 1128 }
1146   - if (!resolveXRefTable()) {
1147   - QTC::TC("qpdf", "QPDF fix dangling triggered xref reconstruction");
1148   - resolveXRefTable();
  1129 + if (!m->objects.resolveXRefTable()) {
  1130 + m->objects.resolveXRefTable();
1149 1131 }
1150 1132 m->fixed_dangling_refs = true;
1151 1133 }
... ... @@ -1171,13 +1153,13 @@ QPDF::getAllObjects()
1171 1153 fixDanglingReferences();
1172 1154 std::vector<QPDFObjectHandle> result;
1173 1155 for (auto const& iter: m->obj_cache) {
1174   - result.push_back(newIndirect(iter.first, iter.second.object));
  1156 + result.emplace_back(m->objects.newIndirect(iter.first, iter.second.object));
1175 1157 }
1176 1158 return result;
1177 1159 }
1178 1160  
1179 1161 void
1180   -QPDF::setLastObjectDescription(std::string const& description, QPDFObjGen og)
  1162 +Objects::setLastObjectDescription(std::string const& description, QPDFObjGen og)
1181 1163 {
1182 1164 m->last_object_description.clear();
1183 1165 if (!description.empty()) {
... ... @@ -1192,17 +1174,17 @@ QPDF::setLastObjectDescription(std::string const&amp; description, QPDFObjGen og)
1192 1174 }
1193 1175  
1194 1176 QPDFObjectHandle
1195   -QPDF::readTrailer()
  1177 +Objects::readTrailer()
1196 1178 {
1197 1179 qpdf_offset_t offset = m->file->tell();
1198 1180 auto [object, empty] =
1199   - QPDFParser::parse(*m->file, "trailer", m->tokenizer, nullptr, *this, m->reconstructed_xref);
  1181 + QPDFParser::parse(*m->file, "trailer", m->tokenizer, nullptr, qpdf, m->reconstructed_xref);
1200 1182 if (empty) {
1201 1183 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1202 1184 // actual PDF files and Adobe Reader appears to ignore them.
1203   - warn(damagedPDF("trailer", "empty object treated as null"));
1204   - } else if (object.isDictionary() && readToken(*m->file).isWord("stream")) {
1205   - warn(damagedPDF("trailer", m->file->tell(), "stream keyword found in trailer"));
  1185 + qpdf.warn(qpdf.damagedPDF("trailer", "empty object treated as null"));
  1186 + } else if (object.isDictionary() && m->objects.readToken(*m->file).isWord("stream")) {
  1187 + qpdf.warn(qpdf.damagedPDF("trailer", m->file->tell(), "stream keyword found in trailer"));
1206 1188 }
1207 1189 // Override last_offset so that it points to the beginning of the object we just read
1208 1190 m->file->setLastOffset(offset);
... ... @@ -1210,25 +1192,26 @@ QPDF::readTrailer()
1210 1192 }
1211 1193  
1212 1194 QPDFObjectHandle
1213   -QPDF::readObject(std::string const& description, QPDFObjGen og)
  1195 +Objects::readObject(std::string const& description, QPDFObjGen og)
1214 1196 {
1215 1197 setLastObjectDescription(description, og);
1216 1198 qpdf_offset_t offset = m->file->tell();
1217 1199  
1218   - StringDecrypter decrypter{this, og};
  1200 + StringDecrypter decrypter{&qpdf, og};
1219 1201 StringDecrypter* decrypter_ptr = m->encp->encrypted ? &decrypter : nullptr;
1220 1202 auto [object, empty] = QPDFParser::parse(
1221 1203 *m->file,
1222 1204 m->last_object_description,
1223 1205 m->tokenizer,
1224 1206 decrypter_ptr,
1225   - *this,
  1207 + qpdf,
1226 1208 m->reconstructed_xref || m->in_read_xref_stream);
1227 1209 ;
1228 1210 if (empty) {
1229 1211 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1230 1212 // actual PDF files and Adobe Reader appears to ignore them.
1231   - warn(damagedPDF(*m->file, m->file->getLastOffset(), "empty object treated as null"));
  1213 + qpdf.warn(
  1214 + qpdf.damagedPDF(*m->file, m->file->getLastOffset(), "empty object treated as null"));
1232 1215 return object;
1233 1216 }
1234 1217 auto token = readToken(*m->file);
... ... @@ -1237,15 +1220,14 @@ QPDF::readObject(std::string const&amp; description, QPDFObjGen og)
1237 1220 token = readToken(*m->file);
1238 1221 }
1239 1222 if (!token.isWord("endobj")) {
1240   - QTC::TC("qpdf", "QPDF err expected endobj");
1241   - warn(damagedPDF("expected endobj"));
  1223 + qpdf.warn(qpdf.damagedPDF("expected endobj"));
1242 1224 }
1243 1225 return object;
1244 1226 }
1245 1227  
1246 1228 // After reading stream dictionary and stream keyword, read rest of stream.
1247 1229 void
1248   -QPDF::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset)
  1230 +Objects::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset)
1249 1231 {
1250 1232 validateStreamLineEnd(object, og, offset);
1251 1233  
... ... @@ -1259,9 +1241,9 @@ QPDF::readStream(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset_t offset)
1259 1241  
1260 1242 if (!length_obj.isInteger()) {
1261 1243 if (length_obj.null()) {
1262   - throw damagedPDF(offset, "stream dictionary lacks /Length key");
  1244 + throw qpdf.damagedPDF(offset, "stream dictionary lacks /Length key");
1263 1245 }
1264   - throw damagedPDF(offset, "/Length key in stream dictionary is not an integer");
  1246 + throw qpdf.damagedPDF(offset, "/Length key in stream dictionary is not an integer");
1265 1247 }
1266 1248  
1267 1249 length = toS(length_obj.getUIntValue());
... ... @@ -1269,21 +1251,21 @@ QPDF::readStream(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset_t offset)
1269 1251 m->file->seek(stream_offset, SEEK_SET);
1270 1252 m->file->seek(toO(length), SEEK_CUR);
1271 1253 if (!readToken(*m->file).isWord("endstream")) {
1272   - throw damagedPDF("expected endstream");
  1254 + throw qpdf.damagedPDF("expected endstream");
1273 1255 }
1274 1256 } catch (QPDFExc& e) {
1275 1257 if (m->attempt_recovery) {
1276   - warn(e);
  1258 + qpdf.warn(e);
1277 1259 length = recoverStreamLength(m->file, og, stream_offset);
1278 1260 } else {
1279 1261 throw;
1280 1262 }
1281 1263 }
1282   - object = QPDFObjectHandle(qpdf::Stream(*this, og, object, stream_offset, length));
  1264 + object = QPDFObjectHandle(qpdf::Stream(qpdf, og, object, stream_offset, length));
1283 1265 }
1284 1266  
1285 1267 void
1286   -QPDF::validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset)
  1268 +Objects::validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset)
1287 1269 {
1288 1270 // The PDF specification states that the word "stream" should be followed by either a carriage
1289 1271 // return and a newline or by a newline alone. It specifically disallowed following it by a
... ... @@ -1301,7 +1283,6 @@ QPDF::validateStreamLineEnd(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset
1301 1283 }
1302 1284 if (ch == '\n') {
1303 1285 // ready to read stream data
1304   - QTC::TC("qpdf", "QPDF stream with NL only");
1305 1286 return;
1306 1287 }
1307 1288 if (ch == '\r') {
... ... @@ -1313,33 +1294,32 @@ QPDF::validateStreamLineEnd(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset
1313 1294 } else {
1314 1295 // Treat the \r by itself as the whitespace after endstream and start reading
1315 1296 // stream data in spite of not having seen a newline.
1316   - QTC::TC("qpdf", "QPDF stream with CR only");
1317 1297 m->file->unreadCh(ch);
1318   - warn(damagedPDF(
  1298 + qpdf.warn(qpdf.damagedPDF(
1319 1299 m->file->tell(), "stream keyword followed by carriage return only"));
1320 1300 }
1321 1301 }
1322 1302 return;
1323 1303 }
1324 1304 if (!util::is_space(ch)) {
1325   - QTC::TC("qpdf", "QPDF stream without newline");
1326 1305 m->file->unreadCh(ch);
1327   - warn(damagedPDF(
  1306 + qpdf.warn(qpdf.damagedPDF(
1328 1307 m->file->tell(), "stream keyword not followed by proper line terminator"));
1329 1308 return;
1330 1309 }
1331   - warn(damagedPDF(m->file->tell(), "stream keyword followed by extraneous whitespace"));
  1310 + qpdf.warn(
  1311 + qpdf.damagedPDF(m->file->tell(), "stream keyword followed by extraneous whitespace"));
1332 1312 }
1333 1313 }
1334 1314  
1335 1315 QPDFObjectHandle
1336   -QPDF::readObjectInStream(is::OffsetBuffer& input, int stream_id, int obj_id)
  1316 +Objects::readObjectInStream(is::OffsetBuffer& input, int stream_id, int obj_id)
1337 1317 {
1338   - auto [object, empty] = QPDFParser::parse(input, stream_id, obj_id, m->tokenizer, *this);
  1318 + auto [object, empty] = QPDFParser::parse(input, stream_id, obj_id, m->tokenizer, qpdf);
1339 1319 if (empty) {
1340 1320 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1341 1321 // actual PDF files and Adobe Reader appears to ignore them.
1342   - warn(QPDFExc(
  1322 + qpdf.warn(QPDFExc(
1343 1323 qpdf_e_damaged_pdf,
1344 1324 m->file->getName() + " object stream " + std::to_string(stream_id),
1345 1325 +"object " + std::to_string(obj_id) + " 0, offset " +
... ... @@ -1354,7 +1334,7 @@ bool
1354 1334 QPDF::findEndstream()
1355 1335 {
1356 1336 // Find endstream or endobj. Position the input at that token.
1357   - auto t = readToken(*m->file, 20);
  1337 + auto t = m->objects.readToken(*m->file, 20);
1358 1338 if (t.isWord("endobj") || t.isWord("endstream")) {
1359 1339 m->file->seek(m->file->getLastOffset(), SEEK_SET);
1360 1340 return true;
... ... @@ -1363,13 +1343,13 @@ QPDF::findEndstream()
1363 1343 }
1364 1344  
1365 1345 size_t
1366   -QPDF::recoverStreamLength(
  1346 +Objects::recoverStreamLength(
1367 1347 std::shared_ptr<InputSource> input, QPDFObjGen og, qpdf_offset_t stream_offset)
1368 1348 {
1369 1349 // Try to reconstruct stream length by looking for endstream or endobj
1370   - warn(damagedPDF(*input, stream_offset, "attempting to recover stream length"));
  1350 + qpdf.warn(qpdf.damagedPDF(*input, stream_offset, "attempting to recover stream length"));
1371 1351  
1372   - PatternFinder ef(*this, &QPDF::findEndstream);
  1352 + PatternFinder ef(qpdf, &QPDF::findEndstream);
1373 1353 size_t length = 0;
1374 1354 if (m->file->findFirst("end", stream_offset, 0, ef)) {
1375 1355 length = toS(m->file->tell() - stream_offset);
... ... @@ -1401,65 +1381,58 @@ QPDF::recoverStreamLength(
1401 1381 // found endstream\nendobj within the space allowed for this object, so we're probably
1402 1382 // in good shape.
1403 1383 } else {
1404   - QTC::TC("qpdf", "QPDF found wrong endstream in recovery");
1405 1384 length = 0;
1406 1385 }
1407 1386 }
1408 1387  
1409 1388 if (length == 0) {
1410   - warn(damagedPDF(
  1389 + qpdf.warn(qpdf.damagedPDF(
1411 1390 *input, stream_offset, "unable to recover stream data; treating stream as empty"));
1412 1391 } else {
1413   - warn(damagedPDF(
  1392 + qpdf.warn(qpdf.damagedPDF(
1414 1393 *input, stream_offset, "recovered stream length: " + std::to_string(length)));
1415 1394 }
1416 1395  
1417   - QTC::TC("qpdf", "QPDF recovered stream length");
1418 1396 return length;
1419 1397 }
1420 1398  
1421 1399 QPDFTokenizer::Token
1422   -QPDF::readToken(InputSource& input, size_t max_len)
  1400 +Objects::readToken(InputSource& input, size_t max_len)
1423 1401 {
1424 1402 return m->tokenizer.readToken(input, m->last_object_description, true, max_len);
1425 1403 }
1426 1404  
1427 1405 QPDFObjGen
1428   -QPDF::read_object_start(qpdf_offset_t offset)
  1406 +Objects::read_object_start(qpdf_offset_t offset)
1429 1407 {
1430 1408 m->file->seek(offset, SEEK_SET);
1431 1409 QPDFTokenizer::Token tobjid = readToken(*m->file);
1432 1410 bool objidok = tobjid.isInteger();
1433   - QTC::TC("qpdf", "QPDF check objid", objidok ? 1 : 0);
1434 1411 if (!objidok) {
1435   - QTC::TC("qpdf", "QPDF expected n n obj");
1436   - throw damagedPDF(offset, "expected n n obj");
  1412 + throw qpdf.damagedPDF(offset, "expected n n obj");
1437 1413 }
1438 1414 QPDFTokenizer::Token tgen = readToken(*m->file);
1439 1415 bool genok = tgen.isInteger();
1440   - QTC::TC("qpdf", "QPDF check generation", genok ? 1 : 0);
1441 1416 if (!genok) {
1442   - throw damagedPDF(offset, "expected n n obj");
  1417 + throw qpdf.damagedPDF(offset, "expected n n obj");
1443 1418 }
1444 1419 QPDFTokenizer::Token tobj = readToken(*m->file);
1445 1420  
1446 1421 bool objok = tobj.isWord("obj");
1447   - QTC::TC("qpdf", "QPDF check obj", objok ? 1 : 0);
1448 1422  
1449 1423 if (!objok) {
1450   - throw damagedPDF(offset, "expected n n obj");
  1424 + throw qpdf.damagedPDF(offset, "expected n n obj");
1451 1425 }
1452 1426 int objid = QUtil::string_to_int(tobjid.getValue().c_str());
1453 1427 int generation = QUtil::string_to_int(tgen.getValue().c_str());
1454 1428 if (objid == 0) {
1455   - QTC::TC("qpdf", "QPDF object id 0");
1456   - throw damagedPDF(offset, "object with ID 0");
  1429 + throw qpdf.damagedPDF(offset, "object with ID 0");
1457 1430 }
1458 1431 return {objid, generation};
1459 1432 }
1460 1433  
1461 1434 void
1462   -QPDF::readObjectAtOffset(
  1435 +Objects::readObjectAtOffset(
1463 1436 bool try_recovery, qpdf_offset_t offset, std::string const& description, QPDFObjGen exp_og)
1464 1437 {
1465 1438 QPDFObjGen og;
... ... @@ -1474,22 +1447,20 @@ QPDF::readObjectAtOffset(
1474 1447 // "0000000000 00000 n", which is not correct, but it won't hurt anything for us to ignore
1475 1448 // these.
1476 1449 if (offset == 0) {
1477   - QTC::TC("qpdf", "QPDF bogus 0 offset", 0);
1478   - warn(damagedPDF(-1, "object has offset 0"));
  1450 + qpdf.warn(qpdf.damagedPDF(-1, "object has offset 0"));
1479 1451 return;
1480 1452 }
1481 1453  
1482 1454 try {
1483 1455 og = read_object_start(offset);
1484 1456 if (exp_og != og) {
1485   - QTC::TC("qpdf", "QPDF err wrong objid/generation");
1486   - QPDFExc e = damagedPDF(offset, "expected " + exp_og.unparse(' ') + " obj");
  1457 + QPDFExc e = qpdf.damagedPDF(offset, "expected " + exp_og.unparse(' ') + " obj");
1487 1458 if (try_recovery) {
1488 1459 // Will be retried below
1489 1460 throw e;
1490 1461 } else {
1491 1462 // We can try reading the object anyway even if the ID doesn't match.
1492   - warn(e);
  1463 + qpdf.warn(e);
1493 1464 }
1494 1465 }
1495 1466 } catch (QPDFExc& e) {
... ... @@ -1501,11 +1472,9 @@ QPDF::readObjectAtOffset(
1501 1472 if (m->xref_table.contains(exp_og) && m->xref_table[exp_og].getType() == 1) {
1502 1473 qpdf_offset_t new_offset = m->xref_table[exp_og].getOffset();
1503 1474 readObjectAtOffset(false, new_offset, description, exp_og);
1504   - QTC::TC("qpdf", "QPDF recovered in readObjectAtOffset");
1505 1475 return;
1506 1476 }
1507   - QTC::TC("qpdf", "QPDF object gone after xref reconstruction");
1508   - warn(damagedPDF(
  1477 + qpdf.warn(qpdf.damagedPDF(
1509 1478 "",
1510 1479 -1,
1511 1480 ("object " + exp_og.unparse(' ') +
... ... @@ -1524,24 +1493,24 @@ QPDF::readObjectAtOffset(
1524 1493 while (true) {
1525 1494 char ch;
1526 1495 if (!m->file->read(&ch, 1)) {
1527   - throw damagedPDF(m->file->tell(), "EOF after endobj");
  1496 + throw qpdf.damagedPDF(m->file->tell(), "EOF after endobj");
1528 1497 }
1529 1498 if (!isspace(static_cast<unsigned char>(ch))) {
1530 1499 m->file->seek(-1, SEEK_CUR);
1531 1500 break;
1532 1501 }
1533 1502 }
1534   - updateCache(og, oh.getObj(), end_before_space, m->file->tell());
  1503 + m->objects.updateCache(og, oh.getObj(), end_before_space, m->file->tell());
1535 1504 }
1536 1505  
1537 1506 QPDFObjectHandle
1538   -QPDF::readObjectAtOffset(
  1507 +Objects::readObjectAtOffset(
1539 1508 qpdf_offset_t offset, std::string const& description, bool skip_cache_if_in_xref)
1540 1509 {
1541 1510 auto og = read_object_start(offset);
1542 1511 auto oh = readObject(description, og);
1543 1512  
1544   - if (!isUnresolved(og)) {
  1513 + if (!m->objects.isUnresolved(og)) {
1545 1514 return oh;
1546 1515 }
1547 1516  
... ... @@ -1583,20 +1552,20 @@ QPDF::readObjectAtOffset(
1583 1552 while (true) {
1584 1553 char ch;
1585 1554 if (!m->file->read(&ch, 1)) {
1586   - throw damagedPDF(m->file->tell(), "EOF after endobj");
  1555 + throw qpdf.damagedPDF(m->file->tell(), "EOF after endobj");
1587 1556 }
1588 1557 if (!isspace(static_cast<unsigned char>(ch))) {
1589 1558 m->file->seek(-1, SEEK_CUR);
1590 1559 break;
1591 1560 }
1592 1561 }
1593   - updateCache(og, oh.getObj(), end_before_space, m->file->tell());
  1562 + m->objects.updateCache(og, oh.getObj(), end_before_space, m->file->tell());
1594 1563  
1595 1564 return oh;
1596 1565 }
1597 1566  
1598 1567 std::shared_ptr<QPDFObject> const&
1599   -QPDF::resolve(QPDFObjGen og)
  1568 +Objects::resolve(QPDFObjGen og)
1600 1569 {
1601 1570 if (!isUnresolved(og)) {
1602 1571 return m->obj_cache[og].object;
... ... @@ -1605,12 +1574,11 @@ QPDF::resolve(QPDFObjGen og)
1605 1574 if (m->resolving.contains(og)) {
1606 1575 // This can happen if an object references itself directly or indirectly in some key that
1607 1576 // has to be resolved during object parsing, such as stream length.
1608   - QTC::TC("qpdf", "QPDF recursion loop in resolve");
1609   - warn(damagedPDF("", "loop detected resolving object " + og.unparse(' ')));
  1577 + qpdf.warn(qpdf.damagedPDF("", "loop detected resolving object " + og.unparse(' ')));
1610 1578 updateCache(og, QPDFObject::create<QPDF_Null>(), -1, -1);
1611 1579 return m->obj_cache[og].object;
1612 1580 }
1613   - ResolveRecorder rr(*this, og);
  1581 + ResolveRecorder rr(qpdf, og);
1614 1582  
1615 1583 if (m->xref_table.contains(og)) {
1616 1584 QPDFXRefEntry const& entry = m->xref_table[og];
... ... @@ -1626,30 +1594,29 @@ QPDF::resolve(QPDFObjGen og)
1626 1594 break;
1627 1595  
1628 1596 default:
1629   - throw damagedPDF(
  1597 + throw qpdf.damagedPDF(
1630 1598 "", -1, ("object " + og.unparse('/') + " has unexpected xref entry type"));
1631 1599 }
1632 1600 } catch (QPDFExc& e) {
1633   - warn(e);
  1601 + qpdf.warn(e);
1634 1602 } catch (std::exception& e) {
1635   - warn(damagedPDF(
  1603 + qpdf.warn(qpdf.damagedPDF(
1636 1604 "", -1, ("object " + og.unparse('/') + ": error reading object: " + e.what())));
1637 1605 }
1638 1606 }
1639 1607  
1640 1608 if (isUnresolved(og)) {
1641 1609 // PDF spec says unknown objects resolve to the null object.
1642   - QTC::TC("qpdf", "QPDF resolve failure to null");
1643 1610 updateCache(og, QPDFObject::create<QPDF_Null>(), -1, -1);
1644 1611 }
1645 1612  
1646 1613 auto& result(m->obj_cache[og].object);
1647   - result->setDefaultDescription(this, og);
  1614 + result->setDefaultDescription(&qpdf, og);
1648 1615 return result;
1649 1616 }
1650 1617  
1651 1618 void
1652   -QPDF::resolveObjectsInStream(int obj_stream_number)
  1619 +Objects::resolveObjectsInStream(int obj_stream_number)
1653 1620 {
1654 1621 auto damaged =
1655 1622 [this, obj_stream_number](int id, qpdf_offset_t offset, std::string const& msg) -> QPDFExc {
... ... @@ -1667,9 +1634,9 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1667 1634 }
1668 1635 m->resolved_object_streams.insert(obj_stream_number);
1669 1636 // Force resolution of object stream
1670   - auto obj_stream = getObject(obj_stream_number, 0).as_stream();
  1637 + auto obj_stream = qpdf.getObject(obj_stream_number, 0).as_stream();
1671 1638 if (!obj_stream) {
1672   - throw damagedPDF(
  1639 + throw qpdf.damagedPDF(
1673 1640 "object " + std::to_string(obj_stream_number) + " 0",
1674 1641 "supposed object stream " + std::to_string(obj_stream_number) + " is not a stream");
1675 1642 }
... ... @@ -1682,8 +1649,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1682 1649  
1683 1650 QPDFObjectHandle dict = obj_stream.getDict();
1684 1651 if (!dict.isDictionaryOfType("/ObjStm")) {
1685   - QTC::TC("qpdf", "QPDF ERR object stream with wrong type");
1686   - warn(damagedPDF(
  1652 + qpdf.warn(qpdf.damagedPDF(
1687 1653 "object " + std::to_string(obj_stream_number) + " 0",
1688 1654 "supposed object stream " + std::to_string(obj_stream_number) + " has wrong type"));
1689 1655 }
... ... @@ -1691,7 +1657,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1691 1657 unsigned int n{0};
1692 1658 int first{0};
1693 1659 if (!(dict.getKey("/N").getValueAsUInt(n) && dict.getKey("/First").getValueAsInt(first))) {
1694   - throw damagedPDF(
  1660 + throw qpdf.damagedPDF(
1695 1661 "object " + std::to_string(obj_stream_number) + " 0",
1696 1662 "object stream " + std::to_string(obj_stream_number) + " has incorrect keys");
1697 1663 }
... ... @@ -1708,7 +1674,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1708 1674 auto b_start = stream_data.data();
1709 1675  
1710 1676 if (first >= end_offset) {
1711   - throw damagedPDF(
  1677 + throw qpdf.damagedPDF(
1712 1678 "object " + std::to_string(obj_stream_number) + " 0",
1713 1679 "object stream " + std::to_string(obj_stream_number) + " has invalid /First entry");
1714 1680 }
... ... @@ -1728,20 +1694,17 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1728 1694 long long offset = QUtil::string_to_int(toffset.getValue().c_str());
1729 1695  
1730 1696 if (num == obj_stream_number) {
1731   - QTC::TC("qpdf", "QPDF ignore self-referential object stream");
1732   - warn(damaged(num, id_offset, "object stream claims to contain itself"));
  1697 + qpdf.warn(damaged(num, id_offset, "object stream claims to contain itself"));
1733 1698 continue;
1734 1699 }
1735 1700  
1736 1701 if (num < 1) {
1737   - QTC::TC("qpdf", "QPDF object stream contains id < 1");
1738   - warn(damaged(num, id_offset, "object id is invalid"s));
  1702 + qpdf.warn(damaged(num, id_offset, "object id is invalid"s));
1739 1703 continue;
1740 1704 }
1741 1705  
1742 1706 if (offset <= last_offset) {
1743   - QTC::TC("qpdf", "QPDF object stream offsets not increasing");
1744   - warn(damaged(
  1707 + qpdf.warn(damaged(
1745 1708 num,
1746 1709 input.getLastOffset(),
1747 1710 "offset " + std::to_string(offset) +
... ... @@ -1755,7 +1718,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1755 1718 }
1756 1719  
1757 1720 if (first + offset >= end_offset) {
1758   - warn(damaged(
  1721 + qpdf.warn(damaged(
1759 1722 num, input.getLastOffset(), "offset " + std::to_string(offset) + " is too large"));
1760 1723 continue;
1761 1724 }
... ... @@ -1796,21 +1759,21 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1796 1759 }
1797 1760  
1798 1761 QPDFObjectHandle
1799   -QPDF::newIndirect(QPDFObjGen og, std::shared_ptr<QPDFObject> const& obj)
  1762 +Objects::newIndirect(QPDFObjGen og, std::shared_ptr<QPDFObject> const& obj)
1800 1763 {
1801   - obj->setDefaultDescription(this, og);
  1764 + obj->setDefaultDescription(&qpdf, og);
1802 1765 return {obj};
1803 1766 }
1804 1767  
1805 1768 void
1806   -QPDF::updateCache(
  1769 +Objects::updateCache(
1807 1770 QPDFObjGen og,
1808 1771 std::shared_ptr<QPDFObject> const& object,
1809 1772 qpdf_offset_t end_before_space,
1810 1773 qpdf_offset_t end_after_space,
1811 1774 bool destroy)
1812 1775 {
1813   - object->setObjGen(this, og);
  1776 + object->setObjGen(&qpdf, og);
1814 1777 if (isCached(og)) {
1815 1778 auto& cache = m->obj_cache[og];
1816 1779 object->move_to(cache.object, destroy);
... ... @@ -1822,21 +1785,21 @@ QPDF::updateCache(
1822 1785 }
1823 1786  
1824 1787 bool
1825   -QPDF::isCached(QPDFObjGen og)
  1788 +Objects::isCached(QPDFObjGen og)
1826 1789 {
1827 1790 return m->obj_cache.contains(og);
1828 1791 }
1829 1792  
1830 1793 bool
1831   -QPDF::isUnresolved(QPDFObjGen og)
  1794 +Objects::isUnresolved(QPDFObjGen og)
1832 1795 {
1833 1796 return !isCached(og) || m->obj_cache[og].object->isUnresolved();
1834 1797 }
1835 1798  
1836 1799 QPDFObjGen
1837   -QPDF::nextObjGen()
  1800 +Objects::nextObjGen()
1838 1801 {
1839   - int max_objid = toI(getObjectCount());
  1802 + int max_objid = toI(qpdf.getObjectCount());
1840 1803 if (max_objid == std::numeric_limits<int>::max()) {
1841 1804 throw std::range_error("max object id is too high to create new objects");
1842 1805 }
... ... @@ -1844,7 +1807,7 @@ QPDF::nextObjGen()
1844 1807 }
1845 1808  
1846 1809 QPDFObjectHandle
1847   -QPDF::makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj)
  1810 +Objects::makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj)
1848 1811 {
1849 1812 QPDFObjGen next{nextObjGen()};
1850 1813 m->obj_cache[next] = ObjCache(obj, -1, -1);
... ... @@ -1857,11 +1820,11 @@ QPDF::makeIndirectObject(QPDFObjectHandle oh)
1857 1820 if (!oh) {
1858 1821 throw std::logic_error("attempted to make an uninitialized QPDFObjectHandle indirect");
1859 1822 }
1860   - return makeIndirectFromQPDFObject(oh.getObj());
  1823 + return m->objects.makeIndirectFromQPDFObject(oh.getObj());
1861 1824 }
1862 1825  
1863 1826 std::shared_ptr<QPDFObject>
1864   -QPDF::getObjectForParser(int id, int gen, bool parse_pdf)
  1827 +Objects::getObjectForParser(int id, int gen, bool parse_pdf)
1865 1828 {
1866 1829 // This method is called by the parser and therefore must not resolve any objects.
1867 1830 auto og = QPDFObjGen(id, gen);
... ... @@ -1869,25 +1832,25 @@ QPDF::getObjectForParser(int id, int gen, bool parse_pdf)
1869 1832 return iter->second.object;
1870 1833 }
1871 1834 if (m->xref_table.contains(og) || (!m->parsed && og.getObj() < m->xref_table_max_id)) {
1872   - return m->obj_cache.insert({og, QPDFObject::create<QPDF_Unresolved>(this, og)})
  1835 + return m->obj_cache.insert({og, QPDFObject::create<QPDF_Unresolved>(&qpdf, og)})
1873 1836 .first->second.object;
1874 1837 }
1875 1838 if (parse_pdf) {
1876 1839 return QPDFObject::create<QPDF_Null>();
1877 1840 }
1878   - return m->obj_cache.insert({og, QPDFObject::create<QPDF_Null>(this, og)}).first->second.object;
  1841 + return m->obj_cache.insert({og, QPDFObject::create<QPDF_Null>(&qpdf, og)}).first->second.object;
1879 1842 }
1880 1843  
1881 1844 std::shared_ptr<QPDFObject>
1882   -QPDF::getObjectForJSON(int id, int gen)
  1845 +Objects::getObjectForJSON(int id, int gen)
1883 1846 {
1884 1847 auto og = QPDFObjGen(id, gen);
1885 1848 auto [it, inserted] = m->obj_cache.try_emplace(og);
1886 1849 auto& obj = it->second.object;
1887 1850 if (inserted) {
1888 1851 obj = (m->parsed && !m->xref_table.contains(og))
1889   - ? QPDFObject::create<QPDF_Null>(this, og)
1890   - : QPDFObject::create<QPDF_Unresolved>(this, og);
  1852 + ? QPDFObject::create<QPDF_Null>(&qpdf, og)
  1853 + : QPDFObject::create<QPDF_Unresolved>(&qpdf, og);
1891 1854 }
1892 1855 return obj;
1893 1856 }
... ... @@ -1916,10 +1879,9 @@ void
1916 1879 QPDF::replaceObject(QPDFObjGen og, QPDFObjectHandle oh)
1917 1880 {
1918 1881 if (!oh || (oh.isIndirect() && !(oh.isStream() && oh.getObjGen() == og))) {
1919   - QTC::TC("qpdf", "QPDF replaceObject called with indirect object");
1920 1882 throw std::logic_error("QPDF::replaceObject called with indirect object handle");
1921 1883 }
1922   - updateCache(og, oh.getObj(), -1, -1, false);
  1884 + m->objects.updateCache(og, oh.getObj(), -1, -1, false);
1923 1885 }
1924 1886  
1925 1887 void
... ... @@ -1955,13 +1917,13 @@ void
1955 1917 QPDF::swapObjects(QPDFObjGen og1, QPDFObjGen og2)
1956 1918 {
1957 1919 // Force objects to be read from the input source if needed, then swap them in the cache.
1958   - resolve(og1);
1959   - resolve(og2);
  1920 + m->objects.resolve(og1);
  1921 + m->objects.resolve(og2);
1960 1922 m->obj_cache[og1].object->swapWith(m->obj_cache[og2].object);
1961 1923 }
1962 1924  
1963 1925 size_t
1964   -QPDF::tableSize()
  1926 +Objects::tableSize()
1965 1927 {
1966 1928 // If obj_cache is dense, accommodate all object in tables,else accommodate only original
1967 1929 // objects.
... ... @@ -1972,7 +1934,7 @@ QPDF::tableSize()
1972 1934 // Temporary fix. Long-term solution is
1973 1935 // - QPDFObjGen to enforce objgens are valid and sensible
1974 1936 // - xref table and obj cache to protect against insertion of impossibly large obj ids
1975   - stopOnError("Impossibly large object id encountered.");
  1937 + qpdf.stopOnError("Impossibly large object id encountered.");
1976 1938 }
1977 1939 if (max_obj < 1.1 * std::max(toI(m->obj_cache.size()), max_xref)) {
1978 1940 return toS(++max_obj);
... ... @@ -1981,20 +1943,20 @@ QPDF::tableSize()
1981 1943 }
1982 1944  
1983 1945 std::vector<QPDFObjGen>
1984   -QPDF::getCompressibleObjVector()
  1946 +Objects::getCompressibleObjVector()
1985 1947 {
1986 1948 return getCompressibleObjGens<QPDFObjGen>();
1987 1949 }
1988 1950  
1989 1951 std::vector<bool>
1990   -QPDF::getCompressibleObjSet()
  1952 +Objects::getCompressibleObjSet()
1991 1953 {
1992 1954 return getCompressibleObjGens<bool>();
1993 1955 }
1994 1956  
1995 1957 template <typename T>
1996 1958 std::vector<T>
1997   -QPDF::getCompressibleObjGens()
  1959 +Objects::getCompressibleObjGens()
1998 1960 {
1999 1961 // Return a list of objects that are allowed to be in object streams. Walk through the objects
2000 1962 // by traversing the document from the root, including a traversal of the pages tree. This
... ... @@ -2006,11 +1968,11 @@ QPDF::getCompressibleObjGens()
2006 1968 QPDFObjectHandle encryption_dict = m->trailer.getKey("/Encrypt");
2007 1969 QPDFObjGen encryption_dict_og = encryption_dict.getObjGen();
2008 1970  
2009   - const size_t max_obj = getObjectCount();
  1971 + const size_t max_obj = qpdf.getObjectCount();
2010 1972 std::vector<bool> visited(max_obj, false);
2011 1973 std::vector<QPDFObjectHandle> queue;
2012 1974 queue.reserve(512);
2013   - queue.push_back(m->trailer);
  1975 + queue.emplace_back(m->trailer);
2014 1976 std::vector<T> result;
2015 1977 if constexpr (std::is_same_v<T, QPDFObjGen>) {
2016 1978 result.reserve(m->obj_cache.size());
... ... @@ -2030,7 +1992,6 @@ QPDF::getCompressibleObjGens()
2030 1992 "unexpected object id encountered in getCompressibleObjGens");
2031 1993 }
2032 1994 if (visited[id]) {
2033   - QTC::TC("qpdf", "QPDF loop detected traversing objects");
2034 1995 continue;
2035 1996 }
2036 1997  
... ... @@ -2039,7 +2000,7 @@ QPDF::getCompressibleObjGens()
2039 2000 // in the queue.
2040 2001 auto upper = m->obj_cache.upper_bound(og);
2041 2002 if (upper != m->obj_cache.end() && upper->first.getObj() == og.getObj()) {
2042   - removeObject(og);
  2003 + qpdf.removeObject(og);
2043 2004 continue;
2044 2005 }
2045 2006  
... ...
libqpdf/QPDF_optimization.cc
... ... @@ -7,6 +7,9 @@
7 7 #include <qpdf/QPDFWriter_private.hh>
8 8 #include <qpdf/QTC.hh>
9 9  
  10 +using Lin = QPDF::Doc::Linearization;
  11 +using Pages = QPDF::Doc::Pages;
  12 +
10 13 QPDF::ObjUser::ObjUser(user_e type) :
11 14 ou_type(type)
12 15 {
... ... @@ -58,11 +61,11 @@ QPDF::optimize(
58 61 bool allow_changes,
59 62 std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
60 63 {
61   - optimize_internal(object_stream_data, allow_changes, skip_stream_parameters);
  64 + m->lin.optimize_internal(object_stream_data, allow_changes, skip_stream_parameters);
62 65 }
63 66  
64 67 void
65   -QPDF::optimize(
  68 +Lin::optimize(
66 69 QPDFWriter::ObjTable const& obj, std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
67 70 {
68 71 optimize_internal(obj, true, skip_stream_parameters);
... ... @@ -70,7 +73,7 @@ QPDF::optimize(
70 73  
71 74 template <typename T>
72 75 void
73   -QPDF::optimize_internal(
  76 +Lin::optimize_internal(
74 77 T const& object_stream_data,
75 78 bool allow_changes,
76 79 std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
... ... @@ -82,18 +85,17 @@ QPDF::optimize_internal(
82 85  
83 86 // The PDF specification indicates that /Outlines is supposed to be an indirect reference. Force
84 87 // it to be so if it exists and is direct. (This has been seen in the wild.)
85   - QPDFObjectHandle root = getRoot();
  88 + QPDFObjectHandle root = qpdf.getRoot();
86 89 if (root.getKey("/Outlines").isDictionary()) {
87 90 QPDFObjectHandle outlines = root.getKey("/Outlines");
88 91 if (!outlines.isIndirect()) {
89   - QTC::TC("qpdf", "QPDF_optimization indirect outlines");
90   - root.replaceKey("/Outlines", makeIndirectObject(outlines));
  92 + root.replaceKey("/Outlines", qpdf.makeIndirectObject(outlines));
91 93 }
92 94 }
93 95  
94 96 // Traverse pages tree pushing all inherited resources down to the page level. This also
95 97 // initializes m->all_pages.
96   - pushInheritedAttributesToPage(allow_changes, false);
  98 + m->pages.pushInheritedAttributesToPage(allow_changes, false);
97 99  
98 100 // Traverse pages
99 101 size_t n = m->all_pages.size();
... ... @@ -136,11 +138,11 @@ void
136 138 QPDF::pushInheritedAttributesToPage()
137 139 {
138 140 // Public API should not have access to allow_changes.
139   - pushInheritedAttributesToPage(true, false);
  141 + m->pages.pushInheritedAttributesToPage(true, false);
140 142 }
141 143  
142 144 void
143   -QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
  145 +Pages::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
144 146 {
145 147 // Traverse pages tree pushing all inherited resources down to the page level.
146 148  
... ... @@ -152,7 +154,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
152 154  
153 155 // Calling getAllPages() resolves any duplicated page objects, repairs broken nodes, and detects
154 156 // loops, so we don't have to do those activities here.
155   - getAllPages();
  157 + qpdf.getAllPages();
156 158  
157 159 // key_ancestors is a mapping of page attribute keys to a stack of Pages nodes that contain
158 160 // values for them.
... ... @@ -171,7 +173,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
171 173 }
172 174  
173 175 void
174   -QPDF::pushInheritedAttributesToPageInternal(
  176 +Pages ::pushInheritedAttributesToPageInternal(
175 177 QPDFObjectHandle cur_pages,
176 178 std::map<std::string, std::vector<QPDFObjectHandle>>& key_ancestors,
177 179 bool allow_changes,
... ... @@ -183,8 +185,7 @@ QPDF::pushInheritedAttributesToPageInternal(
183 185  
184 186 std::set<std::string> inheritable_keys;
185 187 for (auto const& key: cur_pages.getKeys()) {
186   - if ((key == "/MediaBox") || (key == "/CropBox") || (key == "/Resources") ||
187   - (key == "/Rotate")) {
  188 + if (key == "/MediaBox" || key == "/CropBox" || key == "/Resources" || key == "/Rotate") {
188 189 if (!allow_changes) {
189 190 throw QPDFExc(
190 191 qpdf_e_internal,
... ... @@ -197,34 +198,29 @@ QPDF::pushInheritedAttributesToPageInternal(
197 198 // This is an inheritable resource
198 199 inheritable_keys.insert(key);
199 200 QPDFObjectHandle oh = cur_pages.getKey(key);
200   - QTC::TC("qpdf", "QPDF opt direct pages resource", oh.isIndirect() ? 0 : 1);
201   - if (!oh.isIndirect()) {
  201 + QTC::TC("qpdf", "QPDF opt direct pages resource", oh.indirect() ? 0 : 1);
  202 + if (!oh.indirect()) {
202 203 if (!oh.isScalar()) {
203 204 // Replace shared direct object non-scalar resources with indirect objects to
204 205 // avoid copying large structures around.
205   - cur_pages.replaceKey(key, makeIndirectObject(oh));
  206 + cur_pages.replaceKey(key, qpdf.makeIndirectObject(oh));
206 207 oh = cur_pages.getKey(key);
207 208 } else {
208 209 // It's okay to copy scalars.
209   - QTC::TC("qpdf", "QPDF opt inherited scalar");
210 210 }
211 211 }
212 212 key_ancestors[key].push_back(oh);
213 213 if (key_ancestors[key].size() > 1) {
214   - QTC::TC("qpdf", "QPDF opt key ancestors depth > 1");
215 214 }
216 215 // Remove this resource from this node. It will be reattached at the page level.
217 216 cur_pages.removeKey(key);
218   - } else if (!((key == "/Type") || (key == "/Parent") || (key == "/Kids") ||
219   - (key == "/Count"))) {
  217 + } else if (!(key == "/Type" || key == "/Parent" || key == "/Kids" || key == "/Count")) {
220 218 // Warn when flattening, but not if the key is at the top level (i.e. "/Parent" not
221 219 // set), as we don't change these; but flattening removes intermediate /Pages nodes.
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());
225   - warn(
  220 + if (warn_skipped_keys && cur_pages.hasKey("/Parent")) {
  221 + qpdf.warn(
226 222 qpdf_e_pages,
227   - m->last_object_description,
  223 + "Pages object: object " + cur_pages.id_gen().unparse(' '),
228 224 0,
229 225 ("Unknown key " + key +
230 226 " in /Pages object is being discarded as a result of flattening the /Pages "
... ... @@ -245,7 +241,6 @@ QPDF::pushInheritedAttributesToPageInternal(
245 241 for (auto const& iter: key_ancestors) {
246 242 std::string const& key = iter.first;
247 243 if (!kid.hasKey(key)) {
248   - QTC::TC("qpdf", "QPDF opt resource inherited");
249 244 kid.replaceKey(key, iter.second.back());
250 245 } else {
251 246 QTC::TC("qpdf", "QPDF opt page resource hides ancestor");
... ... @@ -259,11 +254,9 @@ QPDF::pushInheritedAttributesToPageInternal(
259 254 // which inheritable attributes are available.
260 255  
261 256 if (!inheritable_keys.empty()) {
262   - QTC::TC("qpdf", "QPDF opt inheritable keys");
263 257 for (auto const& key: inheritable_keys) {
264 258 key_ancestors[key].pop_back();
265 259 if (key_ancestors[key].empty()) {
266   - QTC::TC("qpdf", "QPDF opt erase empty key ancestor");
267 260 key_ancestors.erase(key);
268 261 }
269 262 }
... ... @@ -273,7 +266,7 @@ QPDF::pushInheritedAttributesToPageInternal(
273 266 }
274 267  
275 268 void
276   -QPDF::updateObjectMaps(
  269 +Lin::updateObjectMaps(
277 270 ObjUser const& first_ou,
278 271 QPDFObjectHandle first_oh,
279 272 std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
... ... @@ -346,7 +339,7 @@ QPDF::updateObjectMaps(
346 339 }
347 340  
348 341 void
349   -QPDF::filterCompressedObjects(std::map<int, int> const& object_stream_data)
  342 +Lin::filterCompressedObjects(std::map<int, int> const& object_stream_data)
350 343 {
351 344 if (object_stream_data.empty()) {
352 345 return;
... ... @@ -390,7 +383,7 @@ QPDF::filterCompressedObjects(std::map&lt;int, int&gt; const&amp; object_stream_data)
390 383 }
391 384  
392 385 void
393   -QPDF::filterCompressedObjects(QPDFWriter::ObjTable const& obj)
  386 +Lin::filterCompressedObjects(QPDFWriter::ObjTable const& obj)
394 387 {
395 388 if (obj.getStreamsEmpty()) {
396 389 return;
... ...
libqpdf/QPDF_pages.cc
... ... @@ -37,6 +37,8 @@
37 37 // insertPage, and removePage, along with methods they call, are concerned with it. Everything else
38 38 // goes through one of those methods.
39 39  
  40 +using Pages = QPDF::Doc::Pages;
  41 +
40 42 std::vector<QPDFObjectHandle> const&
41 43 QPDF::getAllPages()
42 44 {
... ... @@ -75,14 +77,14 @@ QPDF::getAllPages()
75 77 qpdf_e_pages, m->file->getName(), "", 0, "root of pages tree has no /Kids array");
76 78 }
77 79 try {
78   - getAllPagesInternal(pages, visited, seen, false, false);
  80 + m->pages.getAllPagesInternal(pages, visited, seen, false, false);
79 81 } catch (...) {
80 82 m->all_pages.clear();
81 83 m->invalid_page_found = false;
82 84 throw;
83 85 }
84 86 if (m->invalid_page_found) {
85   - flattenPagesTree();
  87 + m->pages.flattenPagesTree();
86 88 m->invalid_page_found = false;
87 89 }
88 90 }
... ... @@ -90,7 +92,7 @@ QPDF::getAllPages()
90 92 }
91 93  
92 94 void
93   -QPDF::getAllPagesInternal(
  95 +Pages::getAllPagesInternal(
94 96 QPDFObjectHandle cur_node,
95 97 QPDFObjGen::set& visited,
96 98 QPDFObjGen::set& seen,
... ... @@ -139,17 +141,15 @@ QPDF::getAllPagesInternal(
139 141 continue;
140 142 }
141 143 if (!kid.isIndirect()) {
142   - QTC::TC("qpdf", "QPDF handle direct page object");
143 144 cur_node.warn(
144 145 "kid " + std::to_string(i) + " (from 0) is direct; converting to indirect");
145   - kid = makeIndirectObject(kid);
  146 + kid = qpdf.makeIndirectObject(kid);
146 147 ++errors;
147 148 }
148 149 if (kid.hasKey("/Kids")) {
149 150 getAllPagesInternal(kid, visited, seen, media_box, resources);
150 151 } else {
151 152 if (!media_box && !kid.getKey("/MediaBox").isRectangle()) {
152   - QTC::TC("qpdf", "QPDF missing mediabox");
153 153 kid.warn(
154 154 "kid " + std::to_string(i) +
155 155 " (from 0) MediaBox is undefined; setting to letter / ANSI A");
... ... @@ -193,7 +193,6 @@ QPDF::getAllPagesInternal(
193 193 if (!seen.add(kid)) {
194 194 // Make a copy of the page. This does the same as shallowCopyPage in
195 195 // QPDFPageObjectHelper.
196   - QTC::TC("qpdf", "QPDF resolve duplicated page object");
197 196 if (!m->reconstructed_xref) {
198 197 cur_node.warn(
199 198 "kid " + std::to_string(i) +
... ... @@ -201,7 +200,7 @@ QPDF::getAllPagesInternal(
201 200 " creating a new page object as a copy");
202 201 // This needs to be fixed. shallowCopy does not necessarily produce a valid
203 202 // page.
204   - kid = makeIndirectObject(QPDFObjectHandle(kid).shallowCopy());
  203 + kid = qpdf.makeIndirectObject(QPDFObjectHandle(kid).shallowCopy());
205 204 seen.add(kid);
206 205 } else {
207 206 cur_node.warn(
... ... @@ -239,7 +238,6 @@ QPDF::updateAllPagesCache()
239 238 // Force regeneration of the pages cache. We force immediate recalculation of all_pages since
240 239 // users may have references to it that they got from calls to getAllPages(). We can defer
241 240 // recalculation of pageobj_to_pages_pos until needed.
242   - QTC::TC("qpdf", "QPDF updateAllPagesCache");
243 241 m->all_pages.clear();
244 242 m->pageobj_to_pages_pos.clear();
245 243 m->pushed_inherited_attributes_to_pages = false;
... ... @@ -247,7 +245,7 @@ QPDF::updateAllPagesCache()
247 245 }
248 246  
249 247 void
250   -QPDF::flattenPagesTree()
  248 +Pages::flattenPagesTree()
251 249 {
252 250 // If not already done, flatten the /Pages structure and initialize pageobj_to_pages_pos.
253 251  
... ... @@ -259,7 +257,7 @@ QPDF::flattenPagesTree()
259 257 // generated.
260 258 pushInheritedAttributesToPage(true, true);
261 259  
262   - QPDFObjectHandle pages = getRoot().getKey("/Pages");
  260 + QPDFObjectHandle pages = qpdf.getRoot().getKey("/Pages");
263 261  
264 262 size_t const len = m->all_pages.size();
265 263 for (size_t pos = 0; pos < len; ++pos) {
... ... @@ -282,17 +280,16 @@ QPDF::flattenPagesTree()
282 280 }
283 281  
284 282 void
285   -QPDF::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate)
  283 +Pages::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate)
286 284 {
287 285 QPDFObjGen og(obj.getObjGen());
288 286 if (check_duplicate) {
289 287 if (!m->pageobj_to_pages_pos.insert(std::make_pair(og, pos)).second) {
290 288 // The library never calls insertPageobjToPage in a way that causes this to happen.
291   - setLastObjectDescription("page " + std::to_string(pos) + " (numbered from zero)", og);
292 289 throw QPDFExc(
293 290 qpdf_e_pages,
294 291 m->file->getName(),
295   - m->last_object_description,
  292 + "page " + std::to_string(pos) + " (numbered from zero): object " + og.unparse(' '),
296 293 0,
297 294 "duplicate page reference found; this would cause loss of data");
298 295 }
... ... @@ -302,24 +299,22 @@ QPDF::insertPageobjToPage(QPDFObjectHandle const&amp; obj, int pos, bool check_dupli
302 299 }
303 300  
304 301 void
305   -QPDF::insertPage(QPDFObjectHandle newpage, int pos)
  302 +Pages::insertPage(QPDFObjectHandle newpage, int pos)
306 303 {
307 304 // pos is numbered from 0, so pos = 0 inserts at the beginning and pos = npages adds to the end.
308 305  
309 306 flattenPagesTree();
310 307  
311 308 if (!newpage.isIndirect()) {
312   - QTC::TC("qpdf", "QPDF insert non-indirect page");
313   - newpage = makeIndirectObject(newpage);
314   - } else if (newpage.getOwningQPDF() != this) {
315   - QTC::TC("qpdf", "QPDF insert foreign page");
  309 + newpage = qpdf.makeIndirectObject(newpage);
  310 + } else if (newpage.getOwningQPDF() != &qpdf) {
316 311 newpage.getQPDF().pushInheritedAttributesToPage();
317   - newpage = copyForeignObject(newpage);
  312 + newpage = qpdf.copyForeignObject(newpage);
318 313 } else {
319 314 QTC::TC("qpdf", "QPDF insert indirect page");
320 315 }
321 316  
322   - if ((pos < 0) || (toS(pos) > m->all_pages.size())) {
  317 + if (pos < 0 || toS(pos) > m->all_pages.size()) {
323 318 throw std::runtime_error("QPDF::insertPage called with pos out of range");
324 319 }
325 320  
... ... @@ -332,11 +327,10 @@ QPDF::insertPage(QPDFObjectHandle newpage, int pos)
332 327  
333 328 auto og = newpage.getObjGen();
334 329 if (m->pageobj_to_pages_pos.contains(og)) {
335   - QTC::TC("qpdf", "QPDF resolve duplicated page in insert");
336   - newpage = makeIndirectObject(QPDFObjectHandle(newpage).shallowCopy());
  330 + newpage = qpdf.makeIndirectObject(QPDFObjectHandle(newpage).shallowCopy());
337 331 }
338 332  
339   - QPDFObjectHandle pages = getRoot().getKey("/Pages");
  333 + QPDFObjectHandle pages = qpdf.getRoot().getKey("/Pages");
340 334 QPDFObjectHandle kids = pages.getKey("/Kids");
341 335  
342 336 newpage.replaceKey("/Parent", pages);
... ... @@ -370,7 +364,7 @@ QPDF::removePage(QPDFObjectHandle page)
370 364 m->all_pages.erase(m->all_pages.begin() + pos);
371 365 m->pageobj_to_pages_pos.erase(page.getObjGen());
372 366 for (int i = pos; i < npages; ++i) {
373   - insertPageobjToPage(m->all_pages.at(toS(i)), i, false);
  367 + m->pages.insertPageobjToPage(m->all_pages.at(toS(i)), i, false);
374 368 }
375 369 }
376 370  
... ... @@ -381,16 +375,17 @@ QPDF::addPageAt(QPDFObjectHandle newpage, bool before, QPDFObjectHandle refpage)
381 375 if (!before) {
382 376 ++refpos;
383 377 }
384   - insertPage(newpage, refpos);
  378 + m->pages.insertPage(newpage, refpos);
385 379 }
386 380  
387 381 void
388 382 QPDF::addPage(QPDFObjectHandle newpage, bool first)
389 383 {
390 384 if (first) {
391   - insertPage(newpage, 0);
  385 + m->pages.insertPage(newpage, 0);
392 386 } else {
393   - insertPage(newpage, getRoot().getKey("/Pages").getKey("/Count").getIntValueAsInt());
  387 + m->pages.insertPage(
  388 + newpage, getRoot().getKey("/Pages").getKey("/Count").getIntValueAsInt());
394 389 }
395 390 }
396 391  
... ... @@ -403,15 +398,13 @@ QPDF::findPage(QPDFObjectHandle&amp; page)
403 398 int
404 399 QPDF::findPage(QPDFObjGen og)
405 400 {
406   - flattenPagesTree();
  401 + m->pages.flattenPagesTree();
407 402 auto it = m->pageobj_to_pages_pos.find(og);
408 403 if (it == m->pageobj_to_pages_pos.end()) {
409   - QTC::TC("qpdf", "QPDF_pages findPage not found");
410   - setLastObjectDescription("page object", og);
411 404 throw QPDFExc(
412 405 qpdf_e_pages,
413 406 m->file->getName(),
414   - m->last_object_description,
  407 + "page object: object " + og.unparse(' '),
415 408 0,
416 409 "page object not referenced in /Pages tree");
417 410 }
... ...
libqpdf/qpdf/QPDFObjectHandle_private.hh
... ... @@ -577,7 +577,7 @@ namespace qpdf
577 577 return &std::get<T>(obj->value);
578 578 }
579 579 if (std::holds_alternative<QPDF_Unresolved>(obj->value)) {
580   - return BaseHandle(QPDF::Resolver::resolved(obj->qpdf, obj->og)).as<T>();
  580 + return BaseHandle(QPDF::Doc::Resolver::resolved(obj->qpdf, obj->og)).as<T>();
581 581 }
582 582 if (std::holds_alternative<QPDF_Reference>(obj->value)) {
583 583 // see comment in QPDF_Reference.
... ... @@ -676,7 +676,7 @@ namespace qpdf
676 676 return ::ot_uninitialized;
677 677 }
678 678 if (raw_type_code() == ::ot_unresolved) {
679   - return QPDF::Resolver::resolved(obj->qpdf, obj->og)->getTypeCode();
  679 + return QPDF::Doc::Resolver::resolved(obj->qpdf, obj->og)->getTypeCode();
680 680 }
681 681 return raw_type_code();
682 682 }
... ... @@ -688,7 +688,7 @@ namespace qpdf
688 688 return ::ot_uninitialized;
689 689 }
690 690 if (raw_type_code() == ::ot_unresolved) {
691   - return QPDF::Resolver::resolved(obj->qpdf, obj->og)->getTypeCode();
  691 + return QPDF::Doc::Resolver::resolved(obj->qpdf, obj->og)->getTypeCode();
692 692 }
693 693 if (raw_type_code() == ::ot_reference) {
694 694 return std::get<QPDF_Reference>(obj->value).obj->getTypeCode();
... ... @@ -728,7 +728,7 @@ inline qpdf_object_type_e
728 728 QPDFObject::getResolvedTypeCode() const
729 729 {
730 730 if (getTypeCode() == ::ot_unresolved) {
731   - return QPDF::Resolver::resolved(qpdf, og)->getTypeCode();
  731 + return QPDF::Doc::Resolver::resolved(qpdf, og)->getTypeCode();
732 732 }
733 733 if (getTypeCode() == ::ot_reference) {
734 734 return std::get<QPDF_Reference>(value).obj->getTypeCode();
... ...
libqpdf/qpdf/QPDF_private.hh
... ... @@ -13,86 +13,13 @@
13 13  
14 14 using namespace qpdf;
15 15  
16   -// The Resolver class is restricted to QPDFObject so that only it can resolve indirect
17   -// references.
18   -class QPDF::Resolver
  16 +namespace qpdf::is
19 17 {
20   - friend class QPDFObject;
21   - friend class qpdf::BaseHandle;
22   -
23   - private:
24   - static std::shared_ptr<QPDFObject> const&
25   - resolved(QPDF* qpdf, QPDFObjGen og)
26   - {
27   - return qpdf->resolve(og);
28   - }
29   -};
30   -
31   -// StreamCopier class is restricted to QPDFObjectHandle so it can copy stream data.
32   -class QPDF::StreamCopier
33   -{
34   - friend class QPDFObjectHandle;
35   -
36   - private:
37   - static void
38   - copyStreamData(QPDF* qpdf, QPDFObjectHandle const& dest, QPDFObjectHandle const& src)
39   - {
40   - qpdf->copyStreamData(dest, src);
41   - }
42   -};
  18 + class OffsetBuffer;
  19 +} // namespace qpdf::is
43 20  
44   -// The ParseGuard class allows QPDFParser to detect re-entrant parsing. It also provides
45   -// special access to allow the parser to create unresolved objects and dangling references.
46   -class QPDF::ParseGuard
47   -{
48   - friend class QPDFParser;
49   -
50   - private:
51   - ParseGuard(QPDF* qpdf) :
52   - qpdf(qpdf)
53   - {
54   - if (qpdf) {
55   - qpdf->inParse(true);
56   - }
57   - }
58   -
59   - static std::shared_ptr<QPDFObject>
60   - getObject(QPDF* qpdf, int id, int gen, bool parse_pdf)
61   - {
62   - return qpdf->getObjectForParser(id, gen, parse_pdf);
63   - }
64   -
65   - ~ParseGuard()
66   - {
67   - if (qpdf) {
68   - qpdf->inParse(false);
69   - }
70   - }
71   - QPDF* qpdf;
72   -};
73   -
74   -// Pipe class is restricted to QPDF_Stream.
75   -class QPDF::Pipe
76   -{
77   - friend class qpdf::Stream;
78   -
79   - private:
80   - static bool
81   - pipeStreamData(
82   - QPDF* qpdf,
83   - QPDFObjGen og,
84   - qpdf_offset_t offset,
85   - size_t length,
86   - QPDFObjectHandle dict,
87   - bool is_root_metadata,
88   - Pipeline* pipeline,
89   - bool suppress_warnings,
90   - bool will_retry)
91   - {
92   - return qpdf->pipeStreamData(
93   - og, offset, length, dict, is_root_metadata, pipeline, suppress_warnings, will_retry);
94   - }
95   -};
  21 +class BitStream;
  22 +class BitWriter;
96 23  
97 24 class QPDF::ObjCache
98 25 {
... ... @@ -405,17 +332,498 @@ class QPDF::PatternFinder final: public InputSource::Finder
405 332 bool (QPDF::*checker)();
406 333 };
407 334  
  335 +// This class is used to represent a PDF document.
  336 +//
  337 +// The main function of the QPDF class is to represent a PDF document. Doc is the implementation
  338 +// class for this aspect of QPDF.
  339 +class QPDF::Doc
  340 +{
  341 + public:
  342 + class JobSetter;
  343 + class ParseGuard;
  344 + class Resolver;
  345 + class StreamCopier;
  346 + class Streams;
  347 + class Writer;
  348 +
  349 + class Encryption
  350 + {
  351 + public:
  352 + // This class holds data read from the encryption dictionary.
  353 + Encryption(
  354 + int V,
  355 + int R,
  356 + int Length_bytes,
  357 + int P,
  358 + std::string const& O,
  359 + std::string const& U,
  360 + std::string const& OE,
  361 + std::string const& UE,
  362 + std::string const& Perms,
  363 + std::string const& id1,
  364 + bool encrypt_metadata) :
  365 + V(V),
  366 + R(R),
  367 + Length_bytes(Length_bytes),
  368 + P(static_cast<unsigned long long>(P)),
  369 + O(O),
  370 + U(U),
  371 + OE(OE),
  372 + UE(UE),
  373 + Perms(Perms),
  374 + id1(id1),
  375 + encrypt_metadata(encrypt_metadata)
  376 + {
  377 + }
  378 + Encryption(int V, int R, int Length_bytes, bool encrypt_metadata) :
  379 + V(V),
  380 + R(R),
  381 + Length_bytes(Length_bytes),
  382 + encrypt_metadata(encrypt_metadata)
  383 + {
  384 + }
  385 +
  386 + int getV() const;
  387 + int getR() const;
  388 + int getLengthBytes() const;
  389 + int getP() const;
  390 + // Bits in P are numbered from 1 as in the PDF spec.
  391 + bool getP(size_t bit) const;
  392 + std::string const& getO() const;
  393 + std::string const& getU() const;
  394 + std::string const& getOE() const;
  395 + std::string const& getUE() const;
  396 + std::string const& getPerms() const;
  397 + std::string const& getId1() const;
  398 + bool getEncryptMetadata() const;
  399 + // Bits in P are numbered from 1 as in the PDF spec.
  400 + void setP(size_t bit, bool val);
  401 + void setP(unsigned long val);
  402 + void setO(std::string const&);
  403 + void setU(std::string const&);
  404 + void setId1(std::string const& val);
  405 + void setV5EncryptionParameters(
  406 + std::string const& O,
  407 + std::string const& OE,
  408 + std::string const& U,
  409 + std::string const& UE,
  410 + std::string const& Perms);
  411 +
  412 + std::string compute_encryption_key(std::string const& password) const;
  413 +
  414 + bool
  415 + check_owner_password(std::string& user_password, std::string const& owner_password) const;
  416 +
  417 + bool check_user_password(std::string const& user_password) const;
  418 +
  419 + std::string
  420 + recover_encryption_key_with_password(std::string const& password, bool& perms_valid) const;
  421 +
  422 + void compute_encryption_O_U(char const* user_password, char const* owner_password);
  423 +
  424 + std::string
  425 + compute_encryption_parameters_V5(char const* user_password, char const* owner_password);
  426 +
  427 + std::string compute_parameters(char const* user_password, char const* owner_password);
  428 +
  429 + private:
  430 + static constexpr unsigned int OU_key_bytes_V4 = 16; // ( == sizeof(MD5::Digest)
  431 +
  432 + Encryption(Encryption const&) = delete;
  433 + Encryption& operator=(Encryption const&) = delete;
  434 +
  435 + std::string hash_V5(
  436 + std::string const& password, std::string const& salt, std::string const& udata) const;
  437 + std::string
  438 + compute_O_value(std::string const& user_password, std::string const& owner_password) const;
  439 + std::string compute_U_value(std::string const& user_password) const;
  440 + std::string compute_encryption_key_from_password(std::string const& password) const;
  441 + std::string recover_encryption_key_with_password(std::string const& password) const;
  442 + bool check_owner_password_V4(
  443 + std::string& user_password, std::string const& owner_password) const;
  444 + bool check_owner_password_V5(std::string const& owner_passworda) const;
  445 + std::string compute_Perms_value_V5_clear() const;
  446 + std::string compute_O_rc4_key(
  447 + std::string const& user_password, std::string const& owner_password) const;
  448 + std::string compute_U_value_R2(std::string const& user_password) const;
  449 + std::string compute_U_value_R3(std::string const& user_password) const;
  450 + bool check_user_password_V4(std::string const& user_password) const;
  451 + bool check_user_password_V5(std::string const& user_password) const;
  452 +
  453 + int V;
  454 + int R;
  455 + int Length_bytes;
  456 + std::bitset<32> P{0xfffffffc}; // Specification always requires bits 1 and 2 to be cleared.
  457 + std::string O;
  458 + std::string U;
  459 + std::string OE;
  460 + std::string UE;
  461 + std::string Perms;
  462 + std::string id1;
  463 + bool encrypt_metadata;
  464 + }; // class QPDF::Doc::Encryption
  465 +
  466 + class Linearization
  467 + {
  468 + public:
  469 + Linearization() = delete;
  470 + Linearization(Linearization const&) = delete;
  471 + Linearization(Linearization&&) = delete;
  472 + Linearization& operator=(Linearization const&) = delete;
  473 + Linearization& operator=(Linearization&&) = delete;
  474 + ~Linearization() = default;
  475 +
  476 + Linearization(QPDF& qpdf, QPDF::Members* m) :
  477 + qpdf(qpdf),
  478 + m(m)
  479 + {
  480 + }
  481 +
  482 + // For QPDFWriter:
  483 +
  484 + template <typename T>
  485 + void optimize_internal(
  486 + T const& object_stream_data,
  487 + bool allow_changes = true,
  488 + std::function<int(QPDFObjectHandle&)> skip_stream_parameters = nullptr);
  489 + void optimize(
  490 + QPDFWriter::ObjTable const& obj,
  491 + std::function<int(QPDFObjectHandle&)> skip_stream_parameters);
  492 +
  493 + // Get lists of all objects in order according to the part of a linearized file that they
  494 + // belong to.
  495 + void getLinearizedParts(
  496 + QPDFWriter::ObjTable const& obj,
  497 + std::vector<QPDFObjectHandle>& part4,
  498 + std::vector<QPDFObjectHandle>& part6,
  499 + std::vector<QPDFObjectHandle>& part7,
  500 + std::vector<QPDFObjectHandle>& part8,
  501 + std::vector<QPDFObjectHandle>& part9);
  502 +
  503 + void generateHintStream(
  504 + QPDFWriter::NewObjTable const& new_obj,
  505 + QPDFWriter::ObjTable const& obj,
  506 + std::string& hint_stream,
  507 + int& S,
  508 + int& O,
  509 + bool compressed);
  510 +
  511 + // methods to support linearization checking -- implemented in QPDF_linearization.cc
  512 +
  513 + void readLinearizationData();
  514 + void checkLinearizationInternal();
  515 + void dumpLinearizationDataInternal();
  516 + void linearizationWarning(std::string_view);
  517 + qpdf::Dictionary readHintStream(Pipeline&, qpdf_offset_t offset, size_t length);
  518 + void readHPageOffset(BitStream);
  519 + void readHSharedObject(BitStream);
  520 + void readHGeneric(BitStream, HGeneric&);
  521 + qpdf_offset_t maxEnd(ObjUser const& ou);
  522 + qpdf_offset_t getLinearizationOffset(QPDFObjGen);
  523 + QPDFObjectHandle
  524 + getUncompressedObject(QPDFObjectHandle&, std::map<int, int> const& object_stream_data);
  525 + QPDFObjectHandle getUncompressedObject(QPDFObjectHandle&, QPDFWriter::ObjTable const& obj);
  526 + int lengthNextN(int first_object, int n);
  527 + void checkHPageOffset(
  528 + std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& idx_to_obj);
  529 + void checkHSharedObject(
  530 + std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& idx_to_obj);
  531 + void checkHOutlines();
  532 + void dumpHPageOffset();
  533 + void dumpHSharedObject();
  534 + void dumpHGeneric(HGeneric&);
  535 + qpdf_offset_t adjusted_offset(qpdf_offset_t offset);
  536 + template <typename T>
  537 + void calculateLinearizationData(T const& object_stream_data);
  538 + template <typename T>
  539 + void pushOutlinesToPart(
  540 + std::vector<QPDFObjectHandle>& part,
  541 + std::set<QPDFObjGen>& lc_outlines,
  542 + T const& object_stream_data);
  543 + int outputLengthNextN(
  544 + int in_object,
  545 + int n,
  546 + QPDFWriter::NewObjTable const& new_obj,
  547 + QPDFWriter::ObjTable const& obj);
  548 + void calculateHPageOffset(
  549 + QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
  550 + void calculateHSharedObject(
  551 + QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
  552 + void
  553 + calculateHOutline(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
  554 + void writeHPageOffset(BitWriter&);
  555 + void writeHSharedObject(BitWriter&);
  556 + void writeHGeneric(BitWriter&, HGeneric&);
  557 +
  558 + // Methods to support optimization
  559 +
  560 + void updateObjectMaps(
  561 + ObjUser const& ou,
  562 + QPDFObjectHandle oh,
  563 + std::function<int(QPDFObjectHandle&)> skip_stream_parameters);
  564 + void filterCompressedObjects(std::map<int, int> const& object_stream_data);
  565 + void filterCompressedObjects(QPDFWriter::ObjTable const& object_stream_data);
  566 +
  567 + private:
  568 + QPDF& qpdf;
  569 + QPDF::Members* m;
  570 + };
  571 +
  572 + class Objects
  573 + {
  574 + public:
  575 + Objects() = delete;
  576 + Objects(Objects const&) = delete;
  577 + Objects(Objects&&) = delete;
  578 + Objects& operator=(Objects const&) = delete;
  579 + Objects& operator=(Objects&&) = delete;
  580 + ~Objects() = default;
  581 +
  582 + Objects(QPDF& qpdf, QPDF::Members* m) :
  583 + qpdf(qpdf),
  584 + m(m)
  585 + {
  586 + }
  587 +
  588 + void parse(char const* password);
  589 + std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og);
  590 + void inParse(bool);
  591 + QPDFObjGen nextObjGen();
  592 + QPDFObjectHandle newIndirect(QPDFObjGen, std::shared_ptr<QPDFObject> const&);
  593 + void updateCache(
  594 + QPDFObjGen og,
  595 + std::shared_ptr<QPDFObject> const& object,
  596 + qpdf_offset_t end_before_space,
  597 + qpdf_offset_t end_after_space,
  598 + bool destroy = true);
  599 + bool resolveXRefTable();
  600 + QPDFObjectHandle readObjectAtOffset(
  601 + qpdf_offset_t offset, std::string const& description, bool skip_cache_if_in_xref);
  602 + QPDFTokenizer::Token readToken(InputSource& input, size_t max_len = 0);
  603 + QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj);
  604 + std::shared_ptr<QPDFObject> getObjectForParser(int id, int gen, bool parse_pdf);
  605 + std::shared_ptr<QPDFObject> getObjectForJSON(int id, int gen);
  606 + size_t tableSize();
  607 +
  608 + // For QPDFWriter:
  609 +
  610 + // Get a list of objects that would be permitted in an object stream.
  611 + template <typename T>
  612 + std::vector<T> getCompressibleObjGens();
  613 + std::vector<QPDFObjGen> getCompressibleObjVector();
  614 + std::vector<bool> getCompressibleObjSet();
  615 +
  616 + private:
  617 + void setTrailer(QPDFObjectHandle obj);
  618 + void reconstruct_xref(QPDFExc& e, bool found_startxref = true);
  619 + void read_xref(qpdf_offset_t offset, bool in_stream_recovery = false);
  620 + bool parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes);
  621 + bool read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
  622 + bool read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
  623 + qpdf_offset_t read_xrefTable(qpdf_offset_t offset);
  624 + qpdf_offset_t read_xrefStream(qpdf_offset_t offset, bool in_stream_recovery = false);
  625 + qpdf_offset_t processXRefStream(
  626 + qpdf_offset_t offset, QPDFObjectHandle& xref_stream, bool in_stream_recovery = false);
  627 + std::pair<int, std::array<int, 3>>
  628 + processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged);
  629 + int processXRefSize(
  630 + QPDFObjectHandle& dict,
  631 + int entry_size,
  632 + std::function<QPDFExc(std::string_view)> damaged);
  633 + std::pair<int, std::vector<std::pair<int, int>>> processXRefIndex(
  634 + QPDFObjectHandle& dict,
  635 + int max_num_entries,
  636 + std::function<QPDFExc(std::string_view)> damaged);
  637 + void insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2);
  638 + void insertFreeXrefEntry(QPDFObjGen);
  639 + QPDFObjectHandle readTrailer();
  640 + QPDFObjectHandle readObject(std::string const& description, QPDFObjGen og);
  641 + void readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
  642 + void validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
  643 + QPDFObjectHandle
  644 + readObjectInStream(qpdf::is::OffsetBuffer& input, int stream_id, int obj_id);
  645 + size_t recoverStreamLength(
  646 + std::shared_ptr<InputSource> input, QPDFObjGen og, qpdf_offset_t stream_offset);
  647 +
  648 + QPDFObjGen read_object_start(qpdf_offset_t offset);
  649 + void readObjectAtOffset(
  650 + bool attempt_recovery,
  651 + qpdf_offset_t offset,
  652 + std::string const& description,
  653 + QPDFObjGen exp_og);
  654 + void resolveObjectsInStream(int obj_stream_number);
  655 + bool isCached(QPDFObjGen og);
  656 + bool isUnresolved(QPDFObjGen og);
  657 + void setLastObjectDescription(std::string const& description, QPDFObjGen og);
  658 +
  659 + private:
  660 + QPDF& qpdf;
  661 + QPDF::Members* m;
  662 + }; // class QPDF::Doc::Objects
  663 +
  664 + // This class is used to represent a PDF Pages tree.
  665 + class Pages
  666 + {
  667 + public:
  668 + Pages() = delete;
  669 + Pages(Pages const&) = delete;
  670 + Pages(Pages&&) = delete;
  671 + Pages& operator=(Pages const&) = delete;
  672 + Pages& operator=(Pages&&) = delete;
  673 + ~Pages() = default;
  674 +
  675 + Pages(QPDF& qpdf, QPDF::Members* m) :
  676 + qpdf(qpdf),
  677 + m(m)
  678 + {
  679 + }
  680 +
  681 + void getAllPagesInternal(
  682 + QPDFObjectHandle cur_pages,
  683 + QPDFObjGen::set& visited,
  684 + QPDFObjGen::set& seen,
  685 + bool media_box,
  686 + bool resources);
  687 + void insertPage(QPDFObjectHandle newpage, int pos);
  688 + void flattenPagesTree();
  689 + void insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate);
  690 + void pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys);
  691 + void pushInheritedAttributesToPageInternal(
  692 + QPDFObjectHandle,
  693 + std::map<std::string, std::vector<QPDFObjectHandle>>&,
  694 + bool allow_changes,
  695 + bool warn_skipped_keys);
  696 +
  697 + private:
  698 + QPDF& qpdf;
  699 + QPDF::Members* m;
  700 + }; // class QPDF::Doc::Pages
  701 +
  702 + // StreamCopier class is restricted to QPDFObjectHandle so it can copy stream data.
  703 + class StreamCopier
  704 + {
  705 + friend class QPDFObjectHandle;
  706 +
  707 + private:
  708 + static void
  709 + copyStreamData(QPDF* qpdf, QPDFObjectHandle const& dest, QPDFObjectHandle const& src)
  710 + {
  711 + qpdf->copyStreamData(dest, src);
  712 + }
  713 + };
  714 +
  715 + Doc() = delete;
  716 + Doc(Doc const&) = delete;
  717 + Doc(Doc&&) = delete;
  718 + Doc& operator=(Doc const&) = delete;
  719 + Doc& operator=(Doc&&) = delete;
  720 + ~Doc() = default;
  721 +
  722 + Doc(QPDF& qpdf, QPDF::Members& m) :
  723 + qpdf(qpdf),
  724 + m(m),
  725 + lin_(qpdf, &m),
  726 + objects_(qpdf, &m),
  727 + pages_(qpdf, &m)
  728 + {
  729 + }
  730 +
  731 + Linearization&
  732 + linearization()
  733 + {
  734 + return lin_;
  735 + };
  736 +
  737 + Objects&
  738 + objects()
  739 + {
  740 + return objects_;
  741 + };
  742 +
  743 + Pages&
  744 + pages()
  745 + {
  746 + return pages_;
  747 + }
  748 +
  749 + bool reconstructed_xref() const;
  750 +
  751 + QPDFAcroFormDocumentHelper&
  752 + acroform()
  753 + {
  754 + if (!acroform_) {
  755 + acroform_ = std::make_unique<QPDFAcroFormDocumentHelper>(qpdf);
  756 + }
  757 + return *acroform_;
  758 + }
  759 +
  760 + QPDFEmbeddedFileDocumentHelper&
  761 + embedded_files()
  762 + {
  763 + if (!embedded_files_) {
  764 + embedded_files_ = std::make_unique<QPDFEmbeddedFileDocumentHelper>(qpdf);
  765 + }
  766 + return *embedded_files_;
  767 + }
  768 +
  769 + QPDFOutlineDocumentHelper&
  770 + outlines()
  771 + {
  772 + if (!outlines_) {
  773 + outlines_ = std::make_unique<QPDFOutlineDocumentHelper>(qpdf);
  774 + }
  775 + return *outlines_;
  776 + }
  777 +
  778 + QPDFPageDocumentHelper&
  779 + page_dh()
  780 + {
  781 + if (!page_dh_) {
  782 + page_dh_ = std::make_unique<QPDFPageDocumentHelper>(qpdf);
  783 + }
  784 + return *page_dh_;
  785 + }
  786 +
  787 + QPDFPageLabelDocumentHelper&
  788 + page_labels()
  789 + {
  790 + if (!page_labels_) {
  791 + page_labels_ = std::make_unique<QPDFPageLabelDocumentHelper>(qpdf);
  792 + }
  793 + return *page_labels_;
  794 + }
  795 +
  796 + private:
  797 + QPDF& qpdf;
  798 + QPDF::Members& m;
  799 +
  800 + Linearization lin_;
  801 + Objects objects_;
  802 + Pages pages_;
  803 +
  804 + // Document Helpers;
  805 + std::unique_ptr<QPDFAcroFormDocumentHelper> acroform_;
  806 + std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files_;
  807 + std::unique_ptr<QPDFOutlineDocumentHelper> outlines_;
  808 + std::unique_ptr<QPDFPageDocumentHelper> page_dh_;
  809 + std::unique_ptr<QPDFPageLabelDocumentHelper> page_labels_;
  810 +};
  811 +
408 812 class QPDF::Members
409 813 {
410 814 friend class QPDF;
411 815 friend class ResolveRecorder;
412 816  
413 817 public:
414   - Members();
  818 + Members(QPDF& qpdf);
415 819 Members(Members const&) = delete;
416 820 ~Members() = default;
417 821  
418 822 private:
  823 + Doc doc;
  824 + Doc::Linearization& lin;
  825 + Doc::Objects& objects;
  826 + Doc::Pages& pages;
419 827 std::shared_ptr<QPDFLogger> log;
420 828 unsigned long long unique_id{0};
421 829 qpdf::Tokenizer tokenizer;
... ... @@ -488,78 +896,33 @@ class QPDF::Members
488 896 // Optimization data
489 897 std::map<ObjUser, std::set<QPDFObjGen>> obj_user_to_objects;
490 898 std::map<QPDFObjGen, std::set<ObjUser>> object_to_obj_users;
491   -
492   - // Document Helpers;
493   - std::unique_ptr<QPDFAcroFormDocumentHelper> acroform;
494   - std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files;
495   - std::unique_ptr<QPDFOutlineDocumentHelper> outlines;
496   - std::unique_ptr<QPDFPageDocumentHelper> pages;
497   - std::unique_ptr<QPDFPageLabelDocumentHelper> page_labels;
498 899 };
499 900  
500   -// JobSetter class is restricted to QPDFJob.
501   -class QPDF::JobSetter
  901 +// The Resolver class is restricted to QPDFObject and BaseHandle so that only it can resolve
  902 +// indirect references.
  903 +class QPDF::Doc::Resolver
502 904 {
503   - friend class QPDFJob;
  905 + friend class QPDFObject;
  906 + friend class qpdf::BaseHandle;
504 907  
505 908 private:
506   - // Enable enhanced warnings for pdf file checking.
507   - static void
508   - setCheckMode(QPDF& qpdf, bool val)
  909 + static std::shared_ptr<QPDFObject> const&
  910 + resolved(QPDF* qpdf, QPDFObjGen og)
509 911 {
510   - qpdf.m->check_mode = val;
  912 + return qpdf->m->objects.resolve(og);
511 913 }
512 914 };
513 915  
514 916 inline bool
515   -QPDF::reconstructed_xref() const
516   -{
517   - return m->reconstructed_xref;
518   -}
519   -
520   -inline QPDFAcroFormDocumentHelper&
521   -QPDF::acroform()
522   -{
523   - if (!m->acroform) {
524   - m->acroform = std::make_unique<QPDFAcroFormDocumentHelper>(*this);
525   - }
526   - return *m->acroform;
527   -}
528   -
529   -inline QPDFEmbeddedFileDocumentHelper&
530   -QPDF::embedded_files()
  917 +QPDF::Doc::reconstructed_xref() const
531 918 {
532   - if (!m->embedded_files) {
533   - m->embedded_files = std::make_unique<QPDFEmbeddedFileDocumentHelper>(*this);
534   - }
535   - return *m->embedded_files;
  919 + return m.reconstructed_xref;
536 920 }
537 921  
538   -inline QPDFOutlineDocumentHelper&
539   -QPDF::outlines()
  922 +inline QPDF::Doc&
  923 +QPDF::doc()
540 924 {
541   - if (!m->outlines) {
542   - m->outlines = std::make_unique<QPDFOutlineDocumentHelper>(*this);
543   - }
544   - return *m->outlines;
545   -}
546   -
547   -inline QPDFPageDocumentHelper&
548   -QPDF::pages()
549   -{
550   - if (!m->pages) {
551   - m->pages = std::make_unique<QPDFPageDocumentHelper>(*this);
552   - }
553   - return *m->pages;
554   -}
555   -
556   -inline QPDFPageLabelDocumentHelper&
557   -QPDF::page_labels()
558   -{
559   - if (!m->page_labels) {
560   - m->page_labels = std::make_unique<QPDFPageLabelDocumentHelper>(*this);
561   - }
562   - return *m->page_labels;
  925 + return m->doc;
563 926 }
564 927  
565 928 // Throw a generic exception for unusual error conditions that do not be covered during CI testing.
... ...
qpdf/qpdf.testcov
1 1 ignored-scope: libtests
2   -QPDF err expected endobj 0
3   -QPDF err wrong objid/generation 0
4   -QPDF check objid 1
5   -QPDF check generation 1
6   -QPDF check obj 1
7   -QPDF object stream offsets not increasing 0
8   -QPDF ignore self-referential object stream 0
9   -QPDF object stream contains id < 1 0
10 2 QPDF hint table length direct 0
11 3 QPDF P absent in lindict 1
12   -QPDF expected n n obj 0
13 4 QPDF opt direct pages resource 1
14   -QPDF opt inheritable keys 0
15 5 QPDF opt no inheritable keys 0
16   -QPDF opt erase empty key ancestor 0
17   -QPDF opt resource inherited 0
18 6 QPDF opt page resource hides ancestor 0
19   -QPDF opt key ancestors depth > 1 0
20 7 QPDF opt loop detected 0
21 8 QPDF categorize pagemode present 1
22 9 QPDF categorize pagemode outlines 1
... ... @@ -39,26 +26,13 @@ main QTest dictionary indirect 1
39 26 main QTest stream 0
40 27 QPDF lin write nshared_total > nshared_first_page 1
41 28 QPDFWriter encrypted hint stream 0
42   -QPDF opt inherited scalar 0
43   -QPDF xref reused object 0
44 29 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 30 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 31 QPDFParser bad brace 0
56 32 QPDFParser bad brace in parseRemainder 0
57 33 QPDFParser bad array close 0
58 34 QPDFParser bad array close in parseRemainder 0
59 35 QPDFParser bad dictionary close 0
60   -QPDFParser bad dictionary close in parseRemainder 0
61   -QPDF can't find xref 0
62 36 QPDFTokenizer bad ) 0
63 37 QPDFTokenizer bad > 0
64 38 QPDFTokenizer bad hexstring character 0
... ... @@ -69,26 +43,15 @@ QPDFTokenizer bad name 2 0
69 43 QPDF UseOutlines but no Outlines 0
70 44 QPDFObjectHandle makeDirect loop 0
71 45 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 46 QPDF ignoring XRefStm in trailer 0
77   -QPDF xref deleted object 0
78 47 SF_FlateLzwDecode PNG filter 0
79 48 QPDF xref /Index is array 1
80 49 QPDFWriter encrypt object stream 0
81 50 QPDF exclude indirect length 0
82 51 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 52 QPDF_Stream pipeStreamData with null pipeline 0
89 53 QPDFJob unable to filter 0
90 54 QUtil non-trivial UTF-16 0
91   -QPDF xref overwrite invalid objgen 0
92 55 QPDF decoding error warning 0
93 56 qpdf-c called qpdf_init 0
94 57 qpdf-c called qpdf_cleanup 0
... ... @@ -133,8 +96,6 @@ QPDF_encryption aes decode string 0
133 96 QPDFWriter forced version disabled encryption 0
134 97 qpdf-c called qpdf_set_r4_encryption_parameters_insecure 0
135 98 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 99 qpdf-c called qpdf_has_error 0
139 100 qpdf-c called qpdf_get_qpdf_version 0
140 101 QPDF_Stream pipe original stream data 0
... ... @@ -148,11 +109,7 @@ QPDFObjectHandle append page contents 0
148 109 QPDF_Stream getRawStreamData 0
149 110 QPDF_Stream getStreamData 0
150 111 qpdf-c called qpdf_read_memory 0
151   -QPDF stream without newline 0
152   -QPDF stream with CR only 0
153 112 QPDF stream with CRNL 0
154   -QPDF stream with NL only 0
155   -QPDF replaceObject called with indirect object 0
156 113 QPDFWriter copy encrypt metadata 1
157 114 qpdf-c get_info_key 1
158 115 qpdf-c set_info_key to value 0
... ... @@ -165,12 +122,9 @@ exercise processFile(FILE*) 0
165 122 exercise processMemoryFile 0
166 123 QPDF remove page 2
167 124 QPDF insert page 2
168   -QPDF updateAllPagesCache 0
169   -QPDF insert non-indirect page 0
170 125 QPDF insert indirect page 0
171 126 QPDF_Stream ERR shallow copy stream 0
172 127 QPDFObjectHandle newStream with string 0
173   -QPDF unknown key not inherited 0
174 128 QPDF_Stream provider length not provided 0
175 129 QPDF_Stream unknown stream length 0
176 130 QPDF replaceReserved 0
... ... @@ -181,14 +135,12 @@ QPDF replace array 0
181 135 QPDF replace dictionary 0
182 136 QPDF replace stream 0
183 137 QPDF replace foreign indirect with null 0
184   -QPDF insert foreign page 0
185 138 QPDFWriter copy use_aes 1
186 139 QPDFParser indirect without context 0
187 140 QPDFObjectHandle trailing data in parse 0
188 141 QPDFTokenizer EOF reading token 0
189 142 QPDFTokenizer EOF reading appendable token 0
190 143 QPDFWriter extra header text no newline 0
191   -QPDF bogus 0 offset 0
192 144 QPDF global offset 0
193 145 QPDFWriter make Extensions direct 0
194 146 QPDFWriter make ADBE direct 1
... ... @@ -202,29 +154,21 @@ qpdf-c called qpdf_set_r6_encryption_parameters 0
202 154 QPDFObjectHandle EOF in inline image 0
203 155 QPDFObjectHandle inline image token 0
204 156 QPDF not caching overridden objstm object 0
205   -QPDF_optimization indirect outlines 0
206 157 QPDF xref space 2
207 158 QPDFJob pages range omitted in middle 0
208 159 QPDFWriter standard deterministic ID 1
209 160 QPDFWriter linearized deterministic ID 1
210 161 qpdf-c called qpdf_set_deterministic_ID 0
211 162 QPDFParser invalid objgen 0
212   -QPDF object id 0 0
213   -QPDF recursion loop in resolve 0
214 163 QPDFParser treat word as string 0
215 164 QPDFParser treat word as string in parseRemainder 0
216 165 QPDFParser found fake 1
217 166 QPDFParser no val for last key 0
218   -QPDF resolve failure to null 0
219 167 QPDFObjectHandle errors in parsecontent 0
220 168 QPDFJob split-pages %d 0
221 169 QPDFJob split-pages .pdf 0
222 170 QPDFJob split-pages other 0
223 171 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 172 QPDF_encryption pad short parameter 0
229 173 QPDFObjectHandle found old angle 1
230 174 QPDFTokenizer block long token 0
... ... @@ -261,7 +205,6 @@ QPDFObjectHandle dictionary ignoring replaceKey 0
261 205 QPDFObjectHandle numeric non-numeric 0
262 206 QPDFObjectHandle erase array bounds 0
263 207 qpdf-c called qpdf_check_pdf 0
264   -QPDF xref loop 0
265 208 QPDFParser too deep 0
266 209 QPDFFormFieldObjectHelper TU present 0
267 210 QPDFFormFieldObjectHelper TM present 0
... ... @@ -330,9 +273,6 @@ QPDFPageDocumentHelper ignore annotation with no appearance 0
330 273 QPDFFormFieldObjectHelper replaced BMC at EOF 0
331 274 QPDFFormFieldObjectHelper fallback Tf 0
332 275 QPDFPageObjectHelper copy shared attribute 1
333   -QPDF resolve duplicated page object 0
334   -QPDF handle direct page object 0
335   -QPDF missing mediabox 0
336 276 QPDF inherit mediabox 1
337 277 QPDFTokenizer finder found wrong word 0
338 278 QPDFTokenizer found EI by byte count 0
... ... @@ -341,7 +281,6 @@ QPDFPageObjectHelper externalize inline image 0
341 281 QPDFPageObjectHelper keep inline image 0
342 282 QPDFJob image optimize colorspace 0
343 283 QPDFJob image optimize bits per component 0
344   -QPDF xref skipped space 0
345 284 QPDF eof skipping spaces before xref 1
346 285 QPDF_encryption user matches owner V < 5 0
347 286 QPDF_encryption same password 1
... ... @@ -451,9 +390,7 @@ QPDFAcroFormDocumentHelper /DA parse error 0
451 390 QPDFAcroFormDocumentHelper AP parse error 1
452 391 QPDFJob copy fields not this file 0
453 392 QPDFJob copy fields non-first from orig 0
454   -QPDF resolve duplicated page in insert 0
455 393 QPDFWriter exclude from object stream 0
456   -QPDF_pages findPage not found 0
457 394 QPDFJob weak crypto error 0
458 395 qpdf-c called qpdf_oh_is_initialized 0
459 396 qpdf-c registered progress reporter 0
... ... @@ -533,7 +470,6 @@ QPDF_json bad calledgetallpages 0
533 470 QPDF_json bad pushedinheritedpageresources 0
534 471 QPDFPageObjectHelper used fallback without copying 0
535 472 QPDF skipping cache for known unchecked object 0
536   -QPDF fix dangling triggered xref reconstruction 0
537 473 QPDF recover xref stream 0
538 474 QPDFJob json over/under no file 0
539 475 QPDF_Array copy 1
... ...