diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index e82be88..ba9f793 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -46,27 +46,7 @@ #include #include -namespace qpdf -{ - class Dictionary; - - namespace is - { - class OffsetBuffer; - } -} // namespace qpdf - -class QPDF_Stream; -class BitStream; -class BitWriter; -class BufferInputSource; class QPDFLogger; -class QPDFParser; -class QPDFAcroFormDocumentHelper; -class QPDFEmbeddedFileDocumentHelper; -class QPDFOutlineDocumentHelper; -class QPDFPageDocumentHelper; -class QPDFPageLabelDocumentHelper; class QPDF { @@ -477,7 +457,7 @@ class QPDF V(V), R(R), Length_bytes(Length_bytes), - P(static_cast(P)), + P(P), O(O), U(U), OE(OE), @@ -487,20 +467,11 @@ class QPDF encrypt_metadata(encrypt_metadata) { } - EncryptionData(int V, int R, int Length_bytes, bool encrypt_metadata) : - V(V), - R(R), - Length_bytes(Length_bytes), - encrypt_metadata(encrypt_metadata) - { - } int getV() const; int getR() const; int getLengthBytes() const; int getP() const; - // Bits in P are numbered from 1 as in the PDF spec. - bool getP(size_t bit) const; std::string const& getO() const; std::string const& getU() const; std::string const& getOE() const; @@ -508,12 +479,9 @@ class QPDF std::string const& getPerms() const; std::string const& getId1() const; bool getEncryptMetadata() const; - // Bits in P are numbered from 1 as in the PDF spec. - void setP(size_t bit, bool val); - void setP(unsigned long val); + void setO(std::string const&); void setU(std::string const&); - void setId1(std::string const& val); void setV5EncryptionParameters( std::string const& O, std::string const& OE, @@ -521,51 +489,14 @@ class QPDF std::string const& UE, std::string const& Perms); - std::string compute_encryption_key(std::string const& password) const; - - bool - check_owner_password(std::string& user_password, std::string const& owner_password) const; - - bool check_user_password(std::string const& user_password) const; - - std::string - recover_encryption_key_with_password(std::string const& password, bool& perms_valid) const; - - void compute_encryption_O_U(char const* user_password, char const* owner_password); - - std::string - compute_encryption_parameters_V5(char const* user_password, char const* owner_password); - - std::string compute_parameters(char const* user_password, char const* owner_password); - private: - static constexpr unsigned int OU_key_bytes_V4 = 16; // ( == sizeof(MD5::Digest) - EncryptionData(EncryptionData const&) = delete; EncryptionData& operator=(EncryptionData const&) = delete; - std::string hash_V5( - std::string const& password, std::string const& salt, std::string const& udata) const; - std::string - compute_O_value(std::string const& user_password, std::string const& owner_password) const; - std::string compute_U_value(std::string const& user_password) const; - std::string compute_encryption_key_from_password(std::string const& password) const; - std::string recover_encryption_key_with_password(std::string const& password) const; - bool check_owner_password_V4( - std::string& user_password, std::string const& owner_password) const; - bool check_owner_password_V5(std::string const& owner_passworda) const; - std::string compute_Perms_value_V5_clear() const; - std::string compute_O_rc4_key( - std::string const& user_password, std::string const& owner_password) const; - std::string compute_U_value_R2(std::string const& user_password) const; - std::string compute_U_value_R3(std::string const& user_password) const; - bool check_user_password_V4(std::string const& user_password) const; - bool check_user_password_V5(std::string const& user_password) const; - int V; int R; int Length_bytes; - std::bitset<32> P{0xfffffffc}; // Specification always requires bits 1 and 2 to be cleared. + int P; std::string O; std::string U; std::string OE; @@ -574,7 +505,6 @@ class QPDF std::string id1; bool encrypt_metadata; }; - QPDF_DLL bool isEncrypted() const; @@ -794,19 +724,9 @@ class QPDF // End of the public API. The following classes and methods are for qpdf internal use only. - class Writer; - class Resolver; - class StreamCopier; - class ParseGuard; - class Pipe; - class JobSetter; + class Doc; - inline bool reconstructed_xref() const; - inline QPDFAcroFormDocumentHelper& acroform(); - inline QPDFEmbeddedFileDocumentHelper& embedded_files(); - inline QPDFOutlineDocumentHelper& outlines(); - inline QPDFPageDocumentHelper& pages(); - inline QPDFPageLabelDocumentHelper& page_labels(); + inline Doc& doc(); // For testing only -- do not add to DLL static bool test_json_validators(); @@ -830,66 +750,10 @@ class QPDF class ResolveRecorder; class JSONReactor; - void parse(char const* password); - void inParse(bool); - void setTrailer(QPDFObjectHandle obj); - void read_xref(qpdf_offset_t offset, bool in_stream_recovery = false); - bool resolveXRefTable(); - void reconstruct_xref(QPDFExc& e, bool found_startxref = true); - bool parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes); - bool read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type); - bool read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type); - qpdf_offset_t read_xrefTable(qpdf_offset_t offset); - qpdf_offset_t read_xrefStream(qpdf_offset_t offset, bool in_stream_recovery = false); - qpdf_offset_t processXRefStream( - qpdf_offset_t offset, QPDFObjectHandle& xref_stream, bool in_stream_recovery = false); - std::pair> - processXRefW(QPDFObjectHandle& dict, std::function damaged); - int processXRefSize( - QPDFObjectHandle& dict, int entry_size, std::function damaged); - std::pair>> processXRefIndex( - QPDFObjectHandle& dict, - int max_num_entries, - std::function damaged); - void insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2); - void insertFreeXrefEntry(QPDFObjGen); - void setLastObjectDescription(std::string const& description, QPDFObjGen og); - QPDFObjectHandle readTrailer(); - QPDFObjectHandle readObject(std::string const& description, QPDFObjGen og); - void readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset); - void validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset); - QPDFObjectHandle readObjectInStream(qpdf::is::OffsetBuffer& input, int stream_id, int obj_id); - size_t recoverStreamLength( - std::shared_ptr input, QPDFObjGen og, qpdf_offset_t stream_offset); - QPDFTokenizer::Token readToken(InputSource&, size_t max_len = 0); - - QPDFObjGen read_object_start(qpdf_offset_t offset); - void readObjectAtOffset( - bool attempt_recovery, - qpdf_offset_t offset, - std::string const& description, - QPDFObjGen exp_og); - QPDFObjectHandle readObjectAtOffset( - qpdf_offset_t offset, std::string const& description, bool skip_cache_if_in_xref); - std::shared_ptr const& resolve(QPDFObjGen og); - void resolveObjectsInStream(int obj_stream_number); void stopOnError(std::string const& message); inline void no_ci_stop_if(bool condition, std::string const& message, std::string const& context = {}); - QPDFObjGen nextObjGen(); - QPDFObjectHandle newIndirect(QPDFObjGen, std::shared_ptr const&); - QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr const& obj); - bool isCached(QPDFObjGen og); - bool isUnresolved(QPDFObjGen og); - std::shared_ptr getObjectForParser(int id, int gen, bool parse_pdf); - std::shared_ptr getObjectForJSON(int id, int gen); void removeObject(QPDFObjGen og); - void updateCache( - QPDFObjGen og, - std::shared_ptr const& object, - qpdf_offset_t end_before_space, - qpdf_offset_t end_after_space, - bool destroy = true); static QPDFExc damagedPDF( InputSource& input, std::string const& object, @@ -929,34 +793,6 @@ class QPDF // For QPDFWriter: std::map const& getXRefTableInternal(); - template - void optimize_internal( - T const& object_stream_data, - bool allow_changes = true, - std::function skip_stream_parameters = nullptr); - void optimize( - QPDFWriter::ObjTable const& obj, - std::function skip_stream_parameters); - size_t tableSize(); - - // Get lists of all objects in order according to the part of a linearized file that they belong - // to. - void getLinearizedParts( - QPDFWriter::ObjTable const& obj, - std::vector& part4, - std::vector& part6, - std::vector& part7, - std::vector& part8, - std::vector& part9); - - void generateHintStream( - QPDFWriter::NewObjTable const& new_obj, - QPDFWriter::ObjTable const& obj, - std::string& hint_stream, - int& S, - int& O, - bool compressed); - // Get a list of objects that would be permitted in an object stream. template std::vector getCompressibleObjGens(); @@ -1016,66 +852,6 @@ class QPDF bool findStartxref(); bool findEndstream(); - // methods to support linearization checking -- implemented in QPDF_linearization.cc - void readLinearizationData(); - void checkLinearizationInternal(); - void dumpLinearizationDataInternal(); - void linearizationWarning(std::string_view); - qpdf::Dictionary readHintStream(Pipeline&, qpdf_offset_t offset, size_t length); - void readHPageOffset(BitStream); - void readHSharedObject(BitStream); - void readHGeneric(BitStream, HGeneric&); - qpdf_offset_t maxEnd(ObjUser const& ou); - qpdf_offset_t getLinearizationOffset(QPDFObjGen); - QPDFObjectHandle - getUncompressedObject(QPDFObjectHandle&, std::map const& object_stream_data); - QPDFObjectHandle getUncompressedObject(QPDFObjectHandle&, QPDFWriter::ObjTable const& obj); - int lengthNextN(int first_object, int n); - void - checkHPageOffset(std::vector const& pages, std::map& idx_to_obj); - void - checkHSharedObject(std::vector const& pages, std::map& idx_to_obj); - void checkHOutlines(); - void dumpHPageOffset(); - void dumpHSharedObject(); - void dumpHGeneric(HGeneric&); - qpdf_offset_t adjusted_offset(qpdf_offset_t offset); - template - void calculateLinearizationData(T const& object_stream_data); - template - void pushOutlinesToPart( - std::vector& part, - std::set& lc_outlines, - T const& object_stream_data); - int outputLengthNextN( - int in_object, - int n, - QPDFWriter::NewObjTable const& new_obj, - QPDFWriter::ObjTable const& obj); - void - calculateHPageOffset(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj); - void - calculateHSharedObject(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj); - void calculateHOutline(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj); - void writeHPageOffset(BitWriter&); - void writeHSharedObject(BitWriter&); - void writeHGeneric(BitWriter&, HGeneric&); - - // Methods to support optimization - - void pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys); - void pushInheritedAttributesToPageInternal( - QPDFObjectHandle, - std::map>&, - bool allow_changes, - bool warn_skipped_keys); - void updateObjectMaps( - ObjUser const& ou, - QPDFObjectHandle oh, - std::function skip_stream_parameters); - void filterCompressedObjects(std::map const& object_stream_data); - void filterCompressedObjects(QPDFWriter::ObjTable const& object_stream_data); - // JSON import void importJSON(std::shared_ptr, bool must_be_complete); diff --git a/libqpdf/NNTree.cc b/libqpdf/NNTree.cc index c00a472..51c0cc5 100644 --- a/libqpdf/NNTree.cc +++ b/libqpdf/NNTree.cc @@ -30,7 +30,7 @@ void NNTreeImpl::warn(QPDFObjectHandle const& node, std::string const& msg) { qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg); - if (++error_count > 5 && qpdf.reconstructed_xref()) { + if (++error_count > 5 && qpdf.doc().reconstructed_xref()) { error(node, "too many errors - giving up"); } } diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 1854427..2468da6 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -178,7 +178,11 @@ QPDF::QPDFVersion() return QPDF::qpdf_version; } -QPDF::Members::Members() : +QPDF::Members::Members(QPDF& qpdf) : + doc(qpdf, *this), + lin(doc.linearization()), + objects(doc.objects()), + pages(doc.pages()), log(QPDFLogger::defaultLogger()), file(new InvalidInputSource()), encp(new EncryptionParameters) @@ -186,7 +190,7 @@ QPDF::Members::Members() : } QPDF::QPDF() : - m(std::make_unique()) + m(std::make_unique(*this)) { m->tokenizer.allowEOF(); // Generate a unique ID. It just has to be unique among all QPDF objects allocated throughout @@ -266,7 +270,7 @@ void QPDF::processInputSource(std::shared_ptr source, char const* password) { m->file = source; - parse(password); + m->objects.parse(password); } void @@ -434,20 +438,20 @@ QPDF::warn( QPDFObjectHandle QPDF::newReserved() { - return makeIndirectFromQPDFObject(QPDFObject::create()); + return m->objects.makeIndirectFromQPDFObject(QPDFObject::create()); } QPDFObjectHandle QPDF::newIndirectNull() { - return makeIndirectFromQPDFObject(QPDFObject::create()); + return m->objects.makeIndirectFromQPDFObject(QPDFObject::create()); } QPDFObjectHandle QPDF::newStream() { return makeIndirectObject( - qpdf::Stream(*this, nextObjGen(), QPDFObjectHandle::newDictionary(), 0, 0)); + qpdf::Stream(*this, m->objects.nextObjGen(), Dictionary::empty(), 0, 0)); } QPDFObjectHandle diff --git a/libqpdf/QPDFAcroFormDocumentHelper.cc b/libqpdf/QPDFAcroFormDocumentHelper.cc index af0928f..faef618 100644 --- a/libqpdf/QPDFAcroFormDocumentHelper.cc +++ b/libqpdf/QPDFAcroFormDocumentHelper.cc @@ -45,7 +45,7 @@ QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) : QPDFAcroFormDocumentHelper& QPDFAcroFormDocumentHelper::get(QPDF& qpdf) { - return qpdf.acroform(); + return qpdf.doc().acroform(); } void diff --git a/libqpdf/QPDFEmbeddedFileDocumentHelper.cc b/libqpdf/QPDFEmbeddedFileDocumentHelper.cc index 3b95279..bdc86ad 100644 --- a/libqpdf/QPDFEmbeddedFileDocumentHelper.cc +++ b/libqpdf/QPDFEmbeddedFileDocumentHelper.cc @@ -53,7 +53,7 @@ QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF& qpdf) : QPDFEmbeddedFileDocumentHelper& QPDFEmbeddedFileDocumentHelper::get(QPDF& qpdf) { - return qpdf.embedded_files(); + return qpdf.doc().embedded_files(); } void diff --git a/libqpdf/QPDFFormFieldObjectHelper.cc b/libqpdf/QPDFFormFieldObjectHelper.cc index 85a426a..8908cc4 100644 --- a/libqpdf/QPDFFormFieldObjectHelper.cc +++ b/libqpdf/QPDFFormFieldObjectHelper.cc @@ -337,7 +337,7 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) QPDF& qpdf = oh().getQPDF( "QPDFFormFieldObjectHelper::setV called with need_appearances = " "true on an object that is not associated with an owning QPDF"); - qpdf.acroform().setNeedAppearances(true); + qpdf.doc().acroform().setNeedAppearances(true); } } diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index 1242d63..cdcadc9 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -30,6 +30,18 @@ using namespace qpdf; +// JobSetter class is restricted to QPDFJob. +class QPDF::Doc::JobSetter +{ + public: + // Enable enhanced warnings for pdf file checking. + static void + setCheckMode(QPDF& qpdf, bool val) + { + qpdf.m->check_mode = val; + } +}; + namespace { class ImageOptimizer final: public QPDFObjectHandle::StreamDataProvider @@ -734,7 +746,7 @@ QPDFJob::doCheck(QPDF& pdf) bool okay = true; auto& cout = *m->log->getInfo(); cout << "checking " << m->infile_name() << "\n"; - QPDF::JobSetter::setCheckMode(pdf, true); + QPDF::Doc::JobSetter::setCheckMode(pdf, true); try { int extension_level = pdf.getExtensionLevel(); cout << "PDF Version: " << pdf.getPDFVersion(); @@ -751,12 +763,13 @@ QPDFJob::doCheck(QPDF& pdf) } // Create all document helper to trigger any validations they carry out. - auto& pages = pdf.pages(); - (void)pdf.acroform(); - (void)pdf.embedded_files(); - (void)pdf.page_labels(); - (void)pdf.outlines().resolveNamedDest(QPDFObjectHandle::newString("dummy")); - (void)pdf.outlines().getOutlinesForPage(pages.getAllPages().at(0)); + auto& doc = pdf.doc(); + auto& pages = doc.page_dh(); + (void)doc.acroform(); + (void)doc.embedded_files(); + (void)doc.page_labels(); + (void)doc.outlines().resolveNamedDest(QPDFObjectHandle::newString("dummy")); + (void)doc.outlines().getOutlinesForPage(pages.getAllPages().at(0)); // Write the file to nowhere, uncompressing streams. This causes full file traversal and // decoding of all streams we can decode. @@ -839,8 +852,8 @@ QPDFJob::doShowPages(QPDF& pdf) { int pageno = 0; auto& cout = *m->log->getInfo(); - for (auto& ph: pdf.pages().getAllPages()) { - QPDFObjectHandle page = ph.getObjectHandle(); + for (auto& page: pdf.getAllPages()) { + QPDFPageObjectHelper ph(page); ++pageno; cout << "page " << pageno << ": " << page.getObjectID() << " " << page.getGeneration() @@ -871,7 +884,7 @@ QPDFJob::doShowPages(QPDF& pdf) void QPDFJob::doListAttachments(QPDF& pdf) { - auto& efdh = pdf.embedded_files(); + auto& efdh = pdf.doc().embedded_files(); if (efdh.hasEmbeddedFiles()) { for (auto const& i: efdh.getEmbeddedFiles()) { std::string const& key = i.first; @@ -911,7 +924,7 @@ QPDFJob::doListAttachments(QPDF& pdf) void QPDFJob::doShowAttachment(QPDF& pdf) { - auto& efdh = pdf.embedded_files(); + auto& efdh = pdf.doc().embedded_files(); auto fs = efdh.getEmbeddedFile(m->attachment_to_show); if (!fs) { throw std::runtime_error("attachment " + m->attachment_to_show + " not found"); @@ -1030,13 +1043,13 @@ QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf) JSON::writeDictionaryKey(p, first, "pages", 1); bool first_page = true; JSON::writeArrayOpen(p, first_page, 2); - auto& pldh = pdf.page_labels(); - auto& odh = pdf.outlines(); + auto& pldh = pdf.doc().page_labels(); + auto& odh = pdf.doc().outlines(); int pageno = -1; - for (auto& ph: pdf.pages().getAllPages()) { + for (auto& page: pdf.getAllPages()) { ++pageno; JSON j_page = JSON::makeDictionary(); - QPDFObjectHandle page = ph.getObjectHandle(); + QPDFPageObjectHelper ph(page); j_page.addDictionaryMember("object", page.getJSON(m->json_version)); JSON j_images = j_page.addDictionaryMember("images", JSON::makeArray()); for (auto const& iter2: ph.getImages()) { @@ -1093,8 +1106,8 @@ void QPDFJob::doJSONPageLabels(Pipeline* p, bool& first, QPDF& pdf) { JSON j_labels = JSON::makeArray(); - auto& pldh = pdf.page_labels(); - long long npages = QIntC::to_longlong(pdf.pages().getAllPages().size()); + auto& pldh = pdf.doc().page_labels(); + long long npages = QIntC::to_longlong(pdf.getAllPages().size()); if (pldh.hasPageLabels()) { std::vector labels; pldh.getLabelsForPageRange(0, npages - 1, 0, labels); @@ -1142,13 +1155,12 @@ QPDFJob::doJSONOutlines(Pipeline* p, bool& first, QPDF& pdf) { std::map page_numbers; int n = 0; - for (auto const& ph: pdf.pages().getAllPages()) { - QPDFObjectHandle oh = ph.getObjectHandle(); - page_numbers[oh.getObjGen()] = ++n; + for (auto const& oh: pdf.getAllPages()) { + page_numbers[oh] = ++n; } JSON j_outlines = JSON::makeArray(); - addOutlinesToJson(pdf.outlines().getTopLevelOutlines(), j_outlines, page_numbers); + addOutlinesToJson(pdf.doc().outlines().getTopLevelOutlines(), j_outlines, page_numbers); JSON::writeDictionaryItem(p, first, "outlines", j_outlines, 1); } @@ -1156,14 +1168,14 @@ void QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf) { JSON j_acroform = JSON::makeDictionary(); - auto& afdh = pdf.acroform(); + auto& afdh = pdf.doc().acroform(); j_acroform.addDictionaryMember("hasacroform", JSON::makeBool(afdh.hasAcroForm())); j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances())); JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray()); int pagepos1 = 0; - for (auto const& page: pdf.pages().getAllPages()) { + for (auto const& page: pdf.getAllPages()) { ++pagepos1; - for (auto& aoh: afdh.getWidgetAnnotationsForPage(page)) { + for (auto& aoh: afdh.getWidgetAnnotationsForPage({page})) { QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(aoh); if (!ffh.getObjectHandle().isDictionary()) { continue; @@ -1297,7 +1309,7 @@ QPDFJob::doJSONAttachments(Pipeline* p, bool& first, QPDF& pdf) }; JSON j_attachments = JSON::makeDictionary(); - auto& efdh = pdf.embedded_files(); + auto& efdh = pdf.doc().embedded_files(); for (auto const& iter: efdh.getEmbeddedFiles()) { std::string const& key = iter.first; auto fsoh = iter.second; @@ -1873,7 +1885,7 @@ QPDFJob::doUnderOverlayForPage( if (!(uo.pdf && pagenos[pageno.idx].contains(uo_idx))) { return ""; } - auto& dest_afdh = dest_page.qpdf()->acroform(); + auto& dest_afdh = dest_page.qpdf()->doc().acroform(); auto const& pages = uo.pdf->getAllPages(); std::string content; @@ -1894,7 +1906,7 @@ QPDFJob::doUnderOverlayForPage( QPDFMatrix cm; std::string new_content = dest_page.placeFormXObject( fo[from_no.no][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm); - dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->acroform()); + dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->doc().acroform()); if (!new_content.empty()) { resources.mergeResources("<< /XObject << >> >>"_qpdf); auto xobject = resources.getKey("/XObject"); @@ -2019,7 +2031,7 @@ void QPDFJob::addAttachments(QPDF& pdf) { maybe_set_pagemode(pdf, "/UseAttachments"); - auto& efdh = pdf.embedded_files(); + auto& efdh = pdf.doc().embedded_files(); std::vector duplicated_keys; for (auto const& to_add: m->attachments_to_add) { if ((!to_add.replace) && efdh.getEmbeddedFile(to_add.key)) { @@ -2063,7 +2075,7 @@ void QPDFJob::copyAttachments(QPDF& pdf) { maybe_set_pagemode(pdf, "/UseAttachments"); - auto& efdh = pdf.embedded_files(); + auto& efdh = pdf.doc().embedded_files(); std::vector duplicates; for (auto const& to_copy: m->attachments_to_copy) { doIfVerbose([&](Pipeline& v, std::string const& prefix) { @@ -2071,7 +2083,7 @@ QPDFJob::copyAttachments(QPDF& pdf) }); std::unique_ptr other; processFile(other, to_copy.path.c_str(), to_copy.password.c_str(), false, false); - auto& other_efdh = other->embedded_files(); + auto& other_efdh = other->doc().embedded_files(); auto other_attachments = other_efdh.getEmbeddedFiles(); for (auto const& iter: other_attachments) { std::string new_key = to_copy.prefix + iter.first; @@ -2114,7 +2126,7 @@ QPDFJob::handleTransformations(QPDF& pdf) QPDFAcroFormDocumentHelper* afdh_ptr = nullptr; auto afdh = [&]() -> QPDFAcroFormDocumentHelper& { if (!afdh_ptr) { - afdh_ptr = &pdf.acroform(); + afdh_ptr = &pdf.doc().acroform(); } return *afdh_ptr; }; @@ -2205,7 +2217,7 @@ QPDFJob::handleTransformations(QPDF& pdf) pdf.getRoot().replaceKey("/PageLabels", page_labels); } if (!m->attachments_to_remove.empty()) { - auto& efdh = pdf.embedded_files(); + auto& efdh = pdf.doc().embedded_files(); for (auto const& key: m->attachments_to_remove) { if (efdh.removeEmbeddedFile(key)) { doIfVerbose([&](Pipeline& v, std::string const& prefix) { @@ -2344,7 +2356,7 @@ QPDFJob::Input::initialize(Inputs& in, QPDF* a_qpdf) if (in.job.m->remove_unreferenced_page_resources != QPDFJob::re_no) { remove_unreferenced = in.job.shouldRemoveUnreferencedResources(*qpdf); } - if (qpdf->page_labels().hasPageLabels()) { + if (qpdf->doc().page_labels().hasPageLabels()) { in.any_page_labels = true; } } @@ -2574,15 +2586,15 @@ QPDFJob::handlePageSpecs(QPDF& pdf) // original file that we are selecting. std::vector new_labels; int out_pageno = 0; - auto& this_afdh = pdf.acroform(); + auto& this_afdh = pdf.doc().acroform(); std::set referenced_fields; for (auto& selection: new_specs.empty() ? m->inputs.selections : new_specs) { auto& input = selection.input(); if (input.cfis) { input.cfis->stayOpen(true); } - auto* pldh = m->inputs.any_page_labels ? &input.qpdf->page_labels() : nullptr; - auto& other_afdh = input.qpdf->acroform(); + auto* pldh = m->inputs.any_page_labels ? &input.qpdf->doc().page_labels() : nullptr; + auto& other_afdh = input.qpdf->doc().acroform(); doIfVerbose([&](Pipeline& v, std::string const& prefix) { v << prefix << ": adding pages from " << selection.filename() << "\n"; }); @@ -3012,8 +3024,8 @@ QPDFJob::doSplitPages(QPDF& pdf) QPDFPageDocumentHelper dh(pdf); dh.removeUnreferencedResources(); } - auto& pldh = pdf.page_labels(); - auto& afdh = pdf.acroform(); + auto& pldh = pdf.doc().page_labels(); + auto& afdh = pdf.doc().acroform(); std::vector const& pages = pdf.getAllPages(); size_t pageno_len = std::to_string(pages.size()).length(); size_t num_pages = pages.size(); @@ -3025,7 +3037,8 @@ QPDFJob::doSplitPages(QPDF& pdf) } QPDF outpdf; outpdf.emptyPDF(); - QPDFAcroFormDocumentHelper* out_afdh = afdh.hasAcroForm() ? &outpdf.acroform() : nullptr; + QPDFAcroFormDocumentHelper* out_afdh = + afdh.hasAcroForm() ? &outpdf.doc().acroform() : nullptr; if (m->suppress_warnings) { outpdf.setSuppressWarnings(true); } diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 0b40d5b..20933a3 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -1949,7 +1949,7 @@ QPDFObjectHandle::copyStream() dict.replaceKey(iter.first, iter.second.shallowCopy()); } } - QPDF::StreamCopier::copyStreamData(getOwningQPDF(), result, *this); + QPDF::Doc::StreamCopier::copyStreamData(getOwningQPDF(), result, *this); return result; } diff --git a/libqpdf/QPDFOutlineDocumentHelper.cc b/libqpdf/QPDFOutlineDocumentHelper.cc index 5403f64..6133afc 100644 --- a/libqpdf/QPDFOutlineDocumentHelper.cc +++ b/libqpdf/QPDFOutlineDocumentHelper.cc @@ -34,7 +34,7 @@ QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) : QPDFOutlineDocumentHelper& QPDFOutlineDocumentHelper::get(QPDF& qpdf) { - return qpdf.outlines(); + return qpdf.doc().outlines(); } void diff --git a/libqpdf/QPDFPageDocumentHelper.cc b/libqpdf/QPDFPageDocumentHelper.cc index 98ef166..8f1c323 100644 --- a/libqpdf/QPDFPageDocumentHelper.cc +++ b/libqpdf/QPDFPageDocumentHelper.cc @@ -18,7 +18,7 @@ QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) : QPDFPageDocumentHelper& QPDFPageDocumentHelper::get(QPDF& qpdf) { - return qpdf.pages(); + return qpdf.doc().page_dh(); } void @@ -72,7 +72,7 @@ QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page) void QPDFPageDocumentHelper::flattenAnnotations(int required_flags, int forbidden_flags) { - auto& afdh = qpdf.acroform(); + auto& afdh = qpdf.doc().acroform(); if (afdh.getNeedAppearances()) { qpdf.getRoot() .getKey("/AcroForm") diff --git a/libqpdf/QPDFPageLabelDocumentHelper.cc b/libqpdf/QPDFPageLabelDocumentHelper.cc index 0f32a8b..48847fb 100644 --- a/libqpdf/QPDFPageLabelDocumentHelper.cc +++ b/libqpdf/QPDFPageLabelDocumentHelper.cc @@ -26,7 +26,7 @@ QPDFPageLabelDocumentHelper::QPDFPageLabelDocumentHelper(QPDF& qpdf) : QPDFPageLabelDocumentHelper& QPDFPageLabelDocumentHelper::get(QPDF& qpdf) { - return qpdf.page_labels(); + return qpdf.doc().page_labels(); } void diff --git a/libqpdf/QPDFParser.cc b/libqpdf/QPDFParser.cc index 0132f17..52d9a9c 100644 --- a/libqpdf/QPDFParser.cc +++ b/libqpdf/QPDFParser.cc @@ -15,6 +15,36 @@ using namespace qpdf; using ObjectPtr = std::shared_ptr; +// The ParseGuard class allows QPDFParser to detect re-entrant parsing. It also provides +// special access to allow the parser to create unresolved objects and dangling references. +class QPDF::Doc::ParseGuard +{ + public: + ParseGuard(QPDF* qpdf) : + objects(qpdf ? &qpdf->m->objects : nullptr) + { + if (objects) { + objects->inParse(true); + } + } + + static std::shared_ptr + getObject(QPDF* qpdf, int id, int gen, bool parse_pdf) + { + return qpdf->m->objects.getObjectForParser(id, gen, parse_pdf); + } + + ~ParseGuard() + { + if (objects) { + objects->inParse(false); + } + } + QPDF::Doc::Objects* objects; +}; + +using ParseGuard = QPDF::Doc::ParseGuard; + QPDFObjectHandle QPDFParser::parse(InputSource& input, std::string const& object_description, QPDF* context) { @@ -49,7 +79,7 @@ QPDFParser::parse_content( true, 0, 0, - context && context->reconstructed_xref()) + context && context->doc().reconstructed_xref()) .parse(empty, true); } @@ -126,7 +156,7 @@ QPDFParser::parse(bool& empty, bool content_stream) // effect of reading the object and changing the file pointer. If you do this, it will cause a // logic error to be thrown from QPDF::inParse(). - QPDF::ParseGuard pg(context); + ParseGuard pg(context); empty = false; start = input.tell(); @@ -262,7 +292,7 @@ QPDFParser::parseRemainder(bool content_stream) auto id = QIntC::to_int(int_buffer[(int_count - 1) % 2]); auto gen = QIntC::to_int(int_buffer[(int_count) % 2]); if (!(id < 1 || gen < 0 || gen >= 65535)) { - add(QPDF::ParseGuard::getObject(context, id, gen, parse_pdf)); + add(ParseGuard::getObject(context, id, gen, parse_pdf)); } else { QTC::TC("qpdf", "QPDFParser invalid objgen"); addNull(); @@ -392,7 +422,6 @@ QPDFParser::parseRemainder(bool content_stream) frame = &stack.back(); add(std::move(object)); } else { - QTC::TC("qpdf", "QPDFParser bad dictionary close in parseRemainder"); if (sanity_checks) { // During sanity checks, assume nesting of containers is corrupt and object is // unusable. diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index b7062f8..2ab21f1 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -27,6 +27,8 @@ using namespace std::literals; using namespace qpdf; +using Encryption = QPDF::Doc::Encryption; + QPDFWriter::ProgressReporter::~ProgressReporter() // NOLINT (modernize-use-equals-default) { // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer @@ -260,11 +262,13 @@ Pl_stack::Popper::pop() } // Writer class is restricted to QPDFWriter so that only it can call certain methods. -class QPDF::Writer +class QPDF::Doc::Writer { friend class QPDFWriter; Writer(QPDF& pdf) : - pdf(pdf) + pdf(pdf), + lin(pdf.m->lin), + objects(pdf.m->objects) { } @@ -274,7 +278,7 @@ class QPDF::Writer QPDFWriter::ObjTable const& obj, std::function skip_stream_parameters) { - pdf.optimize(obj, skip_stream_parameters); + lin.optimize(obj, skip_stream_parameters); } void @@ -286,7 +290,7 @@ class QPDF::Writer std::vector& part8, std::vector& part9) { - pdf.getLinearizedParts(obj, part4, part6, part7, part8, part9); + lin.getLinearizedParts(obj, part4, part6, part7, part8, part9); } void @@ -298,19 +302,19 @@ class QPDF::Writer int& O, bool compressed) { - pdf.generateHintStream(new_obj, obj, hint_stream, S, O, compressed); + lin.generateHintStream(new_obj, obj, hint_stream, S, O, compressed); } std::vector getCompressibleObjGens() { - return pdf.getCompressibleObjVector(); + return objects.getCompressibleObjVector(); } std::vector getCompressibleObjSet() { - return pdf.getCompressibleObjSet(); + return objects.getCompressibleObjSet(); } std::map const& @@ -322,13 +326,15 @@ class QPDF::Writer size_t tableSize() { - return pdf.tableSize(); + return pdf.m->objects.tableSize(); } QPDF& pdf; + QPDF::Doc::Linearization& lin; + QPDF::Doc::Objects& objects; }; -class QPDFWriter::Members: QPDF::Writer +class QPDFWriter::Members: QPDF::Doc::Writer { friend class QPDFWriter; @@ -343,7 +349,7 @@ class QPDFWriter::Members: QPDF::Writer enum trailer_e { t_normal, t_lin_first, t_lin_second }; Members(QPDFWriter& w, QPDF& pdf) : - QPDF::Writer(pdf), + QPDF::Doc::Writer(pdf), w(w), root_og( pdf.getRoot().getObjGen().isIndirect() ? pdf.getRoot().getObjGen() : QPDFObjGen(-1, 0)), @@ -504,7 +510,7 @@ class QPDFWriter::Members: QPDF::Writer bool pclm{false}; qpdf_object_stream_e object_stream_mode{qpdf_o_preserve}; - std::unique_ptr encryption; + std::unique_ptr encryption; std::string encryption_key; bool encrypt_use_aes{false}; @@ -829,7 +835,7 @@ QPDFWriter::setR2EncryptionParametersInsecure( bool allow_extract, bool allow_annotate) { - m->encryption = std::make_unique(1, 2, 5, true); + m->encryption = std::make_unique(1, 2, 5, true); if (!allow_print) { m->encryption->setP(3, false); } @@ -857,7 +863,7 @@ QPDFWriter::setR3EncryptionParametersInsecure( bool allow_modify_other, qpdf_r3_print_e print) { - m->encryption = std::make_unique(2, 3, 16, true); + m->encryption = std::make_unique(2, 3, 16, true); m->interpretR3EncryptionParameters( allow_accessibility, allow_extract, @@ -884,7 +890,7 @@ QPDFWriter::setR4EncryptionParametersInsecure( bool encrypt_metadata, bool use_aes) { - m->encryption = std::make_unique(4, 4, 16, encrypt_metadata); + m->encryption = std::make_unique(4, 4, 16, encrypt_metadata); m->encrypt_use_aes = use_aes; m->interpretR3EncryptionParameters( allow_accessibility, @@ -911,7 +917,7 @@ QPDFWriter::setR5EncryptionParameters( qpdf_r3_print_e print, bool encrypt_metadata) { - m->encryption = std::make_unique(5, 5, 32, encrypt_metadata); + m->encryption = std::make_unique(5, 5, 32, encrypt_metadata); m->encrypt_use_aes = true; m->interpretR3EncryptionParameters( allow_accessibility, @@ -938,7 +944,7 @@ QPDFWriter::setR6EncryptionParameters( qpdf_r3_print_e print, bool encrypt_metadata) { - m->encryption = std::make_unique(5, 6, 32, encrypt_metadata); + m->encryption = std::make_unique(5, 6, 32, encrypt_metadata); m->interpretR3EncryptionParameters( allow_accessibility, allow_extract, @@ -1094,7 +1100,7 @@ QPDFWriter::Members::copyEncryptionParameters(QPDF& qpdf) QTC::TC("qpdf", "QPDFWriter copy encrypt metadata", encrypt_metadata ? 0 : 1); QTC::TC("qpdf", "QPDFWriter copy use_aes", encrypt_use_aes ? 0 : 1); - encryption = std::make_unique( + encryption = std::make_unique( V, encrypt.getKey("/R").getIntValueAsInt(), key_len, diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc index 558b542..5b2c47e 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -26,6 +26,27 @@ using namespace std::literals; using namespace qpdf; +// Pipe class is restricted to QPDF_Stream. +class QPDF::Doc::Streams +{ + public: + static bool + pipeStreamData( + QPDF* qpdf, + QPDFObjGen og, + qpdf_offset_t offset, + size_t length, + QPDFObjectHandle dict, + bool is_root_metadata, + Pipeline* pipeline, + bool suppress_warnings, + bool will_retry) + { + return qpdf->pipeStreamData( + og, offset, length, dict, is_root_metadata, pipeline, suppress_warnings, will_retry); + } +}; + namespace { class SF_Crypt final: public QPDFStreamFilter @@ -563,7 +584,7 @@ Stream::pipeStreamData( throw std::logic_error("pipeStreamData called for stream with no data"); } QTC::TC("qpdf", "QPDF_Stream pipe original stream data"); - if (!QPDF::Pipe::pipeStreamData( + if (!QPDF::Doc::Streams::pipeStreamData( obj->getQPDF(), obj->getObjGen(), obj->getParsedOffset(), diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc index 5e25d72..f45a72c 100644 --- a/libqpdf/QPDF_encryption.cc +++ b/libqpdf/QPDF_encryption.cc @@ -21,6 +21,8 @@ using namespace qpdf; using namespace std::literals; +using Encryption = QPDF::Doc::Encryption; + static std::string padding_string = "\x28\xbf\x4e\x5e\x4e\x75\x8a\x41\x64\x00\x4e\x56\xff\xfa\x01\x08" "\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 int QPDF::EncryptionData::getP() const { + return this->P; +} + +std::string const& +QPDF::EncryptionData::getO() const +{ + return this->O; +} + +std::string const& +QPDF::EncryptionData::getU() const +{ + return this->U; +} + +std::string const& +QPDF::EncryptionData::getOE() const +{ + return this->OE; +} + +std::string const& +QPDF::EncryptionData::getUE() const +{ + return this->UE; +} + +std::string const& +QPDF::EncryptionData::getPerms() const +{ + return this->Perms; +} + +std::string const& +QPDF::EncryptionData::getId1() const +{ + return this->id1; +} + +bool +QPDF::EncryptionData::getEncryptMetadata() const +{ + return this->encrypt_metadata; +} + +void +QPDF::EncryptionData::setO(std::string const& O) +{ + this->O = O; +} + +void +QPDF::EncryptionData::setU(std::string const& U) +{ + this->U = U; +} + +void +QPDF::EncryptionData::setV5EncryptionParameters( + std::string const& O, + std::string const& OE, + std::string const& U, + std::string const& UE, + std::string const& Perms) +{ + this->O = O; + this->OE = OE; + this->U = U; + this->UE = UE; + this->Perms = Perms; +} + +int +Encryption::getV() const +{ + return this->V; +} + +int +Encryption::getR() const +{ + return this->R; +} + +int +Encryption::getLengthBytes() const +{ + return this->Length_bytes; +} + +int +Encryption::getP() const +{ return static_cast(P.to_ulong()); } bool -QPDF::EncryptionData::getP(size_t bit) const +Encryption::getP(size_t bit) const { qpdf_assert_debug(bit); return P.test(bit - 1); @@ -70,80 +165,80 @@ QPDF::EncryptionParameters::P(size_t bit) const } std::string const& -QPDF::EncryptionData::getO() const +Encryption::getO() const { return this->O; } std::string const& -QPDF::EncryptionData::getU() const +Encryption::getU() const { return this->U; } std::string const& -QPDF::EncryptionData::getOE() const +Encryption::getOE() const { return this->OE; } std::string const& -QPDF::EncryptionData::getUE() const +Encryption::getUE() const { return this->UE; } std::string const& -QPDF::EncryptionData::getPerms() const +Encryption::getPerms() const { return this->Perms; } std::string const& -QPDF::EncryptionData::getId1() const +Encryption::getId1() const { return this->id1; } bool -QPDF::EncryptionData::getEncryptMetadata() const +Encryption::getEncryptMetadata() const { return this->encrypt_metadata; } void -QPDF::EncryptionData::setO(std::string const& O) +Encryption::setO(std::string const& O) { this->O = O; } void -QPDF::EncryptionData::setU(std::string const& U) +Encryption::setU(std::string const& U) { this->U = U; } void -QPDF::EncryptionData::setP(size_t bit, bool val) +Encryption::setP(size_t bit, bool val) { qpdf_assert_debug(bit); P.set(bit - 1, val); } void -QPDF::EncryptionData::setP(unsigned long val) +Encryption::setP(unsigned long val) { P = std::bitset<32>(val); } void -QPDF::EncryptionData::setId1(std::string const& val) +Encryption::setId1(std::string const& val) { id1 = val; } void -QPDF::EncryptionData::setV5EncryptionParameters( +Encryption::setV5EncryptionParameters( std::string const& O, std::string const& OE, std::string const& U, @@ -246,7 +341,7 @@ process_with_aes( } std::string -QPDF::EncryptionData::hash_V5( +Encryption::hash_V5( std::string const& password, std::string const& salt, std::string const& udata) const { Pl_SHA2 hash(256); @@ -358,13 +453,25 @@ QPDF::compute_data_key( } std::string -QPDF::compute_encryption_key(std::string const& password, EncryptionData const& data) -{ - return data.compute_encryption_key(password); +QPDF::compute_encryption_key(std::string const& password, EncryptionData const& ed) +{ + return Encryption( + ed.getV(), + ed.getR(), + ed.getLengthBytes(), + ed.getP(), + ed.getO(), + ed.getU(), + ed.getOE(), + ed.getUE(), + ed.getPerms(), + ed.getId1(), + ed.getEncryptMetadata()) + .compute_encryption_key(password); } std::string -QPDF::EncryptionData::compute_encryption_key(std::string const& password) const +Encryption::compute_encryption_key(std::string const& password) const { if (getV() >= 5) { // 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& password) const } std::string -QPDF::EncryptionData::compute_encryption_key_from_password(std::string const& password) const +Encryption::compute_encryption_key_from_password(std::string const& password) const { // Algorithm 3.2 from the PDF 1.7 Reference Manual @@ -405,7 +512,7 @@ QPDF::EncryptionData::compute_encryption_key_from_password(std::string const& pa } std::string -QPDF::EncryptionData::compute_O_rc4_key( +Encryption::compute_O_rc4_key( std::string const& user_password, std::string const& owner_password) const { if (getV() >= 5) { @@ -418,7 +525,7 @@ QPDF::EncryptionData::compute_O_rc4_key( } std::string -QPDF::EncryptionData::compute_O_value( +Encryption::compute_O_value( std::string const& user_password, std::string const& owner_password) const { // Algorithm 3.3 from the PDF 1.7 Reference Manual @@ -431,7 +538,7 @@ QPDF::EncryptionData::compute_O_value( } std::string -QPDF::EncryptionData::compute_U_value_R2(std::string const& user_password) const +Encryption::compute_U_value_R2(std::string const& user_password) const { // Algorithm 3.4 from the PDF 1.7 Reference Manual @@ -443,7 +550,7 @@ QPDF::EncryptionData::compute_U_value_R2(std::string const& user_password) const } std::string -QPDF::EncryptionData::compute_U_value_R3(std::string const& user_password) const +Encryption::compute_U_value_R3(std::string const& user_password) const { // Algorithm 3.5 from the PDF 1.7 Reference Manual @@ -460,7 +567,7 @@ QPDF::EncryptionData::compute_U_value_R3(std::string const& user_password) const } std::string -QPDF::EncryptionData::compute_U_value(std::string const& user_password) const +Encryption::compute_U_value(std::string const& user_password) const { if (getR() >= 3) { return compute_U_value_R3(user_password); @@ -470,7 +577,7 @@ QPDF::EncryptionData::compute_U_value(std::string const& user_password) const } bool -QPDF::EncryptionData::check_user_password_V4(std::string const& user_password) const +Encryption::check_user_password_V4(std::string const& user_password) const { // Algorithm 3.6 from the PDF 1.7 Reference Manual @@ -480,7 +587,7 @@ QPDF::EncryptionData::check_user_password_V4(std::string const& user_password) c } bool -QPDF::EncryptionData::check_user_password_V5(std::string const& user_password) const +Encryption::check_user_password_V5(std::string const& user_password) const { // Algorithm 3.11 from the PDF 1.7 extension level 3 @@ -491,7 +598,7 @@ QPDF::EncryptionData::check_user_password_V5(std::string const& user_password) c } bool -QPDF::EncryptionData::check_user_password(std::string const& user_password) const +Encryption::check_user_password(std::string const& user_password) const { if (getV() < 5) { return check_user_password_V4(user_password); @@ -501,7 +608,7 @@ QPDF::EncryptionData::check_user_password(std::string const& user_password) cons } bool -QPDF::EncryptionData::check_owner_password_V4( +Encryption::check_owner_password_V4( std::string& user_password, std::string const& owner_password) const { // Algorithm 3.7 from the PDF 1.7 Reference Manual @@ -518,7 +625,7 @@ QPDF::EncryptionData::check_owner_password_V4( } bool -QPDF::EncryptionData::check_owner_password_V5(std::string const& owner_password) const +Encryption::check_owner_password_V5(std::string const& owner_password) const { // Algorithm 3.12 from the PDF 1.7 extension level 3 @@ -529,7 +636,7 @@ QPDF::EncryptionData::check_owner_password_V5(std::string const& owner_password) } bool -QPDF::EncryptionData::check_owner_password( +Encryption::check_owner_password( std::string& user_password, std::string const& owner_password) const { if (getV() < 5) { @@ -540,7 +647,7 @@ QPDF::EncryptionData::check_owner_password( } std::string -QPDF::EncryptionData::recover_encryption_key_with_password(std::string const& password) const +Encryption::recover_encryption_key_with_password(std::string const& password) const { // Disregard whether Perms is valid. bool disregard; @@ -548,7 +655,7 @@ QPDF::EncryptionData::recover_encryption_key_with_password(std::string const& pa } std::string -QPDF::EncryptionData::compute_Perms_value_V5_clear() const +Encryption::compute_Perms_value_V5_clear() const { // From algorithm 3.10 from the PDF 1.7 extension level 3 std::string k = " \xff\xff\xff\xffTadb "; @@ -565,7 +672,7 @@ QPDF::EncryptionData::compute_Perms_value_V5_clear() const } std::string -QPDF::EncryptionData::recover_encryption_key_with_password( +Encryption::recover_encryption_key_with_password( std::string const& password, bool& perms_valid) const { // Algorithm 3.2a from the PDF 1.7 extension level 3 @@ -795,7 +902,7 @@ QPDF::EncryptionParameters::initialize(QPDF& qpdf) } } - EncryptionData data(V, R, Length / 8, p, O, U, OE, UE, Perms, id1, encrypt_metadata); + Encryption data(V, R, Length / 8, p, O, U, OE, UE, Perms, id1, encrypt_metadata); if (qm.provided_password_is_hex_key) { // ignore passwords in file encryption_key = QUtil::hex_decode(provided_password); @@ -1023,14 +1130,14 @@ QPDF::compute_encryption_O_U( std::string& out_O, std::string& out_U) { - EncryptionData data(V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata); + Encryption data(V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata); data.compute_encryption_O_U(user_password, owner_password); out_O = data.getO(); out_U = data.getU(); } void -QPDF::EncryptionData::compute_encryption_O_U(char const* user_password, char const* owner_password) +Encryption::compute_encryption_O_U(char const* user_password, char const* owner_password) { if (V >= 5) { throw std::logic_error("compute_encryption_O_U called for file with V >= 5"); @@ -1056,7 +1163,7 @@ QPDF::compute_encryption_parameters_V5( std::string& out_UE, std::string& out_Perms) { - EncryptionData data(V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata); + Encryption data(V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata); encryption_key = data.compute_encryption_parameters_V5(user_password, owner_password); out_O = data.getO(); @@ -1067,8 +1174,7 @@ QPDF::compute_encryption_parameters_V5( } std::string -QPDF::EncryptionData::compute_encryption_parameters_V5( - char const* user_password, char const* owner_password) +Encryption::compute_encryption_parameters_V5(char const* user_password, char const* owner_password) { auto out_encryption_key = util::random_string(key_bytes); // Algorithm 8 from the PDF 2.0 @@ -1089,7 +1195,7 @@ QPDF::EncryptionData::compute_encryption_parameters_V5( } std::string -QPDF::EncryptionData::compute_parameters(char const* user_password, char const* owner_password) +Encryption::compute_parameters(char const* user_password, char const* owner_password) { if (V < 5) { compute_encryption_O_U(user_password, owner_password); diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc index e27a84b..b6f4f17 100644 --- a/libqpdf/QPDF_json.cc +++ b/libqpdf/QPDF_json.cc @@ -277,6 +277,7 @@ class QPDF::JSONReactor: public JSON::Reactor void replaceObject(QPDFObjectHandle&& replacement, JSON const& value); QPDF& pdf; + QPDF::Doc::Objects& objects = pdf.m->objects; std::shared_ptr is; bool must_be_complete{true}; std::shared_ptr descr; @@ -541,7 +542,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) } else if (is_obj_key(key, obj, gen)) { this->cur_object = key; if (setNextStateIfDictionary(key, value, st_object_top)) { - next_obj = pdf.getObjectForJSON(obj, gen); + next_obj = objects.getObjectForJSON(obj, gen); } } else { QTC::TC("qpdf", "QPDF_json bad object key"); @@ -743,7 +744,7 @@ QPDF::JSONReactor::makeObject(JSON const& value) int gen = 0; std::string str; if (is_indirect_object(str_v, obj, gen)) { - result = pdf.getObjectForJSON(obj, gen); + result = objects.getObjectForJSON(obj, gen); } else if (is_unicode_string(str_v, str)) { result = QPDFObjectHandle::newUnicodeString(str); } else if (is_binary_string(str_v, str)) { diff --git a/libqpdf/QPDF_linearization.cc b/libqpdf/QPDF_linearization.cc index 0eef521..3ac797b 100644 --- a/libqpdf/QPDF_linearization.cc +++ b/libqpdf/QPDF_linearization.cc @@ -24,6 +24,8 @@ using namespace qpdf; using namespace std::literals; +using Lin = QPDF::Doc::Linearization; + template static void load_vector_int( @@ -68,21 +70,21 @@ load_vector_vector( } void -QPDF::linearizationWarning(std::string_view msg) +Lin::linearizationWarning(std::string_view msg) { m->linearization_warnings = true; - warn(qpdf_e_linearization, "", 0, std::string(msg)); + qpdf.warn(qpdf_e_linearization, "", 0, std::string(msg)); } bool QPDF::checkLinearization() { try { - readLinearizationData(); - checkLinearizationInternal(); + m->lin.readLinearizationData(); + m->lin.checkLinearizationInternal(); return !m->linearization_warnings; } catch (std::runtime_error& e) { - linearizationWarning( + m->lin.linearizationWarning( "error encountered while checking linearization data: " + std::string(e.what())); return false; } @@ -112,9 +114,9 @@ QPDF::isLinearized() // next iteration. m->file->seek(toO(pos), SEEK_SET); - auto t1 = readToken(*m->file, 20); - if (!(t1.isInteger() && readToken(*m->file, 6).isInteger() && - readToken(*m->file, 4).isWord("obj"))) { + auto t1 = m->objects.readToken(*m->file, 20); + if (!(t1.isInteger() && m->objects.readToken(*m->file, 6).isInteger() && + m->objects.readToken(*m->file, 4).isWord("obj"))) { pos = buffer.find_first_not_of("0123456789"sv, pos); if (pos == std::string::npos) { return false; @@ -140,10 +142,10 @@ QPDF::isLinearized() } void -QPDF::readLinearizationData() +Lin::readLinearizationData() { util::assertion( - isLinearized(), "called readLinearizationData for file that is not linearized" // + qpdf.isLinearized(), "called readLinearizationData for file that is not linearized" // ); // This function throws an exception (which is trapped by checkLinearization()) for any errors @@ -164,19 +166,19 @@ QPDF::readLinearizationData() Integer P = P_oh; // first page number QTC::TC("qpdf", "QPDF P absent in lindict", P ? 0 : 1); - no_ci_stop_if( + qpdf.no_ci_stop_if( !(H && O && E && N && T && (P || P_oh.null())), "some keys in linearization dictionary are of the wrong type", "linearization dictionary" // ); - no_ci_stop_if( + qpdf.no_ci_stop_if( !(H_size == 2 || H_size == 4), "H has the wrong number of items", "linearization dictionary" // ); - no_ci_stop_if( + qpdf.no_ci_stop_if( !(H_0 && H_1 && (H_size == 2 || (H_2 && H_3))), "some H items are of the wrong type", "linearization dictionary" // @@ -186,8 +188,8 @@ QPDF::readLinearizationData() // Various places in the code use linp.npages, which is initialized from N, to pre-allocate // memory, so make sure it's accurate and bail right now if it's not. - no_ci_stop_if( - N != getAllPages().size(), + qpdf.no_ci_stop_if( + N != qpdf.getAllPages().size(), "/N does not match number of pages", "linearization dictionary" // ); @@ -232,12 +234,13 @@ QPDF::readLinearizationData() size_t HSi = HS; if (HSi < 0 || HSi >= h_size) { - throw damagedPDF("linearization hint table", "/S (shared object) offset is out of bounds"); + throw qpdf.damagedPDF( + "linearization hint table", "/S (shared object) offset is out of bounds"); } readHSharedObject(BitStream(h_buf + HSi, h_size - HSi)); if (HO) { - no_ci_stop_if( + qpdf.no_ci_stop_if( HO < 0 || HO >= h_size, "/O (outline) offset is out of bounds", "linearization dictionary" // @@ -248,13 +251,13 @@ QPDF::readLinearizationData() } Dictionary -QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) +Lin::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) { - auto H = readObjectAtOffset(offset, "linearization hint stream", false); + auto H = m->objects.readObjectAtOffset(offset, "linearization hint stream", false); ObjCache& oc = m->obj_cache[H]; qpdf_offset_t min_end_offset = oc.end_before_space; qpdf_offset_t max_end_offset = oc.end_after_space; - no_ci_stop_if( + qpdf.no_ci_stop_if( !H.isStream(), "hint table is not a stream", "linearization dictionary" // ); @@ -272,7 +275,7 @@ QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) QTC::TC("qpdf", "QPDF hint table length direct"); } qpdf_offset_t computed_end = offset + toO(length); - no_ci_stop_if( + qpdf.no_ci_stop_if( computed_end < min_end_offset || computed_end > max_end_offset, "hint table length mismatch (expected = " + std::to_string(computed_end) + "; actual = " + std::to_string(min_end_offset) + ".." + std::to_string(max_end_offset) + ")", @@ -283,7 +286,7 @@ QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) } void -QPDF::readHPageOffset(BitStream h) +Lin::readHPageOffset(BitStream h) { // All comments referring to the PDF spec refer to the spec for version 1.4. @@ -332,7 +335,7 @@ QPDF::readHPageOffset(BitStream h) } void -QPDF::readHSharedObject(BitStream h) +Lin::readHSharedObject(BitStream h) { HSharedObject& t = m->shared_object_hints; @@ -368,7 +371,7 @@ QPDF::readHSharedObject(BitStream h) } void -QPDF::readHGeneric(BitStream h, HGeneric& t) +Lin::readHGeneric(BitStream h, HGeneric& t) { t.first_object = h.getBitsInt(32); // 1 t.first_object_offset = h.getBitsInt(32); // 2 @@ -377,7 +380,7 @@ QPDF::readHGeneric(BitStream h, HGeneric& t) } void -QPDF::checkLinearizationInternal() +Lin::checkLinearizationInternal() { // All comments referring to the PDF spec refer to the spec for version 1.4. @@ -388,7 +391,7 @@ QPDF::checkLinearizationInternal() // L: file size in bytes -- checked by isLinearized // O: object number of first page - std::vector const& pages = getAllPages(); + std::vector const& pages = qpdf.getAllPages(); if (p.first_page_object != pages.at(0).getObjectID()) { linearizationWarning("first page object (/O) mismatch"); } @@ -461,7 +464,7 @@ QPDF::checkLinearizationInternal() // are present. In that case, it would probably agree with pdlin. As of this writing, the test // suite doesn't contain any files with threads. - no_ci_stop_if( + qpdf.no_ci_stop_if( m->part6.empty(), "linearization part 6 unexpectedly empty" // ); qpdf_offset_t min_E = -1; @@ -489,16 +492,16 @@ QPDF::checkLinearizationInternal() } qpdf_offset_t -QPDF::maxEnd(ObjUser const& ou) +Lin::maxEnd(ObjUser const& ou) { - no_ci_stop_if( + qpdf.no_ci_stop_if( !m->obj_user_to_objects.contains(ou), "no entry in object user table for requested object user" // ); qpdf_offset_t end = 0; for (auto const& og: m->obj_user_to_objects[ou]) { - no_ci_stop_if( + qpdf.no_ci_stop_if( !m->obj_cache.contains(og), "unknown object referenced in object user table" // ); end = std::max(end, m->obj_cache[og].end_after_space); @@ -507,14 +510,14 @@ QPDF::maxEnd(ObjUser const& ou) } qpdf_offset_t -QPDF::getLinearizationOffset(QPDFObjGen og) +Lin::getLinearizationOffset(QPDFObjGen og) { QPDFXRefEntry const& entry = m->xref_table[og]; auto typ = entry.getType(); if (typ == 1) { return entry.getOffset(); } - no_ci_stop_if( + qpdf.no_ci_stop_if( typ != 2, "getLinearizationOffset called for xref entry not of type 1 or 2" // ); // For compressed objects, return the offset of the object stream that contains them. @@ -522,33 +525,33 @@ QPDF::getLinearizationOffset(QPDFObjGen og) } QPDFObjectHandle -QPDF::getUncompressedObject(QPDFObjectHandle& obj, std::map const& object_stream_data) +Lin::getUncompressedObject(QPDFObjectHandle& obj, std::map const& object_stream_data) { if (obj.null() || !object_stream_data.contains(obj.getObjectID())) { return obj; } - return getObject((*(object_stream_data.find(obj.getObjectID()))).second, 0); + return qpdf.getObject((*(object_stream_data.find(obj.getObjectID()))).second, 0); } QPDFObjectHandle -QPDF::getUncompressedObject(QPDFObjectHandle& oh, QPDFWriter::ObjTable const& obj) +Lin::getUncompressedObject(QPDFObjectHandle& oh, QPDFWriter::ObjTable const& obj) { if (obj.contains(oh)) { if (auto id = obj[oh].object_stream; id > 0) { - return oh.null() ? oh : getObject(id, 0); + return oh.null() ? oh : qpdf.getObject(id, 0); } } return oh; } int -QPDF::lengthNextN(int first_object, int n) +Lin::lengthNextN(int first_object, int n) { int length = 0; for (int i = 0; i < n; ++i) { QPDFObjGen og(first_object + i, 0); if (m->xref_table.contains(og)) { - no_ci_stop_if( + qpdf.no_ci_stop_if( !m->obj_cache.contains(og), "found unknown object while calculating length for linearization data" // ); @@ -563,7 +566,7 @@ QPDF::lengthNextN(int first_object, int n) } void -QPDF::checkHPageOffset( +Lin::checkHPageOffset( std::vector const& pages, std::map& shared_idx_to_obj) { // Implementation note 126 says Acrobat always sets delta_content_offset and @@ -582,7 +585,7 @@ QPDF::checkHPageOffset( qpdf_offset_t table_offset = adjusted_offset(m->page_offset_hints.first_page_offset); QPDFObjGen first_page_og(pages.at(0).getObjGen()); if (!m->xref_table.contains(first_page_og)) { - stopOnError("supposed first page object is not known"); + qpdf.stopOnError("supposed first page object is not known"); } qpdf_offset_t offset = getLinearizationOffset(first_page_og); if (table_offset != offset) { @@ -593,7 +596,7 @@ QPDF::checkHPageOffset( QPDFObjGen page_og(pages.at(pageno).getObjGen()); int first_object = page_og.getObj(); if (!m->xref_table.contains(page_og)) { - stopOnError("unknown object in page offset hint table"); + qpdf.stopOnError("unknown object in page offset hint table"); } offset = getLinearizationOffset(page_og); @@ -633,7 +636,7 @@ QPDF::checkHPageOffset( for (size_t i = 0; i < toS(he.nshared_objects); ++i) { int idx = he.shared_identifiers.at(i); - no_ci_stop_if( + qpdf.no_ci_stop_if( !shared_idx_to_obj.contains(idx), "unable to get object for item in shared objects hint table"); @@ -642,7 +645,7 @@ QPDF::checkHPageOffset( for (size_t i = 0; i < toS(ce.nshared_objects); ++i) { int idx = ce.shared_identifiers.at(i); - no_ci_stop_if( + qpdf.no_ci_stop_if( idx >= m->c_shared_object_data.nshared_total, "index out of bounds for shared object hint table" // ); @@ -673,7 +676,7 @@ QPDF::checkHPageOffset( } void -QPDF::checkHSharedObject(std::vector const& pages, std::map& idx_to_obj) +Lin::checkHSharedObject(std::vector const& pages, std::map& idx_to_obj) { // Implementation note 125 says shared object groups always contain only one object. // Implementation note 128 says that Acrobat always nbits_nobjects to zero. Implementation note @@ -715,7 +718,7 @@ QPDF::checkHSharedObject(std::vector const& pages, std::mapxref_table.contains(og)) { - stopOnError("unknown object in shared object hint table"); + qpdf.stopOnError("unknown object in shared object hint table"); } qpdf_offset_t offset = getLinearizationOffset(og); qpdf_offset_t h_offset = adjusted_offset(so.first_shared_offset); @@ -742,7 +745,7 @@ QPDF::checkHSharedObject(std::vector const& pages, std::mapc_outline_data.first_object == m->outline_hints.first_object) { // Check length and offset. Acrobat gets these wrong. - QPDFObjectHandle outlines = getRoot().getKey("/Outlines"); + QPDFObjectHandle outlines = qpdf.getRoot().getKey("/Outlines"); if (!outlines.isIndirect()) { // This case is not exercised in test suite since not permitted by the spec, but if // this does occur, the code below would fail. @@ -765,7 +768,7 @@ QPDF::checkHOutlines() return; } QPDFObjGen og(outlines.getObjGen()); - no_ci_stop_if( + qpdf.no_ci_stop_if( !m->xref_table.contains(og), "unknown object in outlines hint table" // ); qpdf_offset_t offset = getLinearizationOffset(og); @@ -795,16 +798,16 @@ void QPDF::showLinearizationData() { try { - readLinearizationData(); - checkLinearizationInternal(); - dumpLinearizationDataInternal(); + m->lin.readLinearizationData(); + m->lin.checkLinearizationInternal(); + m->lin.dumpLinearizationDataInternal(); } catch (QPDFExc& e) { - linearizationWarning(e.what()); + m->lin.linearizationWarning(e.what()); } } void -QPDF::dumpLinearizationDataInternal() +Lin::dumpLinearizationDataInternal() { *m->log->getInfo() << m->file->getName() << ": linearization data:\n\n"; @@ -830,7 +833,7 @@ QPDF::dumpLinearizationDataInternal() } qpdf_offset_t -QPDF::adjusted_offset(qpdf_offset_t offset) +Lin::adjusted_offset(qpdf_offset_t offset) { // All offsets >= H_offset have to be increased by H_length since all hint table location values // disregard the hint table itself. @@ -841,7 +844,7 @@ QPDF::adjusted_offset(qpdf_offset_t offset) } void -QPDF::dumpHPageOffset() +Lin::dumpHPageOffset() { HPageOffset& t = m->page_offset_hints; *m->log->getInfo() << "min_nobjects: " << t.min_nobjects << "\n" @@ -880,7 +883,7 @@ QPDF::dumpHPageOffset() } void -QPDF::dumpHSharedObject() +Lin::dumpHSharedObject() { HSharedObject& t = m->shared_object_hints; *m->log->getInfo() << "first_shared_obj: " << t.first_shared_obj << "\n" @@ -908,7 +911,7 @@ QPDF::dumpHSharedObject() } void -QPDF::dumpHGeneric(HGeneric& t) +Lin::dumpHGeneric(HGeneric& t) { *m->log->getInfo() << "first_object: " << t.first_object << "\n" << "first_object_offset: " << adjusted_offset(t.first_object_offset) << "\n" @@ -918,7 +921,7 @@ QPDF::dumpHGeneric(HGeneric& t) template void -QPDF::calculateLinearizationData(T const& object_stream_data) +Lin::calculateLinearizationData(T const& object_stream_data) { // This function calculates the ordering of objects, divides them into the appropriate parts, // and computes some values for the linearization parameter dictionary and hint tables. The @@ -985,7 +988,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) m->c_shared_object_data = CHSharedObject(); m->c_outline_data = HGeneric(); - QPDFObjectHandle root = getRoot(); + QPDFObjectHandle root = qpdf.getRoot(); bool outlines_in_first_page = false; QPDFObjectHandle pagemode = root.getKey("/PageMode"); QTC::TC("qpdf", "QPDF categorize pagemode present", pagemode.isName() ? 1 : 0); @@ -1106,7 +1109,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) { // local scope // Map all page objects to the containing object stream. This should be a no-op in a // properly linearized file. - for (auto oh: getAllPages()) { + for (auto oh: qpdf.getAllPages()) { pages.emplace_back(getUncompressedObject(oh, object_stream_data)); } } @@ -1125,13 +1128,13 @@ QPDF::calculateLinearizationData(T const& object_stream_data) // Part 4: open document objects. We don't care about the order. - no_ci_stop_if( + qpdf.no_ci_stop_if( lc_root.size() != 1, "found other than one root while calculating linearization data" // ); - m->part4.emplace_back(getObject(*(lc_root.begin()))); + m->part4.emplace_back(qpdf.getObject(*(lc_root.begin()))); for (auto const& og: lc_open_document) { - m->part4.emplace_back(getObject(og)); + m->part4.emplace_back(qpdf.getObject(og)); } // Part 6: first page objects. Note: implementation note 124 states that Acrobat always treats @@ -1139,11 +1142,11 @@ QPDF::calculateLinearizationData(T const& object_stream_data) // any option to set this and also disregards /OpenAction. We will do the same. // First, place the actual first page object itself. - no_ci_stop_if( + qpdf.no_ci_stop_if( pages.empty(), "no pages found while calculating linearization data" // ); QPDFObjGen first_page_og(pages.at(0).getObjGen()); - no_ci_stop_if( + qpdf.no_ci_stop_if( !lc_first_page_private.erase(first_page_og), "unable to linearize first page" // ); m->c_linp.first_page_object = pages.at(0).getObjectID(); @@ -1154,11 +1157,11 @@ QPDF::calculateLinearizationData(T const& object_stream_data) // of hint tables. for (auto const& og: lc_first_page_private) { - m->part6.emplace_back(getObject(og)); + m->part6.emplace_back(qpdf.getObject(og)); } for (auto const& og: lc_first_page_shared) { - m->part6.emplace_back(getObject(og)); + m->part6.emplace_back(qpdf.getObject(og)); } // Place the outline dictionary if it goes in the first page section. @@ -1179,7 +1182,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) // Place this page's page object QPDFObjGen page_og(pages.at(i).getObjGen()); - no_ci_stop_if( + qpdf.no_ci_stop_if( !lc_other_page_private.erase(page_og), "unable to linearize page " + std::to_string(i) // ); @@ -1192,14 +1195,14 @@ QPDF::calculateLinearizationData(T const& object_stream_data) m->c_page_offset_data.entries.at(i).nobjects = 1; ObjUser ou(ObjUser::ou_page, i); - no_ci_stop_if( + qpdf.no_ci_stop_if( !m->obj_user_to_objects.contains(ou), "found unreferenced page while calculating linearization data" // ); for (auto const& og: m->obj_user_to_objects[ou]) { if (lc_other_page_private.erase(og)) { - m->part7.emplace_back(getObject(og)); + m->part7.emplace_back(qpdf.getObject(og)); ++m->c_page_offset_data.entries.at(i).nobjects; } } @@ -1215,7 +1218,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) // Order is unimportant. for (auto const& og: lc_other_page_shared) { - m->part8.emplace_back(getObject(og)); + m->part8.emplace_back(qpdf.getObject(og)); } // Part 9: other objects @@ -1228,12 +1231,12 @@ QPDF::calculateLinearizationData(T const& object_stream_data) // Place the pages tree. std::set pages_ogs = m->obj_user_to_objects[ObjUser(ObjUser::ou_root_key, "/Pages")]; - no_ci_stop_if( + qpdf.no_ci_stop_if( pages_ogs.empty(), "found empty pages tree while calculating linearization data" // ); for (auto const& og: pages_ogs) { if (lc_other.erase(og)) { - m->part9.emplace_back(getObject(og)); + m->part9.emplace_back(qpdf.getObject(og)); } } @@ -1255,7 +1258,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) std::set& ogs = m->obj_user_to_objects[ObjUser(ObjUser::ou_thumb, i)]; for (auto const& og: ogs) { if (lc_thumbnail_private.erase(og)) { - m->part9.emplace_back(getObject(og)); + m->part9.emplace_back(qpdf.getObject(og)); } } } @@ -1267,7 +1270,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) // Place shared thumbnail objects for (auto const& og: lc_thumbnail_shared) { - m->part9.emplace_back(getObject(og)); + m->part9.emplace_back(qpdf.getObject(og)); } // Place outlines unless in first page @@ -1277,7 +1280,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) // Place all remaining objects for (auto const& og: lc_other) { - m->part9.emplace_back(getObject(og)); + m->part9.emplace_back(qpdf.getObject(og)); } // Make sure we got everything exactly once. @@ -1285,7 +1288,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) size_t num_placed = m->part4.size() + m->part6.size() + m->part7.size() + m->part8.size() + m->part9.size(); size_t num_wanted = m->object_to_obj_users.size(); - no_ci_stop_if( + qpdf.no_ci_stop_if( // This can happen with damaged files, e.g. if the root is part of the the pages tree. num_placed != num_wanted, "QPDF::calculateLinearizationData: wrong number of objects placed (num_placed = " + @@ -1323,7 +1326,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) shared.emplace_back(obj); } } - no_ci_stop_if( + qpdf.no_ci_stop_if( std::cmp_not_equal( m->c_shared_object_data.nshared_total, m->c_shared_object_data.entries.size()), "shared object hint table has wrong number of entries" // @@ -1334,7 +1337,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) for (size_t i = 1; i < npages; ++i) { CHPageOffsetEntry& pe = m->c_page_offset_data.entries.at(i); ObjUser ou(ObjUser::ou_page, i); - no_ci_stop_if( + qpdf.no_ci_stop_if( !m->obj_user_to_objects.contains(ou), "found unreferenced page while calculating linearization data" // ); @@ -1351,12 +1354,12 @@ QPDF::calculateLinearizationData(T const& object_stream_data) template void -QPDF::pushOutlinesToPart( +Lin::pushOutlinesToPart( std::vector& part, std::set& lc_outlines, T const& object_stream_data) { - QPDFObjectHandle root = getRoot(); + QPDFObjectHandle root = qpdf.getRoot(); QPDFObjectHandle outlines = root.getKey("/Outlines"); if (outlines.null()) { return; @@ -1380,13 +1383,13 @@ QPDF::pushOutlinesToPart( if (!m->c_outline_data.first_object) { m->c_outline_data.first_object = og.getObj(); } - part.emplace_back(getObject(og)); + part.emplace_back(qpdf.getObject(og)); ++m->c_outline_data.nobjects; } } void -QPDF::getLinearizedParts( +Lin::getLinearizedParts( QPDFWriter::ObjTable const& obj, std::vector& part4, std::vector& part6, @@ -1409,7 +1412,7 @@ nbits(int val) } int -QPDF::outputLengthNextN( +Lin::outputLengthNextN( int in_object, int n, QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj) { // Figure out the length of a series of n consecutive objects in the output file starting with @@ -1417,12 +1420,12 @@ QPDF::outputLengthNextN( int first = obj[in_object].renumber; int last = first + n; - no_ci_stop_if( + qpdf.no_ci_stop_if( first <= 0, "found object that is not renumbered while writing linearization data"); qpdf_offset_t length = 0; for (int i = first; i < last; ++i) { auto l = new_obj[i].length; - no_ci_stop_if( + qpdf.no_ci_stop_if( l == 0, "found item with unknown length while writing linearization data" // ); length += l; @@ -1431,13 +1434,13 @@ QPDF::outputLengthNextN( } void -QPDF::calculateHPageOffset(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj) +Lin::calculateHPageOffset(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj) { // Page Offset Hint Table // We are purposely leaving some values set to their initial zero values. - std::vector const& pages = getAllPages(); + std::vector const& pages = qpdf.getAllPages(); size_t npages = pages.size(); CHPageOffset& cph = m->c_page_offset_data; std::vector& cphe = cph.entries; @@ -1499,7 +1502,7 @@ QPDF::calculateHPageOffset(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::O for (auto& phe_i: phe) { // Adjust delta entries if (phe_i.delta_nobjects < min_nobjects || phe_i.delta_page_length < min_length) { - stopOnError( + qpdf.stopOnError( "found too small delta nobjects or delta page length while writing " "linearization data"); } @@ -1515,8 +1518,7 @@ QPDF::calculateHPageOffset(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::O } void -QPDF::calculateHSharedObject( - QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj) +Lin::calculateHSharedObject(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj) { CHSharedObject& cso = m->c_shared_object_data; std::vector& csoe = cso.entries; @@ -1535,7 +1537,7 @@ QPDF::calculateHSharedObject( soe.emplace_back(); soe.at(i).delta_group_length = length; } - no_ci_stop_if( + qpdf.no_ci_stop_if( soe.size() != toS(cso.nshared_total), "soe has wrong size after initialization" // ); @@ -1551,7 +1553,7 @@ QPDF::calculateHSharedObject( for (size_t i = 0; i < toS(cso.nshared_total); ++i) { // Adjust deltas - no_ci_stop_if( + qpdf.no_ci_stop_if( soe.at(i).delta_group_length < min_length, "found too small group length while writing linearization data" // ); @@ -1561,7 +1563,7 @@ QPDF::calculateHSharedObject( } void -QPDF::calculateHOutline(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj) +Lin::calculateHOutline(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj) { HGeneric& cho = m->c_outline_data; @@ -1612,7 +1614,7 @@ write_vector_vector( } void -QPDF::writeHPageOffset(BitWriter& w) +Lin::writeHPageOffset(BitWriter& w) { HPageOffset& t = m->page_offset_hints; @@ -1630,7 +1632,7 @@ QPDF::writeHPageOffset(BitWriter& w) w.writeBitsInt(t.nbits_shared_numerator, 16); // 12 w.writeBitsInt(t.shared_denominator, 16); // 13 - int nitems = toI(getAllPages().size()); + int nitems = toI(qpdf.getAllPages().size()); std::vector& entries = t.entries; write_vector_int(w, nitems, entries, t.nbits_delta_nobjects, &HPageOffsetEntry::delta_nobjects); @@ -1659,7 +1661,7 @@ QPDF::writeHPageOffset(BitWriter& w) } void -QPDF::writeHSharedObject(BitWriter& w) +Lin::writeHSharedObject(BitWriter& w) { HSharedObject& t = m->shared_object_hints; @@ -1685,14 +1687,14 @@ QPDF::writeHSharedObject(BitWriter& w) for (size_t i = 0; i < toS(nitems); ++i) { // If signature were present, we'd have to write a 128-bit hash. if (entries.at(i).signature_present != 0) { - stopOnError("found unexpected signature present while writing linearization data"); + qpdf.stopOnError("found unexpected signature present while writing linearization data"); } } write_vector_int(w, nitems, entries, t.nbits_nobjects, &HSharedObjectEntry::nobjects_minus_one); } void -QPDF::writeHGeneric(BitWriter& w, HGeneric& t) +Lin::writeHGeneric(BitWriter& w, HGeneric& t) { w.writeBitsInt(t.first_object, 32); // 1 w.writeBits(toULL(t.first_object_offset), 32); // 2 @@ -1701,7 +1703,7 @@ QPDF::writeHGeneric(BitWriter& w, HGeneric& t) } void -QPDF::generateHintStream( +Lin::generateHintStream( QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj, std::string& hint_buffer, diff --git a/libqpdf/QPDF_objects.cc b/libqpdf/QPDF_objects.cc index 44234f2..7a0726e 100644 --- a/libqpdf/QPDF_objects.cc +++ b/libqpdf/QPDF_objects.cc @@ -23,6 +23,8 @@ using namespace qpdf; using namespace std::literals; +using Objects = QPDF::Doc::Objects; + namespace { class InvalidInputSource: public InputSource @@ -102,7 +104,8 @@ class QPDF::ResolveRecorder final bool QPDF::findStartxref() { - if (readToken(*m->file).isWord("startxref") && readToken(*m->file).isInteger()) { + if (m->objects.readToken(*m->file).isWord("startxref") && + m->objects.readToken(*m->file).isInteger()) { // Position in front of offset token m->file->seek(m->file->getLastOffset(), SEEK_SET); return true; @@ -111,17 +114,16 @@ QPDF::findStartxref() } void -QPDF::parse(char const* password) +Objects::parse(char const* password) { if (password) { m->encp->provided_password = password; } // Find the header anywhere in the first 1024 bytes of the file. - PatternFinder hf(*this, &QPDF::findHeader); + PatternFinder hf(qpdf, &QPDF::findHeader); if (!m->file->findFirst("%PDF-", 0, 1024, hf)) { - QTC::TC("qpdf", "QPDF not a pdf file"); - warn(damagedPDF("", -1, "can't find PDF header")); + qpdf.warn(qpdf.damagedPDF("", -1, "can't find PDF header")); // QPDFWriter writes files that usually require at least version 1.2 for /FlateDecode m->pdf_version = "1.2"; } @@ -137,7 +139,7 @@ QPDF::parse(char const* password) m->xref_table_max_id = static_cast(m->xref_table_max_offset / 3); } qpdf_offset_t start_offset = (end_offset > 1054 ? end_offset - 1054 : 0); - PatternFinder sf(*this, &QPDF::findStartxref); + PatternFinder sf(qpdf, &QPDF::findStartxref); qpdf_offset_t xref_offset = 0; if (m->file->findLast("startxref", start_offset, 0, sf)) { xref_offset = QUtil::string_to_ll(readToken(*m->file).getValue().c_str()); @@ -145,35 +147,33 @@ QPDF::parse(char const* password) try { if (xref_offset == 0) { - QTC::TC("qpdf", "QPDF can't find startxref"); - throw damagedPDF("", -1, "can't find startxref"); + throw qpdf.damagedPDF("", -1, "can't find startxref"); } try { read_xref(xref_offset); } catch (QPDFExc&) { throw; } catch (std::exception& e) { - throw damagedPDF("", -1, std::string("error reading xref: ") + e.what()); + throw qpdf.damagedPDF("", -1, std::string("error reading xref: ") + e.what()); } } catch (QPDFExc& e) { if (m->attempt_recovery) { reconstruct_xref(e, xref_offset > 0); - QTC::TC("qpdf", "QPDF reconstructed xref table"); } else { throw; } } - initializeEncryption(); + qpdf.initializeEncryption(); m->parsed = true; - if (!m->xref_table.empty() && !getRoot().getKey("/Pages").isDictionary()) { + if (!m->xref_table.empty() && !qpdf.getRoot().getKey("/Pages").isDictionary()) { // QPDFs created from JSON have an empty xref table and no root object yet. - throw damagedPDF("", -1, "unable to find page tree"); + throw qpdf.damagedPDF("", -1, "unable to find page tree"); } } void -QPDF::inParse(bool v) +Objects::inParse(bool v) { if (m->in_parse == v) { // This happens if QPDFParser::parse tries to resolve an indirect object while it is @@ -186,7 +186,7 @@ QPDF::inParse(bool v) } void -QPDF::setTrailer(QPDFObjectHandle obj) +Objects::setTrailer(QPDFObjectHandle obj) { if (m->trailer) { return; @@ -195,7 +195,7 @@ QPDF::setTrailer(QPDFObjectHandle obj) } void -QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref) +Objects::reconstruct_xref(QPDFExc& e, bool found_startxref) { if (m->reconstructed_xref) { // Avoid xref reconstruction infinite loops. This is getting very hard to reproduce because @@ -208,7 +208,8 @@ QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref) const auto max_warnings = m->warnings.size() + 1000U; auto check_warnings = [this, max_warnings]() { if (m->warnings.size() > max_warnings) { - throw damagedPDF("", -1, "too many errors while reconstructing cross-reference table"); + throw qpdf.damagedPDF( + "", -1, "too many errors while reconstructing cross-reference table"); } }; @@ -216,9 +217,9 @@ QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref) // We may find more objects, which may contain dangling references. m->fixed_dangling_refs = false; - warn(damagedPDF("", -1, "file is damaged")); - warn(e); - warn(damagedPDF("", -1, "Attempting to reconstruct cross-reference table")); + qpdf.warn(qpdf.damagedPDF("", -1, "file is damaged")); + qpdf.warn(e); + qpdf.warn(qpdf.damagedPDF("", -1, "Attempting to reconstruct cross-reference table")); // Delete all references to type 1 (uncompressed) objects std::vector to_delete; @@ -241,18 +242,18 @@ QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref) // Don't allow very long tokens here during recovery. All the interesting tokens are covered. static size_t const MAX_LEN = 10; while (m->file->tell() < eof) { - QPDFTokenizer::Token t1 = readToken(*m->file, MAX_LEN); + QPDFTokenizer::Token t1 = m->objects.readToken(*m->file, MAX_LEN); qpdf_offset_t token_start = m->file->tell() - toO(t1.getValue().length()); if (t1.isInteger()) { auto pos = m->file->tell(); - auto t2 = readToken(*m->file, MAX_LEN); - if (t2.isInteger() && readToken(*m->file, MAX_LEN).isWord("obj")) { + auto t2 = m->objects.readToken(*m->file, MAX_LEN); + if (t2.isInteger() && m->objects.readToken(*m->file, MAX_LEN).isWord("obj")) { int obj = QUtil::string_to_int(t1.getValue().c_str()); int gen = QUtil::string_to_int(t2.getValue().c_str()); if (obj <= m->xref_table_max_id) { found_objects.emplace_back(obj, gen, token_start); } else { - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( "", -1, "ignoring object with impossibly large id " + std::to_string(obj))); } } @@ -271,14 +272,15 @@ QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref) auto xref_backup{m->xref_table}; try { m->file->seek(startxrefs.back(), SEEK_SET); - if (auto offset = QUtil::string_to_ll(readToken(*m->file).getValue().data())) { - read_xref(offset); + if (auto offset = + QUtil::string_to_ll(m->objects.readToken(*m->file).getValue().data())) { + m->objects.read_xref(offset); - if (getRoot().getKey("/Pages").isDictionary()) { + if (qpdf.getRoot().getKey("/Pages").isDictionary()) { QTC::TC("qpdf", "QPDF startxref more than 1024 before end"); - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( "", -1, "startxref was more than 1024 bytes before end of file")); - initializeEncryption(); + qpdf.initializeEncryption(); m->parsed = true; m->reconstructed_xref = false; return; @@ -311,7 +313,7 @@ QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref) m->trailer = t; break; } - warn(damagedPDF("trailer", *it, "recovered trailer has no /Root entry")); + qpdf.warn(qpdf.damagedPDF("trailer", *it, "recovered trailer has no /Root entry")); } check_warnings(); } @@ -325,7 +327,7 @@ QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref) if (entry.getType() != 1) { continue; } - auto oh = getObject(iter.first); + auto oh = qpdf.getObject(iter.first); try { if (!oh.isStreamOfType("/XRef")) { continue; @@ -345,7 +347,7 @@ QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref) try { read_xref(max_offset, true); } catch (std::exception&) { - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( "", -1, "error decoding candidate xref stream while recovering damaged file")); } QTC::TC("qpdf", "QPDF recover xref stream"); @@ -366,7 +368,7 @@ QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref) } if (root) { if (!m->trailer) { - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( "", -1, "unable to find trailer dictionary while recovering damaged file")); m->trailer = QPDFObjectHandle::newDictionary(); } @@ -379,21 +381,22 @@ QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref) // could try to get the trailer from there. This may make it possible to recover files with // bad startxref pointers even when they have object streams. - throw damagedPDF("", -1, "unable to find trailer dictionary while recovering damaged file"); + throw qpdf.damagedPDF( + "", -1, "unable to find trailer dictionary while recovering damaged file"); } if (m->xref_table.empty()) { // We cannot check for an empty xref table in parse because empty tables are valid when // creating QPDF objects from JSON. - throw damagedPDF("", -1, "unable to find objects while recovering damaged file"); + throw qpdf.damagedPDF("", -1, "unable to find objects while recovering damaged file"); } check_warnings(); if (!m->parsed) { m->parsed = true; - getAllPages(); + qpdf.getAllPages(); check_warnings(); if (m->all_pages.empty()) { m->parsed = false; - throw damagedPDF("", -1, "unable to find any pages while recovering damaged file"); + throw qpdf.damagedPDF("", -1, "unable to find any pages while recovering damaged file"); } } @@ -405,7 +408,7 @@ QPDF::reconstruct_xref(QPDFExc& e, bool found_startxref) } void -QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery) +Objects::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery) { std::map free_table; std::set visited; @@ -440,8 +443,7 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery) // where it is terminated by arbitrary whitespace. if ((strncmp(buf, "xref", 4) == 0) && util::is_space(buf[4])) { if (skipped_space) { - QTC::TC("qpdf", "QPDF xref skipped space"); - warn(damagedPDF("", -1, "extraneous whitespace seen before xref")); + qpdf.warn(qpdf.damagedPDF("", -1, "extraneous whitespace seen before xref")); } QTC::TC( "qpdf", @@ -460,13 +462,12 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery) xref_offset = read_xrefStream(xref_offset, in_stream_recovery); } if (visited.contains(xref_offset)) { - QTC::TC("qpdf", "QPDF xref loop"); - throw damagedPDF("", -1, "loop detected following xref tables"); + throw qpdf.damagedPDF("", -1, "loop detected following xref tables"); } } if (!m->trailer) { - throw damagedPDF("", -1, "unable to find trailer while reading xref"); + throw qpdf.damagedPDF("", -1, "unable to find trailer while reading xref"); } int size = m->trailer.getKey("/Size").getIntValueAsInt(); int max_obj = 0; @@ -477,8 +478,7 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery) max_obj = std::max(max_obj, *(m->deleted_objects.rbegin())); } if ((size < 1) || (size - 1 != max_obj)) { - QTC::TC("qpdf", "QPDF xref size mismatch"); - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( "", -1, ("reported number of objects (" + std::to_string(size) + @@ -494,14 +494,14 @@ QPDF::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery) for (auto const& item: m->xref_table) { auto id = item.first.getObj(); if (id == last_og.getObj() && id > 0) { - removeObject(last_og); + qpdf.removeObject(last_og); } last_og = item.first; } } bool -QPDF::parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes) +Objects::parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes) { // is_space and is_digit both return false on '\0', so this will not overrun the null-terminated // buffer. @@ -549,7 +549,7 @@ QPDF::parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes) } bool -QPDF::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type) +Objects::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type) { // Reposition after initial read attempt and reread. m->file->seek(m->file->getLastOffset(), SEEK_SET); @@ -563,7 +563,6 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type) bool invalid = false; while (util::is_space(*p)) { ++p; - QTC::TC("qpdf", "QPDF ignore first space in xref entry"); invalid = true; } // Require digit @@ -580,7 +579,6 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type) return false; } if (util::is_space(*(p + 1))) { - QTC::TC("qpdf", "QPDF ignore first extra space in xref entry"); invalid = true; } // Skip spaces @@ -601,7 +599,6 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type) return false; } if (util::is_space(*(p + 1))) { - QTC::TC("qpdf", "QPDF ignore second extra space in xref entry"); invalid = true; } // Skip spaces @@ -614,12 +611,11 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type) return false; } if ((f1_str.length() != 10) || (f2_str.length() != 5)) { - QTC::TC("qpdf", "QPDF ignore length error xref entry"); invalid = true; } if (invalid) { - warn(damagedPDF("xref table", "accepting invalid xref table entry")); + qpdf.warn(qpdf.damagedPDF("xref table", "accepting invalid xref table entry")); } f1 = QUtil::string_to_ll(f1_str.c_str()); @@ -631,7 +627,7 @@ QPDF::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type) // Optimistically read and parse xref entry. If entry is bad, call read_bad_xrefEntry and return // result. bool -QPDF::read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type) +Objects::read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type) { std::array line; if (m->file->read(line.data(), 20) != 20) { @@ -685,7 +681,7 @@ QPDF::read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type) // Read a single cross-reference table section and associated trailer. qpdf_offset_t -QPDF::read_xrefTable(qpdf_offset_t xref_offset) +Objects::read_xrefTable(qpdf_offset_t xref_offset) { m->file->seek(xref_offset, SEEK_SET); std::string line; @@ -696,8 +692,7 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset) int num = 0; int bytes = 0; if (!parse_xrefFirst(line, obj, num, bytes)) { - QTC::TC("qpdf", "QPDF invalid xref"); - throw damagedPDF("xref table", "xref syntax invalid"); + throw qpdf.damagedPDF("xref table", "xref syntax invalid"); } m->file->seek(m->file->getLastOffset() + bytes, SEEK_SET); for (qpdf_offset_t i = obj; i - num < obj; ++i) { @@ -710,8 +705,7 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset) int f2 = 0; char type = '\0'; if (!read_xrefEntry(f1, f2, type)) { - QTC::TC("qpdf", "QPDF invalid xref entry"); - throw damagedPDF( + throw qpdf.damagedPDF( "xref table", "invalid xref entry (obj=" + std::to_string(i) + ")"); } if (type == 'f') { @@ -729,22 +723,19 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset) } // Set offset to previous xref table if any - QPDFObjectHandle cur_trailer = readTrailer(); + QPDFObjectHandle cur_trailer = m->objects.readTrailer(); if (!cur_trailer.isDictionary()) { - QTC::TC("qpdf", "QPDF missing trailer"); - throw damagedPDF("", "expected trailer dictionary"); + throw qpdf.damagedPDF("", "expected trailer dictionary"); } if (!m->trailer) { setTrailer(cur_trailer); if (!m->trailer.hasKey("/Size")) { - QTC::TC("qpdf", "QPDF trailer lacks size"); - throw damagedPDF("trailer", "trailer dictionary lacks /Size key"); + throw qpdf.damagedPDF("trailer", "trailer dictionary lacks /Size key"); } if (!m->trailer.getKey("/Size").isInteger()) { - QTC::TC("qpdf", "QPDF trailer size not integer"); - throw damagedPDF("trailer", "/Size key in trailer dictionary is not an integer"); + throw qpdf.damagedPDF("trailer", "/Size key in trailer dictionary is not an integer"); } } @@ -757,17 +748,15 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset) // /Prev key instead of the xref stream's. (void)read_xrefStream(cur_trailer.getKey("/XRefStm").getIntValue()); } else { - throw damagedPDF("xref stream", xref_offset, "invalid /XRefStm"); + throw qpdf.damagedPDF("xref stream", xref_offset, "invalid /XRefStm"); } } } if (cur_trailer.hasKey("/Prev")) { if (!cur_trailer.getKey("/Prev").isInteger()) { - QTC::TC("qpdf", "QPDF trailer prev not integer"); - throw damagedPDF("trailer", "/Prev key in trailer dictionary is not an integer"); + throw qpdf.damagedPDF("trailer", "/Prev key in trailer dictionary is not an integer"); } - QTC::TC("qpdf", "QPDF prev key in trailer dictionary"); return cur_trailer.getKey("/Prev").getIntValue(); } @@ -776,7 +765,7 @@ QPDF::read_xrefTable(qpdf_offset_t xref_offset) // Read a single cross-reference stream. qpdf_offset_t -QPDF::read_xrefStream(qpdf_offset_t xref_offset, bool in_stream_recovery) +Objects::read_xrefStream(qpdf_offset_t xref_offset, bool in_stream_recovery) { if (!m->ignore_xref_streams) { QPDFObjectHandle xref_obj; @@ -788,19 +777,17 @@ QPDF::read_xrefStream(qpdf_offset_t xref_offset, bool in_stream_recovery) } m->in_read_xref_stream = false; if (xref_obj.isStreamOfType("/XRef")) { - QTC::TC("qpdf", "QPDF found xref stream"); return processXRefStream(xref_offset, xref_obj, in_stream_recovery); } } - QTC::TC("qpdf", "QPDF can't find xref"); - throw damagedPDF("", xref_offset, "xref not found"); + throw qpdf.damagedPDF("", xref_offset, "xref not found"); return 0; // unreachable } // Return the entry size of the xref stream and the processed W array. std::pair> -QPDF::processXRefW(QPDFObjectHandle& dict, std::function damaged) +Objects::processXRefW(QPDFObjectHandle& dict, std::function damaged) { auto W_obj = dict.getKey("/W"); if (!(W_obj.size() >= 3 && W_obj.getArrayItem(0).isInteger() && @@ -830,7 +817,7 @@ QPDF::processXRefW(QPDFObjectHandle& dict, std::function damaged) { // Number of entries is limited by the highest possible object id and stream size. @@ -854,7 +841,7 @@ QPDF::processXRefSize( // Return the number of entries of the xref stream and the processed Index array. std::pair>> -QPDF::processXRefIndex( +Objects::processXRefIndex( QPDFObjectHandle& dict, int max_num_entries, std::function damaged) { auto size = dict.getKey("/Size").getIntValueAsInt(); @@ -921,11 +908,11 @@ QPDF::processXRefIndex( } qpdf_offset_t -QPDF::processXRefStream( +Objects::processXRefStream( qpdf_offset_t xref_offset, QPDFObjectHandle& xref_obj, bool in_stream_recovery) { auto damaged = [this, xref_offset](std::string_view msg) -> QPDFExc { - return damagedPDF("xref stream", xref_offset, msg.data()); + return qpdf.damagedPDF("xref stream", xref_offset, msg.data()); }; auto dict = xref_obj.getDict(); @@ -945,7 +932,7 @@ QPDF::processXRefStream( if (expected_size > actual_size) { throw x; } else { - warn(x); + qpdf.warn(x); } } @@ -960,7 +947,6 @@ QPDF::processXRefStream( // Read this entry std::array fields{}; if (W[0] == 0) { - QTC::TC("qpdf", "QPDF default for xref stream field 0"); fields[0] = 1; } for (size_t j = 0; j < 3; ++j) { @@ -1006,10 +992,9 @@ QPDF::processXRefStream( if (dict.hasKey("/Prev")) { if (!dict.getKey("/Prev").isInteger()) { - throw damagedPDF( + throw qpdf.damagedPDF( "xref stream", "/Prev key in xref stream dictionary is not an integer"); } - QTC::TC("qpdf", "QPDF prev key in xref stream dictionary"); return dict.getKey("/Prev").getIntValue(); } else { return 0; @@ -1017,7 +1002,7 @@ QPDF::processXRefStream( } void -QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2) +Objects::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2) { // Populate the xref table in such a way that the first reference to an object that we see, // 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) // We are ignoring invalid objgens. Most will arrive here from xref reconstruction. There // is probably no point having another warning but we could count invalid items in order to // decide when to give up. - QTC::TC("qpdf", "QPDF xref overwrite invalid objgen"); // ignore impossibly large object ids or object ids > Size. return; } if (m->deleted_objects.contains(obj)) { - QTC::TC("qpdf", "QPDF xref deleted object"); return; } if (f0 == 2) { if (f1 == obj) { - warn( - damagedPDF("xref stream", "self-referential object stream " + std::to_string(obj))); + qpdf.warn(qpdf.damagedPDF( + "xref stream", "self-referential object stream " + std::to_string(obj))); return; } if (f1 > m->xref_table_max_id) { // ignore impossibly large object stream ids - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( "xref stream", "object stream id " + std::to_string(f1) + " for object " + std::to_string(obj) + " is impossibly large")); @@ -1063,7 +1046,6 @@ QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2) auto [iter, created] = m->xref_table.try_emplace(QPDFObjGen(obj, (f0 == 2 ? 0 : f2))); if (!created) { - QTC::TC("qpdf", "QPDF xref reused object"); return; } @@ -1079,13 +1061,14 @@ QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2) break; default: - throw damagedPDF("xref stream", "unknown xref stream entry type " + std::to_string(f0)); + throw qpdf.damagedPDF( + "xref stream", "unknown xref stream entry type " + std::to_string(f0)); break; } } void -QPDF::insertFreeXrefEntry(QPDFObjGen og) +Objects::insertFreeXrefEntry(QPDFObjGen og) { if (!m->xref_table.contains(og)) { m->deleted_objects.insert(og.getObj()); @@ -1121,7 +1104,7 @@ QPDF::showXRefTable() // Resolve all objects in the xref table. If this triggers a xref table reconstruction abort and // return false. Otherwise return true. bool -QPDF::resolveXRefTable() +Objects::resolveXRefTable() { bool may_change = !m->reconstructed_xref; for (auto& iter: m->xref_table) { @@ -1143,9 +1126,8 @@ QPDF::fixDanglingReferences(bool force) if (m->fixed_dangling_refs) { return; } - if (!resolveXRefTable()) { - QTC::TC("qpdf", "QPDF fix dangling triggered xref reconstruction"); - resolveXRefTable(); + if (!m->objects.resolveXRefTable()) { + m->objects.resolveXRefTable(); } m->fixed_dangling_refs = true; } @@ -1171,13 +1153,13 @@ QPDF::getAllObjects() fixDanglingReferences(); std::vector result; for (auto const& iter: m->obj_cache) { - result.push_back(newIndirect(iter.first, iter.second.object)); + result.emplace_back(m->objects.newIndirect(iter.first, iter.second.object)); } return result; } void -QPDF::setLastObjectDescription(std::string const& description, QPDFObjGen og) +Objects::setLastObjectDescription(std::string const& description, QPDFObjGen og) { m->last_object_description.clear(); if (!description.empty()) { @@ -1192,17 +1174,17 @@ QPDF::setLastObjectDescription(std::string const& description, QPDFObjGen og) } QPDFObjectHandle -QPDF::readTrailer() +Objects::readTrailer() { qpdf_offset_t offset = m->file->tell(); auto [object, empty] = - QPDFParser::parse(*m->file, "trailer", m->tokenizer, nullptr, *this, m->reconstructed_xref); + QPDFParser::parse(*m->file, "trailer", m->tokenizer, nullptr, qpdf, m->reconstructed_xref); if (empty) { // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in // actual PDF files and Adobe Reader appears to ignore them. - warn(damagedPDF("trailer", "empty object treated as null")); - } else if (object.isDictionary() && readToken(*m->file).isWord("stream")) { - warn(damagedPDF("trailer", m->file->tell(), "stream keyword found in trailer")); + qpdf.warn(qpdf.damagedPDF("trailer", "empty object treated as null")); + } else if (object.isDictionary() && m->objects.readToken(*m->file).isWord("stream")) { + qpdf.warn(qpdf.damagedPDF("trailer", m->file->tell(), "stream keyword found in trailer")); } // Override last_offset so that it points to the beginning of the object we just read m->file->setLastOffset(offset); @@ -1210,25 +1192,26 @@ QPDF::readTrailer() } QPDFObjectHandle -QPDF::readObject(std::string const& description, QPDFObjGen og) +Objects::readObject(std::string const& description, QPDFObjGen og) { setLastObjectDescription(description, og); qpdf_offset_t offset = m->file->tell(); - StringDecrypter decrypter{this, og}; + StringDecrypter decrypter{&qpdf, og}; StringDecrypter* decrypter_ptr = m->encp->encrypted ? &decrypter : nullptr; auto [object, empty] = QPDFParser::parse( *m->file, m->last_object_description, m->tokenizer, decrypter_ptr, - *this, + qpdf, m->reconstructed_xref || m->in_read_xref_stream); ; if (empty) { // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in // actual PDF files and Adobe Reader appears to ignore them. - warn(damagedPDF(*m->file, m->file->getLastOffset(), "empty object treated as null")); + qpdf.warn( + qpdf.damagedPDF(*m->file, m->file->getLastOffset(), "empty object treated as null")); return object; } auto token = readToken(*m->file); @@ -1237,15 +1220,14 @@ QPDF::readObject(std::string const& description, QPDFObjGen og) token = readToken(*m->file); } if (!token.isWord("endobj")) { - QTC::TC("qpdf", "QPDF err expected endobj"); - warn(damagedPDF("expected endobj")); + qpdf.warn(qpdf.damagedPDF("expected endobj")); } return object; } // After reading stream dictionary and stream keyword, read rest of stream. void -QPDF::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset) +Objects::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset) { validateStreamLineEnd(object, og, offset); @@ -1259,9 +1241,9 @@ QPDF::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset) if (!length_obj.isInteger()) { if (length_obj.null()) { - throw damagedPDF(offset, "stream dictionary lacks /Length key"); + throw qpdf.damagedPDF(offset, "stream dictionary lacks /Length key"); } - throw damagedPDF(offset, "/Length key in stream dictionary is not an integer"); + throw qpdf.damagedPDF(offset, "/Length key in stream dictionary is not an integer"); } length = toS(length_obj.getUIntValue()); @@ -1269,21 +1251,21 @@ QPDF::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset) m->file->seek(stream_offset, SEEK_SET); m->file->seek(toO(length), SEEK_CUR); if (!readToken(*m->file).isWord("endstream")) { - throw damagedPDF("expected endstream"); + throw qpdf.damagedPDF("expected endstream"); } } catch (QPDFExc& e) { if (m->attempt_recovery) { - warn(e); + qpdf.warn(e); length = recoverStreamLength(m->file, og, stream_offset); } else { throw; } } - object = QPDFObjectHandle(qpdf::Stream(*this, og, object, stream_offset, length)); + object = QPDFObjectHandle(qpdf::Stream(qpdf, og, object, stream_offset, length)); } void -QPDF::validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset) +Objects::validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset) { // The PDF specification states that the word "stream" should be followed by either a carriage // return and a newline or by a newline alone. It specifically disallowed following it by a @@ -1301,7 +1283,6 @@ QPDF::validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset } if (ch == '\n') { // ready to read stream data - QTC::TC("qpdf", "QPDF stream with NL only"); return; } if (ch == '\r') { @@ -1313,33 +1294,32 @@ QPDF::validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset } else { // Treat the \r by itself as the whitespace after endstream and start reading // stream data in spite of not having seen a newline. - QTC::TC("qpdf", "QPDF stream with CR only"); m->file->unreadCh(ch); - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( m->file->tell(), "stream keyword followed by carriage return only")); } } return; } if (!util::is_space(ch)) { - QTC::TC("qpdf", "QPDF stream without newline"); m->file->unreadCh(ch); - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( m->file->tell(), "stream keyword not followed by proper line terminator")); return; } - warn(damagedPDF(m->file->tell(), "stream keyword followed by extraneous whitespace")); + qpdf.warn( + qpdf.damagedPDF(m->file->tell(), "stream keyword followed by extraneous whitespace")); } } QPDFObjectHandle -QPDF::readObjectInStream(is::OffsetBuffer& input, int stream_id, int obj_id) +Objects::readObjectInStream(is::OffsetBuffer& input, int stream_id, int obj_id) { - auto [object, empty] = QPDFParser::parse(input, stream_id, obj_id, m->tokenizer, *this); + auto [object, empty] = QPDFParser::parse(input, stream_id, obj_id, m->tokenizer, qpdf); if (empty) { // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in // actual PDF files and Adobe Reader appears to ignore them. - warn(QPDFExc( + qpdf.warn(QPDFExc( qpdf_e_damaged_pdf, m->file->getName() + " object stream " + std::to_string(stream_id), +"object " + std::to_string(obj_id) + " 0, offset " + @@ -1354,7 +1334,7 @@ bool QPDF::findEndstream() { // Find endstream or endobj. Position the input at that token. - auto t = readToken(*m->file, 20); + auto t = m->objects.readToken(*m->file, 20); if (t.isWord("endobj") || t.isWord("endstream")) { m->file->seek(m->file->getLastOffset(), SEEK_SET); return true; @@ -1363,13 +1343,13 @@ QPDF::findEndstream() } size_t -QPDF::recoverStreamLength( +Objects::recoverStreamLength( std::shared_ptr input, QPDFObjGen og, qpdf_offset_t stream_offset) { // Try to reconstruct stream length by looking for endstream or endobj - warn(damagedPDF(*input, stream_offset, "attempting to recover stream length")); + qpdf.warn(qpdf.damagedPDF(*input, stream_offset, "attempting to recover stream length")); - PatternFinder ef(*this, &QPDF::findEndstream); + PatternFinder ef(qpdf, &QPDF::findEndstream); size_t length = 0; if (m->file->findFirst("end", stream_offset, 0, ef)) { length = toS(m->file->tell() - stream_offset); @@ -1401,65 +1381,58 @@ QPDF::recoverStreamLength( // found endstream\nendobj within the space allowed for this object, so we're probably // in good shape. } else { - QTC::TC("qpdf", "QPDF found wrong endstream in recovery"); length = 0; } } if (length == 0) { - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( *input, stream_offset, "unable to recover stream data; treating stream as empty")); } else { - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( *input, stream_offset, "recovered stream length: " + std::to_string(length))); } - QTC::TC("qpdf", "QPDF recovered stream length"); return length; } QPDFTokenizer::Token -QPDF::readToken(InputSource& input, size_t max_len) +Objects::readToken(InputSource& input, size_t max_len) { return m->tokenizer.readToken(input, m->last_object_description, true, max_len); } QPDFObjGen -QPDF::read_object_start(qpdf_offset_t offset) +Objects::read_object_start(qpdf_offset_t offset) { m->file->seek(offset, SEEK_SET); QPDFTokenizer::Token tobjid = readToken(*m->file); bool objidok = tobjid.isInteger(); - QTC::TC("qpdf", "QPDF check objid", objidok ? 1 : 0); if (!objidok) { - QTC::TC("qpdf", "QPDF expected n n obj"); - throw damagedPDF(offset, "expected n n obj"); + throw qpdf.damagedPDF(offset, "expected n n obj"); } QPDFTokenizer::Token tgen = readToken(*m->file); bool genok = tgen.isInteger(); - QTC::TC("qpdf", "QPDF check generation", genok ? 1 : 0); if (!genok) { - throw damagedPDF(offset, "expected n n obj"); + throw qpdf.damagedPDF(offset, "expected n n obj"); } QPDFTokenizer::Token tobj = readToken(*m->file); bool objok = tobj.isWord("obj"); - QTC::TC("qpdf", "QPDF check obj", objok ? 1 : 0); if (!objok) { - throw damagedPDF(offset, "expected n n obj"); + throw qpdf.damagedPDF(offset, "expected n n obj"); } int objid = QUtil::string_to_int(tobjid.getValue().c_str()); int generation = QUtil::string_to_int(tgen.getValue().c_str()); if (objid == 0) { - QTC::TC("qpdf", "QPDF object id 0"); - throw damagedPDF(offset, "object with ID 0"); + throw qpdf.damagedPDF(offset, "object with ID 0"); } return {objid, generation}; } void -QPDF::readObjectAtOffset( +Objects::readObjectAtOffset( bool try_recovery, qpdf_offset_t offset, std::string const& description, QPDFObjGen exp_og) { QPDFObjGen og; @@ -1474,22 +1447,20 @@ QPDF::readObjectAtOffset( // "0000000000 00000 n", which is not correct, but it won't hurt anything for us to ignore // these. if (offset == 0) { - QTC::TC("qpdf", "QPDF bogus 0 offset", 0); - warn(damagedPDF(-1, "object has offset 0")); + qpdf.warn(qpdf.damagedPDF(-1, "object has offset 0")); return; } try { og = read_object_start(offset); if (exp_og != og) { - QTC::TC("qpdf", "QPDF err wrong objid/generation"); - QPDFExc e = damagedPDF(offset, "expected " + exp_og.unparse(' ') + " obj"); + QPDFExc e = qpdf.damagedPDF(offset, "expected " + exp_og.unparse(' ') + " obj"); if (try_recovery) { // Will be retried below throw e; } else { // We can try reading the object anyway even if the ID doesn't match. - warn(e); + qpdf.warn(e); } } } catch (QPDFExc& e) { @@ -1501,11 +1472,9 @@ QPDF::readObjectAtOffset( if (m->xref_table.contains(exp_og) && m->xref_table[exp_og].getType() == 1) { qpdf_offset_t new_offset = m->xref_table[exp_og].getOffset(); readObjectAtOffset(false, new_offset, description, exp_og); - QTC::TC("qpdf", "QPDF recovered in readObjectAtOffset"); return; } - QTC::TC("qpdf", "QPDF object gone after xref reconstruction"); - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( "", -1, ("object " + exp_og.unparse(' ') + @@ -1524,24 +1493,24 @@ QPDF::readObjectAtOffset( while (true) { char ch; if (!m->file->read(&ch, 1)) { - throw damagedPDF(m->file->tell(), "EOF after endobj"); + throw qpdf.damagedPDF(m->file->tell(), "EOF after endobj"); } if (!isspace(static_cast(ch))) { m->file->seek(-1, SEEK_CUR); break; } } - updateCache(og, oh.getObj(), end_before_space, m->file->tell()); + m->objects.updateCache(og, oh.getObj(), end_before_space, m->file->tell()); } QPDFObjectHandle -QPDF::readObjectAtOffset( +Objects::readObjectAtOffset( qpdf_offset_t offset, std::string const& description, bool skip_cache_if_in_xref) { auto og = read_object_start(offset); auto oh = readObject(description, og); - if (!isUnresolved(og)) { + if (!m->objects.isUnresolved(og)) { return oh; } @@ -1583,20 +1552,20 @@ QPDF::readObjectAtOffset( while (true) { char ch; if (!m->file->read(&ch, 1)) { - throw damagedPDF(m->file->tell(), "EOF after endobj"); + throw qpdf.damagedPDF(m->file->tell(), "EOF after endobj"); } if (!isspace(static_cast(ch))) { m->file->seek(-1, SEEK_CUR); break; } } - updateCache(og, oh.getObj(), end_before_space, m->file->tell()); + m->objects.updateCache(og, oh.getObj(), end_before_space, m->file->tell()); return oh; } std::shared_ptr const& -QPDF::resolve(QPDFObjGen og) +Objects::resolve(QPDFObjGen og) { if (!isUnresolved(og)) { return m->obj_cache[og].object; @@ -1605,12 +1574,11 @@ QPDF::resolve(QPDFObjGen og) if (m->resolving.contains(og)) { // This can happen if an object references itself directly or indirectly in some key that // has to be resolved during object parsing, such as stream length. - QTC::TC("qpdf", "QPDF recursion loop in resolve"); - warn(damagedPDF("", "loop detected resolving object " + og.unparse(' '))); + qpdf.warn(qpdf.damagedPDF("", "loop detected resolving object " + og.unparse(' '))); updateCache(og, QPDFObject::create(), -1, -1); return m->obj_cache[og].object; } - ResolveRecorder rr(*this, og); + ResolveRecorder rr(qpdf, og); if (m->xref_table.contains(og)) { QPDFXRefEntry const& entry = m->xref_table[og]; @@ -1626,30 +1594,29 @@ QPDF::resolve(QPDFObjGen og) break; default: - throw damagedPDF( + throw qpdf.damagedPDF( "", -1, ("object " + og.unparse('/') + " has unexpected xref entry type")); } } catch (QPDFExc& e) { - warn(e); + qpdf.warn(e); } catch (std::exception& e) { - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( "", -1, ("object " + og.unparse('/') + ": error reading object: " + e.what()))); } } if (isUnresolved(og)) { // PDF spec says unknown objects resolve to the null object. - QTC::TC("qpdf", "QPDF resolve failure to null"); updateCache(og, QPDFObject::create(), -1, -1); } auto& result(m->obj_cache[og].object); - result->setDefaultDescription(this, og); + result->setDefaultDescription(&qpdf, og); return result; } void -QPDF::resolveObjectsInStream(int obj_stream_number) +Objects::resolveObjectsInStream(int obj_stream_number) { auto damaged = [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) } m->resolved_object_streams.insert(obj_stream_number); // Force resolution of object stream - auto obj_stream = getObject(obj_stream_number, 0).as_stream(); + auto obj_stream = qpdf.getObject(obj_stream_number, 0).as_stream(); if (!obj_stream) { - throw damagedPDF( + throw qpdf.damagedPDF( "object " + std::to_string(obj_stream_number) + " 0", "supposed object stream " + std::to_string(obj_stream_number) + " is not a stream"); } @@ -1682,8 +1649,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number) QPDFObjectHandle dict = obj_stream.getDict(); if (!dict.isDictionaryOfType("/ObjStm")) { - QTC::TC("qpdf", "QPDF ERR object stream with wrong type"); - warn(damagedPDF( + qpdf.warn(qpdf.damagedPDF( "object " + std::to_string(obj_stream_number) + " 0", "supposed object stream " + std::to_string(obj_stream_number) + " has wrong type")); } @@ -1691,7 +1657,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number) unsigned int n{0}; int first{0}; if (!(dict.getKey("/N").getValueAsUInt(n) && dict.getKey("/First").getValueAsInt(first))) { - throw damagedPDF( + throw qpdf.damagedPDF( "object " + std::to_string(obj_stream_number) + " 0", "object stream " + std::to_string(obj_stream_number) + " has incorrect keys"); } @@ -1708,7 +1674,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number) auto b_start = stream_data.data(); if (first >= end_offset) { - throw damagedPDF( + throw qpdf.damagedPDF( "object " + std::to_string(obj_stream_number) + " 0", "object stream " + std::to_string(obj_stream_number) + " has invalid /First entry"); } @@ -1728,20 +1694,17 @@ QPDF::resolveObjectsInStream(int obj_stream_number) long long offset = QUtil::string_to_int(toffset.getValue().c_str()); if (num == obj_stream_number) { - QTC::TC("qpdf", "QPDF ignore self-referential object stream"); - warn(damaged(num, id_offset, "object stream claims to contain itself")); + qpdf.warn(damaged(num, id_offset, "object stream claims to contain itself")); continue; } if (num < 1) { - QTC::TC("qpdf", "QPDF object stream contains id < 1"); - warn(damaged(num, id_offset, "object id is invalid"s)); + qpdf.warn(damaged(num, id_offset, "object id is invalid"s)); continue; } if (offset <= last_offset) { - QTC::TC("qpdf", "QPDF object stream offsets not increasing"); - warn(damaged( + qpdf.warn(damaged( num, input.getLastOffset(), "offset " + std::to_string(offset) + @@ -1755,7 +1718,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number) } if (first + offset >= end_offset) { - warn(damaged( + qpdf.warn(damaged( num, input.getLastOffset(), "offset " + std::to_string(offset) + " is too large")); continue; } @@ -1796,21 +1759,21 @@ QPDF::resolveObjectsInStream(int obj_stream_number) } QPDFObjectHandle -QPDF::newIndirect(QPDFObjGen og, std::shared_ptr const& obj) +Objects::newIndirect(QPDFObjGen og, std::shared_ptr const& obj) { - obj->setDefaultDescription(this, og); + obj->setDefaultDescription(&qpdf, og); return {obj}; } void -QPDF::updateCache( +Objects::updateCache( QPDFObjGen og, std::shared_ptr const& object, qpdf_offset_t end_before_space, qpdf_offset_t end_after_space, bool destroy) { - object->setObjGen(this, og); + object->setObjGen(&qpdf, og); if (isCached(og)) { auto& cache = m->obj_cache[og]; object->move_to(cache.object, destroy); @@ -1822,21 +1785,21 @@ QPDF::updateCache( } bool -QPDF::isCached(QPDFObjGen og) +Objects::isCached(QPDFObjGen og) { return m->obj_cache.contains(og); } bool -QPDF::isUnresolved(QPDFObjGen og) +Objects::isUnresolved(QPDFObjGen og) { return !isCached(og) || m->obj_cache[og].object->isUnresolved(); } QPDFObjGen -QPDF::nextObjGen() +Objects::nextObjGen() { - int max_objid = toI(getObjectCount()); + int max_objid = toI(qpdf.getObjectCount()); if (max_objid == std::numeric_limits::max()) { throw std::range_error("max object id is too high to create new objects"); } @@ -1844,7 +1807,7 @@ QPDF::nextObjGen() } QPDFObjectHandle -QPDF::makeIndirectFromQPDFObject(std::shared_ptr const& obj) +Objects::makeIndirectFromQPDFObject(std::shared_ptr const& obj) { QPDFObjGen next{nextObjGen()}; m->obj_cache[next] = ObjCache(obj, -1, -1); @@ -1857,11 +1820,11 @@ QPDF::makeIndirectObject(QPDFObjectHandle oh) if (!oh) { throw std::logic_error("attempted to make an uninitialized QPDFObjectHandle indirect"); } - return makeIndirectFromQPDFObject(oh.getObj()); + return m->objects.makeIndirectFromQPDFObject(oh.getObj()); } std::shared_ptr -QPDF::getObjectForParser(int id, int gen, bool parse_pdf) +Objects::getObjectForParser(int id, int gen, bool parse_pdf) { // This method is called by the parser and therefore must not resolve any objects. auto og = QPDFObjGen(id, gen); @@ -1869,25 +1832,25 @@ QPDF::getObjectForParser(int id, int gen, bool parse_pdf) return iter->second.object; } if (m->xref_table.contains(og) || (!m->parsed && og.getObj() < m->xref_table_max_id)) { - return m->obj_cache.insert({og, QPDFObject::create(this, og)}) + return m->obj_cache.insert({og, QPDFObject::create(&qpdf, og)}) .first->second.object; } if (parse_pdf) { return QPDFObject::create(); } - return m->obj_cache.insert({og, QPDFObject::create(this, og)}).first->second.object; + return m->obj_cache.insert({og, QPDFObject::create(&qpdf, og)}).first->second.object; } std::shared_ptr -QPDF::getObjectForJSON(int id, int gen) +Objects::getObjectForJSON(int id, int gen) { auto og = QPDFObjGen(id, gen); auto [it, inserted] = m->obj_cache.try_emplace(og); auto& obj = it->second.object; if (inserted) { obj = (m->parsed && !m->xref_table.contains(og)) - ? QPDFObject::create(this, og) - : QPDFObject::create(this, og); + ? QPDFObject::create(&qpdf, og) + : QPDFObject::create(&qpdf, og); } return obj; } @@ -1916,10 +1879,9 @@ void QPDF::replaceObject(QPDFObjGen og, QPDFObjectHandle oh) { if (!oh || (oh.isIndirect() && !(oh.isStream() && oh.getObjGen() == og))) { - QTC::TC("qpdf", "QPDF replaceObject called with indirect object"); throw std::logic_error("QPDF::replaceObject called with indirect object handle"); } - updateCache(og, oh.getObj(), -1, -1, false); + m->objects.updateCache(og, oh.getObj(), -1, -1, false); } void @@ -1955,13 +1917,13 @@ void QPDF::swapObjects(QPDFObjGen og1, QPDFObjGen og2) { // Force objects to be read from the input source if needed, then swap them in the cache. - resolve(og1); - resolve(og2); + m->objects.resolve(og1); + m->objects.resolve(og2); m->obj_cache[og1].object->swapWith(m->obj_cache[og2].object); } size_t -QPDF::tableSize() +Objects::tableSize() { // If obj_cache is dense, accommodate all object in tables,else accommodate only original // objects. @@ -1972,7 +1934,7 @@ QPDF::tableSize() // Temporary fix. Long-term solution is // - QPDFObjGen to enforce objgens are valid and sensible // - xref table and obj cache to protect against insertion of impossibly large obj ids - stopOnError("Impossibly large object id encountered."); + qpdf.stopOnError("Impossibly large object id encountered."); } if (max_obj < 1.1 * std::max(toI(m->obj_cache.size()), max_xref)) { return toS(++max_obj); @@ -1981,20 +1943,20 @@ QPDF::tableSize() } std::vector -QPDF::getCompressibleObjVector() +Objects::getCompressibleObjVector() { return getCompressibleObjGens(); } std::vector -QPDF::getCompressibleObjSet() +Objects::getCompressibleObjSet() { return getCompressibleObjGens(); } template std::vector -QPDF::getCompressibleObjGens() +Objects::getCompressibleObjGens() { // Return a list of objects that are allowed to be in object streams. Walk through the objects // by traversing the document from the root, including a traversal of the pages tree. This @@ -2006,11 +1968,11 @@ QPDF::getCompressibleObjGens() QPDFObjectHandle encryption_dict = m->trailer.getKey("/Encrypt"); QPDFObjGen encryption_dict_og = encryption_dict.getObjGen(); - const size_t max_obj = getObjectCount(); + const size_t max_obj = qpdf.getObjectCount(); std::vector visited(max_obj, false); std::vector queue; queue.reserve(512); - queue.push_back(m->trailer); + queue.emplace_back(m->trailer); std::vector result; if constexpr (std::is_same_v) { result.reserve(m->obj_cache.size()); @@ -2030,7 +1992,6 @@ QPDF::getCompressibleObjGens() "unexpected object id encountered in getCompressibleObjGens"); } if (visited[id]) { - QTC::TC("qpdf", "QPDF loop detected traversing objects"); continue; } @@ -2039,7 +2000,7 @@ QPDF::getCompressibleObjGens() // in the queue. auto upper = m->obj_cache.upper_bound(og); if (upper != m->obj_cache.end() && upper->first.getObj() == og.getObj()) { - removeObject(og); + qpdf.removeObject(og); continue; } diff --git a/libqpdf/QPDF_optimization.cc b/libqpdf/QPDF_optimization.cc index e0cb73f..9cfb605 100644 --- a/libqpdf/QPDF_optimization.cc +++ b/libqpdf/QPDF_optimization.cc @@ -7,6 +7,9 @@ #include #include +using Lin = QPDF::Doc::Linearization; +using Pages = QPDF::Doc::Pages; + QPDF::ObjUser::ObjUser(user_e type) : ou_type(type) { @@ -58,11 +61,11 @@ QPDF::optimize( bool allow_changes, std::function skip_stream_parameters) { - optimize_internal(object_stream_data, allow_changes, skip_stream_parameters); + m->lin.optimize_internal(object_stream_data, allow_changes, skip_stream_parameters); } void -QPDF::optimize( +Lin::optimize( QPDFWriter::ObjTable const& obj, std::function skip_stream_parameters) { optimize_internal(obj, true, skip_stream_parameters); @@ -70,7 +73,7 @@ QPDF::optimize( template void -QPDF::optimize_internal( +Lin::optimize_internal( T const& object_stream_data, bool allow_changes, std::function skip_stream_parameters) @@ -82,18 +85,17 @@ QPDF::optimize_internal( // The PDF specification indicates that /Outlines is supposed to be an indirect reference. Force // it to be so if it exists and is direct. (This has been seen in the wild.) - QPDFObjectHandle root = getRoot(); + QPDFObjectHandle root = qpdf.getRoot(); if (root.getKey("/Outlines").isDictionary()) { QPDFObjectHandle outlines = root.getKey("/Outlines"); if (!outlines.isIndirect()) { - QTC::TC("qpdf", "QPDF_optimization indirect outlines"); - root.replaceKey("/Outlines", makeIndirectObject(outlines)); + root.replaceKey("/Outlines", qpdf.makeIndirectObject(outlines)); } } // Traverse pages tree pushing all inherited resources down to the page level. This also // initializes m->all_pages. - pushInheritedAttributesToPage(allow_changes, false); + m->pages.pushInheritedAttributesToPage(allow_changes, false); // Traverse pages size_t n = m->all_pages.size(); @@ -136,11 +138,11 @@ void QPDF::pushInheritedAttributesToPage() { // Public API should not have access to allow_changes. - pushInheritedAttributesToPage(true, false); + m->pages.pushInheritedAttributesToPage(true, false); } void -QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys) +Pages::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys) { // Traverse pages tree pushing all inherited resources down to the page level. @@ -152,7 +154,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys) // Calling getAllPages() resolves any duplicated page objects, repairs broken nodes, and detects // loops, so we don't have to do those activities here. - getAllPages(); + qpdf.getAllPages(); // key_ancestors is a mapping of page attribute keys to a stack of Pages nodes that contain // values for them. @@ -171,7 +173,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys) } void -QPDF::pushInheritedAttributesToPageInternal( +Pages ::pushInheritedAttributesToPageInternal( QPDFObjectHandle cur_pages, std::map>& key_ancestors, bool allow_changes, @@ -183,8 +185,7 @@ QPDF::pushInheritedAttributesToPageInternal( std::set inheritable_keys; for (auto const& key: cur_pages.getKeys()) { - if ((key == "/MediaBox") || (key == "/CropBox") || (key == "/Resources") || - (key == "/Rotate")) { + if (key == "/MediaBox" || key == "/CropBox" || key == "/Resources" || key == "/Rotate") { if (!allow_changes) { throw QPDFExc( qpdf_e_internal, @@ -197,34 +198,29 @@ QPDF::pushInheritedAttributesToPageInternal( // This is an inheritable resource inheritable_keys.insert(key); QPDFObjectHandle oh = cur_pages.getKey(key); - QTC::TC("qpdf", "QPDF opt direct pages resource", oh.isIndirect() ? 0 : 1); - if (!oh.isIndirect()) { + QTC::TC("qpdf", "QPDF opt direct pages resource", oh.indirect() ? 0 : 1); + if (!oh.indirect()) { if (!oh.isScalar()) { // Replace shared direct object non-scalar resources with indirect objects to // avoid copying large structures around. - cur_pages.replaceKey(key, makeIndirectObject(oh)); + cur_pages.replaceKey(key, qpdf.makeIndirectObject(oh)); oh = cur_pages.getKey(key); } else { // It's okay to copy scalars. - QTC::TC("qpdf", "QPDF opt inherited scalar"); } } key_ancestors[key].push_back(oh); if (key_ancestors[key].size() > 1) { - QTC::TC("qpdf", "QPDF opt key ancestors depth > 1"); } // Remove this resource from this node. It will be reattached at the page level. cur_pages.removeKey(key); - } else if (!((key == "/Type") || (key == "/Parent") || (key == "/Kids") || - (key == "/Count"))) { + } else if (!(key == "/Type" || key == "/Parent" || key == "/Kids" || key == "/Count")) { // Warn when flattening, but not if the key is at the top level (i.e. "/Parent" not // set), as we don't change these; but flattening removes intermediate /Pages nodes. - if ((warn_skipped_keys) && (cur_pages.hasKey("/Parent"))) { - QTC::TC("qpdf", "QPDF unknown key not inherited"); - setLastObjectDescription("Pages object", cur_pages.getObjGen()); - warn( + if (warn_skipped_keys && cur_pages.hasKey("/Parent")) { + qpdf.warn( qpdf_e_pages, - m->last_object_description, + "Pages object: object " + cur_pages.id_gen().unparse(' '), 0, ("Unknown key " + key + " in /Pages object is being discarded as a result of flattening the /Pages " @@ -245,7 +241,6 @@ QPDF::pushInheritedAttributesToPageInternal( for (auto const& iter: key_ancestors) { std::string const& key = iter.first; if (!kid.hasKey(key)) { - QTC::TC("qpdf", "QPDF opt resource inherited"); kid.replaceKey(key, iter.second.back()); } else { QTC::TC("qpdf", "QPDF opt page resource hides ancestor"); @@ -259,11 +254,9 @@ QPDF::pushInheritedAttributesToPageInternal( // which inheritable attributes are available. if (!inheritable_keys.empty()) { - QTC::TC("qpdf", "QPDF opt inheritable keys"); for (auto const& key: inheritable_keys) { key_ancestors[key].pop_back(); if (key_ancestors[key].empty()) { - QTC::TC("qpdf", "QPDF opt erase empty key ancestor"); key_ancestors.erase(key); } } @@ -273,7 +266,7 @@ QPDF::pushInheritedAttributesToPageInternal( } void -QPDF::updateObjectMaps( +Lin::updateObjectMaps( ObjUser const& first_ou, QPDFObjectHandle first_oh, std::function skip_stream_parameters) @@ -346,7 +339,7 @@ QPDF::updateObjectMaps( } void -QPDF::filterCompressedObjects(std::map const& object_stream_data) +Lin::filterCompressedObjects(std::map const& object_stream_data) { if (object_stream_data.empty()) { return; @@ -390,7 +383,7 @@ QPDF::filterCompressedObjects(std::map const& object_stream_data) } void -QPDF::filterCompressedObjects(QPDFWriter::ObjTable const& obj) +Lin::filterCompressedObjects(QPDFWriter::ObjTable const& obj) { if (obj.getStreamsEmpty()) { return; diff --git a/libqpdf/QPDF_pages.cc b/libqpdf/QPDF_pages.cc index 16e28ae..0239314 100644 --- a/libqpdf/QPDF_pages.cc +++ b/libqpdf/QPDF_pages.cc @@ -37,6 +37,8 @@ // insertPage, and removePage, along with methods they call, are concerned with it. Everything else // goes through one of those methods. +using Pages = QPDF::Doc::Pages; + std::vector const& QPDF::getAllPages() { @@ -75,14 +77,14 @@ QPDF::getAllPages() qpdf_e_pages, m->file->getName(), "", 0, "root of pages tree has no /Kids array"); } try { - getAllPagesInternal(pages, visited, seen, false, false); + m->pages.getAllPagesInternal(pages, visited, seen, false, false); } catch (...) { m->all_pages.clear(); m->invalid_page_found = false; throw; } if (m->invalid_page_found) { - flattenPagesTree(); + m->pages.flattenPagesTree(); m->invalid_page_found = false; } } @@ -90,7 +92,7 @@ QPDF::getAllPages() } void -QPDF::getAllPagesInternal( +Pages::getAllPagesInternal( QPDFObjectHandle cur_node, QPDFObjGen::set& visited, QPDFObjGen::set& seen, @@ -139,17 +141,15 @@ QPDF::getAllPagesInternal( continue; } if (!kid.isIndirect()) { - QTC::TC("qpdf", "QPDF handle direct page object"); cur_node.warn( "kid " + std::to_string(i) + " (from 0) is direct; converting to indirect"); - kid = makeIndirectObject(kid); + kid = qpdf.makeIndirectObject(kid); ++errors; } if (kid.hasKey("/Kids")) { getAllPagesInternal(kid, visited, seen, media_box, resources); } else { if (!media_box && !kid.getKey("/MediaBox").isRectangle()) { - QTC::TC("qpdf", "QPDF missing mediabox"); kid.warn( "kid " + std::to_string(i) + " (from 0) MediaBox is undefined; setting to letter / ANSI A"); @@ -193,7 +193,6 @@ QPDF::getAllPagesInternal( if (!seen.add(kid)) { // Make a copy of the page. This does the same as shallowCopyPage in // QPDFPageObjectHelper. - QTC::TC("qpdf", "QPDF resolve duplicated page object"); if (!m->reconstructed_xref) { cur_node.warn( "kid " + std::to_string(i) + @@ -201,7 +200,7 @@ QPDF::getAllPagesInternal( " creating a new page object as a copy"); // This needs to be fixed. shallowCopy does not necessarily produce a valid // page. - kid = makeIndirectObject(QPDFObjectHandle(kid).shallowCopy()); + kid = qpdf.makeIndirectObject(QPDFObjectHandle(kid).shallowCopy()); seen.add(kid); } else { cur_node.warn( @@ -239,7 +238,6 @@ QPDF::updateAllPagesCache() // Force regeneration of the pages cache. We force immediate recalculation of all_pages since // users may have references to it that they got from calls to getAllPages(). We can defer // recalculation of pageobj_to_pages_pos until needed. - QTC::TC("qpdf", "QPDF updateAllPagesCache"); m->all_pages.clear(); m->pageobj_to_pages_pos.clear(); m->pushed_inherited_attributes_to_pages = false; @@ -247,7 +245,7 @@ QPDF::updateAllPagesCache() } void -QPDF::flattenPagesTree() +Pages::flattenPagesTree() { // If not already done, flatten the /Pages structure and initialize pageobj_to_pages_pos. @@ -259,7 +257,7 @@ QPDF::flattenPagesTree() // generated. pushInheritedAttributesToPage(true, true); - QPDFObjectHandle pages = getRoot().getKey("/Pages"); + QPDFObjectHandle pages = qpdf.getRoot().getKey("/Pages"); size_t const len = m->all_pages.size(); for (size_t pos = 0; pos < len; ++pos) { @@ -282,17 +280,16 @@ QPDF::flattenPagesTree() } void -QPDF::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate) +Pages::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate) { QPDFObjGen og(obj.getObjGen()); if (check_duplicate) { if (!m->pageobj_to_pages_pos.insert(std::make_pair(og, pos)).second) { // The library never calls insertPageobjToPage in a way that causes this to happen. - setLastObjectDescription("page " + std::to_string(pos) + " (numbered from zero)", og); throw QPDFExc( qpdf_e_pages, m->file->getName(), - m->last_object_description, + "page " + std::to_string(pos) + " (numbered from zero): object " + og.unparse(' '), 0, "duplicate page reference found; this would cause loss of data"); } @@ -302,24 +299,22 @@ QPDF::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_dupli } void -QPDF::insertPage(QPDFObjectHandle newpage, int pos) +Pages::insertPage(QPDFObjectHandle newpage, int pos) { // pos is numbered from 0, so pos = 0 inserts at the beginning and pos = npages adds to the end. flattenPagesTree(); if (!newpage.isIndirect()) { - QTC::TC("qpdf", "QPDF insert non-indirect page"); - newpage = makeIndirectObject(newpage); - } else if (newpage.getOwningQPDF() != this) { - QTC::TC("qpdf", "QPDF insert foreign page"); + newpage = qpdf.makeIndirectObject(newpage); + } else if (newpage.getOwningQPDF() != &qpdf) { newpage.getQPDF().pushInheritedAttributesToPage(); - newpage = copyForeignObject(newpage); + newpage = qpdf.copyForeignObject(newpage); } else { QTC::TC("qpdf", "QPDF insert indirect page"); } - if ((pos < 0) || (toS(pos) > m->all_pages.size())) { + if (pos < 0 || toS(pos) > m->all_pages.size()) { throw std::runtime_error("QPDF::insertPage called with pos out of range"); } @@ -332,11 +327,10 @@ QPDF::insertPage(QPDFObjectHandle newpage, int pos) auto og = newpage.getObjGen(); if (m->pageobj_to_pages_pos.contains(og)) { - QTC::TC("qpdf", "QPDF resolve duplicated page in insert"); - newpage = makeIndirectObject(QPDFObjectHandle(newpage).shallowCopy()); + newpage = qpdf.makeIndirectObject(QPDFObjectHandle(newpage).shallowCopy()); } - QPDFObjectHandle pages = getRoot().getKey("/Pages"); + QPDFObjectHandle pages = qpdf.getRoot().getKey("/Pages"); QPDFObjectHandle kids = pages.getKey("/Kids"); newpage.replaceKey("/Parent", pages); @@ -370,7 +364,7 @@ QPDF::removePage(QPDFObjectHandle page) m->all_pages.erase(m->all_pages.begin() + pos); m->pageobj_to_pages_pos.erase(page.getObjGen()); for (int i = pos; i < npages; ++i) { - insertPageobjToPage(m->all_pages.at(toS(i)), i, false); + m->pages.insertPageobjToPage(m->all_pages.at(toS(i)), i, false); } } @@ -381,16 +375,17 @@ QPDF::addPageAt(QPDFObjectHandle newpage, bool before, QPDFObjectHandle refpage) if (!before) { ++refpos; } - insertPage(newpage, refpos); + m->pages.insertPage(newpage, refpos); } void QPDF::addPage(QPDFObjectHandle newpage, bool first) { if (first) { - insertPage(newpage, 0); + m->pages.insertPage(newpage, 0); } else { - insertPage(newpage, getRoot().getKey("/Pages").getKey("/Count").getIntValueAsInt()); + m->pages.insertPage( + newpage, getRoot().getKey("/Pages").getKey("/Count").getIntValueAsInt()); } } @@ -403,15 +398,13 @@ QPDF::findPage(QPDFObjectHandle& page) int QPDF::findPage(QPDFObjGen og) { - flattenPagesTree(); + m->pages.flattenPagesTree(); auto it = m->pageobj_to_pages_pos.find(og); if (it == m->pageobj_to_pages_pos.end()) { - QTC::TC("qpdf", "QPDF_pages findPage not found"); - setLastObjectDescription("page object", og); throw QPDFExc( qpdf_e_pages, m->file->getName(), - m->last_object_description, + "page object: object " + og.unparse(' '), 0, "page object not referenced in /Pages tree"); } diff --git a/libqpdf/qpdf/QPDFObjectHandle_private.hh b/libqpdf/qpdf/QPDFObjectHandle_private.hh index 41a1eef..b16232f 100644 --- a/libqpdf/qpdf/QPDFObjectHandle_private.hh +++ b/libqpdf/qpdf/QPDFObjectHandle_private.hh @@ -577,7 +577,7 @@ namespace qpdf return &std::get(obj->value); } if (std::holds_alternative(obj->value)) { - return BaseHandle(QPDF::Resolver::resolved(obj->qpdf, obj->og)).as(); + return BaseHandle(QPDF::Doc::Resolver::resolved(obj->qpdf, obj->og)).as(); } if (std::holds_alternative(obj->value)) { // see comment in QPDF_Reference. @@ -676,7 +676,7 @@ namespace qpdf return ::ot_uninitialized; } if (raw_type_code() == ::ot_unresolved) { - return QPDF::Resolver::resolved(obj->qpdf, obj->og)->getTypeCode(); + return QPDF::Doc::Resolver::resolved(obj->qpdf, obj->og)->getTypeCode(); } return raw_type_code(); } @@ -688,7 +688,7 @@ namespace qpdf return ::ot_uninitialized; } if (raw_type_code() == ::ot_unresolved) { - return QPDF::Resolver::resolved(obj->qpdf, obj->og)->getTypeCode(); + return QPDF::Doc::Resolver::resolved(obj->qpdf, obj->og)->getTypeCode(); } if (raw_type_code() == ::ot_reference) { return std::get(obj->value).obj->getTypeCode(); @@ -728,7 +728,7 @@ inline qpdf_object_type_e QPDFObject::getResolvedTypeCode() const { if (getTypeCode() == ::ot_unresolved) { - return QPDF::Resolver::resolved(qpdf, og)->getTypeCode(); + return QPDF::Doc::Resolver::resolved(qpdf, og)->getTypeCode(); } if (getTypeCode() == ::ot_reference) { return std::get(value).obj->getTypeCode(); diff --git a/libqpdf/qpdf/QPDF_private.hh b/libqpdf/qpdf/QPDF_private.hh index 55c340a..6646ff1 100644 --- a/libqpdf/qpdf/QPDF_private.hh +++ b/libqpdf/qpdf/QPDF_private.hh @@ -13,86 +13,13 @@ using namespace qpdf; -// The Resolver class is restricted to QPDFObject so that only it can resolve indirect -// references. -class QPDF::Resolver +namespace qpdf::is { - friend class QPDFObject; - friend class qpdf::BaseHandle; - - private: - static std::shared_ptr const& - resolved(QPDF* qpdf, QPDFObjGen og) - { - return qpdf->resolve(og); - } -}; - -// StreamCopier class is restricted to QPDFObjectHandle so it can copy stream data. -class QPDF::StreamCopier -{ - friend class QPDFObjectHandle; - - private: - static void - copyStreamData(QPDF* qpdf, QPDFObjectHandle const& dest, QPDFObjectHandle const& src) - { - qpdf->copyStreamData(dest, src); - } -}; + class OffsetBuffer; +} // namespace qpdf::is -// The ParseGuard class allows QPDFParser to detect re-entrant parsing. It also provides -// special access to allow the parser to create unresolved objects and dangling references. -class QPDF::ParseGuard -{ - friend class QPDFParser; - - private: - ParseGuard(QPDF* qpdf) : - qpdf(qpdf) - { - if (qpdf) { - qpdf->inParse(true); - } - } - - static std::shared_ptr - getObject(QPDF* qpdf, int id, int gen, bool parse_pdf) - { - return qpdf->getObjectForParser(id, gen, parse_pdf); - } - - ~ParseGuard() - { - if (qpdf) { - qpdf->inParse(false); - } - } - QPDF* qpdf; -}; - -// Pipe class is restricted to QPDF_Stream. -class QPDF::Pipe -{ - friend class qpdf::Stream; - - private: - static bool - pipeStreamData( - QPDF* qpdf, - QPDFObjGen og, - qpdf_offset_t offset, - size_t length, - QPDFObjectHandle dict, - bool is_root_metadata, - Pipeline* pipeline, - bool suppress_warnings, - bool will_retry) - { - return qpdf->pipeStreamData( - og, offset, length, dict, is_root_metadata, pipeline, suppress_warnings, will_retry); - } -}; +class BitStream; +class BitWriter; class QPDF::ObjCache { @@ -405,17 +332,498 @@ class QPDF::PatternFinder final: public InputSource::Finder bool (QPDF::*checker)(); }; +// This class is used to represent a PDF document. +// +// The main function of the QPDF class is to represent a PDF document. Doc is the implementation +// class for this aspect of QPDF. +class QPDF::Doc +{ + public: + class JobSetter; + class ParseGuard; + class Resolver; + class StreamCopier; + class Streams; + class Writer; + + class Encryption + { + public: + // This class holds data read from the encryption dictionary. + Encryption( + int V, + int R, + int Length_bytes, + int P, + std::string const& O, + std::string const& U, + std::string const& OE, + std::string const& UE, + std::string const& Perms, + std::string const& id1, + bool encrypt_metadata) : + V(V), + R(R), + Length_bytes(Length_bytes), + P(static_cast(P)), + O(O), + U(U), + OE(OE), + UE(UE), + Perms(Perms), + id1(id1), + encrypt_metadata(encrypt_metadata) + { + } + Encryption(int V, int R, int Length_bytes, bool encrypt_metadata) : + V(V), + R(R), + Length_bytes(Length_bytes), + encrypt_metadata(encrypt_metadata) + { + } + + int getV() const; + int getR() const; + int getLengthBytes() const; + int getP() const; + // Bits in P are numbered from 1 as in the PDF spec. + bool getP(size_t bit) const; + std::string const& getO() const; + std::string const& getU() const; + std::string const& getOE() const; + std::string const& getUE() const; + std::string const& getPerms() const; + std::string const& getId1() const; + bool getEncryptMetadata() const; + // Bits in P are numbered from 1 as in the PDF spec. + void setP(size_t bit, bool val); + void setP(unsigned long val); + void setO(std::string const&); + void setU(std::string const&); + void setId1(std::string const& val); + void setV5EncryptionParameters( + std::string const& O, + std::string const& OE, + std::string const& U, + std::string const& UE, + std::string const& Perms); + + std::string compute_encryption_key(std::string const& password) const; + + bool + check_owner_password(std::string& user_password, std::string const& owner_password) const; + + bool check_user_password(std::string const& user_password) const; + + std::string + recover_encryption_key_with_password(std::string const& password, bool& perms_valid) const; + + void compute_encryption_O_U(char const* user_password, char const* owner_password); + + std::string + compute_encryption_parameters_V5(char const* user_password, char const* owner_password); + + std::string compute_parameters(char const* user_password, char const* owner_password); + + private: + static constexpr unsigned int OU_key_bytes_V4 = 16; // ( == sizeof(MD5::Digest) + + Encryption(Encryption const&) = delete; + Encryption& operator=(Encryption const&) = delete; + + std::string hash_V5( + std::string const& password, std::string const& salt, std::string const& udata) const; + std::string + compute_O_value(std::string const& user_password, std::string const& owner_password) const; + std::string compute_U_value(std::string const& user_password) const; + std::string compute_encryption_key_from_password(std::string const& password) const; + std::string recover_encryption_key_with_password(std::string const& password) const; + bool check_owner_password_V4( + std::string& user_password, std::string const& owner_password) const; + bool check_owner_password_V5(std::string const& owner_passworda) const; + std::string compute_Perms_value_V5_clear() const; + std::string compute_O_rc4_key( + std::string const& user_password, std::string const& owner_password) const; + std::string compute_U_value_R2(std::string const& user_password) const; + std::string compute_U_value_R3(std::string const& user_password) const; + bool check_user_password_V4(std::string const& user_password) const; + bool check_user_password_V5(std::string const& user_password) const; + + int V; + int R; + int Length_bytes; + std::bitset<32> P{0xfffffffc}; // Specification always requires bits 1 and 2 to be cleared. + std::string O; + std::string U; + std::string OE; + std::string UE; + std::string Perms; + std::string id1; + bool encrypt_metadata; + }; // class QPDF::Doc::Encryption + + class Linearization + { + public: + Linearization() = delete; + Linearization(Linearization const&) = delete; + Linearization(Linearization&&) = delete; + Linearization& operator=(Linearization const&) = delete; + Linearization& operator=(Linearization&&) = delete; + ~Linearization() = default; + + Linearization(QPDF& qpdf, QPDF::Members* m) : + qpdf(qpdf), + m(m) + { + } + + // For QPDFWriter: + + template + void optimize_internal( + T const& object_stream_data, + bool allow_changes = true, + std::function skip_stream_parameters = nullptr); + void optimize( + QPDFWriter::ObjTable const& obj, + std::function skip_stream_parameters); + + // Get lists of all objects in order according to the part of a linearized file that they + // belong to. + void getLinearizedParts( + QPDFWriter::ObjTable const& obj, + std::vector& part4, + std::vector& part6, + std::vector& part7, + std::vector& part8, + std::vector& part9); + + void generateHintStream( + QPDFWriter::NewObjTable const& new_obj, + QPDFWriter::ObjTable const& obj, + std::string& hint_stream, + int& S, + int& O, + bool compressed); + + // methods to support linearization checking -- implemented in QPDF_linearization.cc + + void readLinearizationData(); + void checkLinearizationInternal(); + void dumpLinearizationDataInternal(); + void linearizationWarning(std::string_view); + qpdf::Dictionary readHintStream(Pipeline&, qpdf_offset_t offset, size_t length); + void readHPageOffset(BitStream); + void readHSharedObject(BitStream); + void readHGeneric(BitStream, HGeneric&); + qpdf_offset_t maxEnd(ObjUser const& ou); + qpdf_offset_t getLinearizationOffset(QPDFObjGen); + QPDFObjectHandle + getUncompressedObject(QPDFObjectHandle&, std::map const& object_stream_data); + QPDFObjectHandle getUncompressedObject(QPDFObjectHandle&, QPDFWriter::ObjTable const& obj); + int lengthNextN(int first_object, int n); + void checkHPageOffset( + std::vector const& pages, std::map& idx_to_obj); + void checkHSharedObject( + std::vector const& pages, std::map& idx_to_obj); + void checkHOutlines(); + void dumpHPageOffset(); + void dumpHSharedObject(); + void dumpHGeneric(HGeneric&); + qpdf_offset_t adjusted_offset(qpdf_offset_t offset); + template + void calculateLinearizationData(T const& object_stream_data); + template + void pushOutlinesToPart( + std::vector& part, + std::set& lc_outlines, + T const& object_stream_data); + int outputLengthNextN( + int in_object, + int n, + QPDFWriter::NewObjTable const& new_obj, + QPDFWriter::ObjTable const& obj); + void calculateHPageOffset( + QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj); + void calculateHSharedObject( + QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj); + void + calculateHOutline(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj); + void writeHPageOffset(BitWriter&); + void writeHSharedObject(BitWriter&); + void writeHGeneric(BitWriter&, HGeneric&); + + // Methods to support optimization + + void updateObjectMaps( + ObjUser const& ou, + QPDFObjectHandle oh, + std::function skip_stream_parameters); + void filterCompressedObjects(std::map const& object_stream_data); + void filterCompressedObjects(QPDFWriter::ObjTable const& object_stream_data); + + private: + QPDF& qpdf; + QPDF::Members* m; + }; + + class Objects + { + public: + Objects() = delete; + Objects(Objects const&) = delete; + Objects(Objects&&) = delete; + Objects& operator=(Objects const&) = delete; + Objects& operator=(Objects&&) = delete; + ~Objects() = default; + + Objects(QPDF& qpdf, QPDF::Members* m) : + qpdf(qpdf), + m(m) + { + } + + void parse(char const* password); + std::shared_ptr const& resolve(QPDFObjGen og); + void inParse(bool); + QPDFObjGen nextObjGen(); + QPDFObjectHandle newIndirect(QPDFObjGen, std::shared_ptr const&); + void updateCache( + QPDFObjGen og, + std::shared_ptr const& object, + qpdf_offset_t end_before_space, + qpdf_offset_t end_after_space, + bool destroy = true); + bool resolveXRefTable(); + QPDFObjectHandle readObjectAtOffset( + qpdf_offset_t offset, std::string const& description, bool skip_cache_if_in_xref); + QPDFTokenizer::Token readToken(InputSource& input, size_t max_len = 0); + QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr const& obj); + std::shared_ptr getObjectForParser(int id, int gen, bool parse_pdf); + std::shared_ptr getObjectForJSON(int id, int gen); + size_t tableSize(); + + // For QPDFWriter: + + // Get a list of objects that would be permitted in an object stream. + template + std::vector getCompressibleObjGens(); + std::vector getCompressibleObjVector(); + std::vector getCompressibleObjSet(); + + private: + void setTrailer(QPDFObjectHandle obj); + void reconstruct_xref(QPDFExc& e, bool found_startxref = true); + void read_xref(qpdf_offset_t offset, bool in_stream_recovery = false); + bool parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes); + bool read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type); + bool read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type); + qpdf_offset_t read_xrefTable(qpdf_offset_t offset); + qpdf_offset_t read_xrefStream(qpdf_offset_t offset, bool in_stream_recovery = false); + qpdf_offset_t processXRefStream( + qpdf_offset_t offset, QPDFObjectHandle& xref_stream, bool in_stream_recovery = false); + std::pair> + processXRefW(QPDFObjectHandle& dict, std::function damaged); + int processXRefSize( + QPDFObjectHandle& dict, + int entry_size, + std::function damaged); + std::pair>> processXRefIndex( + QPDFObjectHandle& dict, + int max_num_entries, + std::function damaged); + void insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2); + void insertFreeXrefEntry(QPDFObjGen); + QPDFObjectHandle readTrailer(); + QPDFObjectHandle readObject(std::string const& description, QPDFObjGen og); + void readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset); + void validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset); + QPDFObjectHandle + readObjectInStream(qpdf::is::OffsetBuffer& input, int stream_id, int obj_id); + size_t recoverStreamLength( + std::shared_ptr input, QPDFObjGen og, qpdf_offset_t stream_offset); + + QPDFObjGen read_object_start(qpdf_offset_t offset); + void readObjectAtOffset( + bool attempt_recovery, + qpdf_offset_t offset, + std::string const& description, + QPDFObjGen exp_og); + void resolveObjectsInStream(int obj_stream_number); + bool isCached(QPDFObjGen og); + bool isUnresolved(QPDFObjGen og); + void setLastObjectDescription(std::string const& description, QPDFObjGen og); + + private: + QPDF& qpdf; + QPDF::Members* m; + }; // class QPDF::Doc::Objects + + // This class is used to represent a PDF Pages tree. + class Pages + { + public: + Pages() = delete; + Pages(Pages const&) = delete; + Pages(Pages&&) = delete; + Pages& operator=(Pages const&) = delete; + Pages& operator=(Pages&&) = delete; + ~Pages() = default; + + Pages(QPDF& qpdf, QPDF::Members* m) : + qpdf(qpdf), + m(m) + { + } + + void getAllPagesInternal( + QPDFObjectHandle cur_pages, + QPDFObjGen::set& visited, + QPDFObjGen::set& seen, + bool media_box, + bool resources); + void insertPage(QPDFObjectHandle newpage, int pos); + void flattenPagesTree(); + void insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate); + void pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys); + void pushInheritedAttributesToPageInternal( + QPDFObjectHandle, + std::map>&, + bool allow_changes, + bool warn_skipped_keys); + + private: + QPDF& qpdf; + QPDF::Members* m; + }; // class QPDF::Doc::Pages + + // StreamCopier class is restricted to QPDFObjectHandle so it can copy stream data. + class StreamCopier + { + friend class QPDFObjectHandle; + + private: + static void + copyStreamData(QPDF* qpdf, QPDFObjectHandle const& dest, QPDFObjectHandle const& src) + { + qpdf->copyStreamData(dest, src); + } + }; + + Doc() = delete; + Doc(Doc const&) = delete; + Doc(Doc&&) = delete; + Doc& operator=(Doc const&) = delete; + Doc& operator=(Doc&&) = delete; + ~Doc() = default; + + Doc(QPDF& qpdf, QPDF::Members& m) : + qpdf(qpdf), + m(m), + lin_(qpdf, &m), + objects_(qpdf, &m), + pages_(qpdf, &m) + { + } + + Linearization& + linearization() + { + return lin_; + }; + + Objects& + objects() + { + return objects_; + }; + + Pages& + pages() + { + return pages_; + } + + bool reconstructed_xref() const; + + QPDFAcroFormDocumentHelper& + acroform() + { + if (!acroform_) { + acroform_ = std::make_unique(qpdf); + } + return *acroform_; + } + + QPDFEmbeddedFileDocumentHelper& + embedded_files() + { + if (!embedded_files_) { + embedded_files_ = std::make_unique(qpdf); + } + return *embedded_files_; + } + + QPDFOutlineDocumentHelper& + outlines() + { + if (!outlines_) { + outlines_ = std::make_unique(qpdf); + } + return *outlines_; + } + + QPDFPageDocumentHelper& + page_dh() + { + if (!page_dh_) { + page_dh_ = std::make_unique(qpdf); + } + return *page_dh_; + } + + QPDFPageLabelDocumentHelper& + page_labels() + { + if (!page_labels_) { + page_labels_ = std::make_unique(qpdf); + } + return *page_labels_; + } + + private: + QPDF& qpdf; + QPDF::Members& m; + + Linearization lin_; + Objects objects_; + Pages pages_; + + // Document Helpers; + std::unique_ptr acroform_; + std::unique_ptr embedded_files_; + std::unique_ptr outlines_; + std::unique_ptr page_dh_; + std::unique_ptr page_labels_; +}; + class QPDF::Members { friend class QPDF; friend class ResolveRecorder; public: - Members(); + Members(QPDF& qpdf); Members(Members const&) = delete; ~Members() = default; private: + Doc doc; + Doc::Linearization& lin; + Doc::Objects& objects; + Doc::Pages& pages; std::shared_ptr log; unsigned long long unique_id{0}; qpdf::Tokenizer tokenizer; @@ -488,78 +896,33 @@ class QPDF::Members // Optimization data std::map> obj_user_to_objects; std::map> object_to_obj_users; - - // Document Helpers; - std::unique_ptr acroform; - std::unique_ptr embedded_files; - std::unique_ptr outlines; - std::unique_ptr pages; - std::unique_ptr page_labels; }; -// JobSetter class is restricted to QPDFJob. -class QPDF::JobSetter +// The Resolver class is restricted to QPDFObject and BaseHandle so that only it can resolve +// indirect references. +class QPDF::Doc::Resolver { - friend class QPDFJob; + friend class QPDFObject; + friend class qpdf::BaseHandle; private: - // Enable enhanced warnings for pdf file checking. - static void - setCheckMode(QPDF& qpdf, bool val) + static std::shared_ptr const& + resolved(QPDF* qpdf, QPDFObjGen og) { - qpdf.m->check_mode = val; + return qpdf->m->objects.resolve(og); } }; inline bool -QPDF::reconstructed_xref() const -{ - return m->reconstructed_xref; -} - -inline QPDFAcroFormDocumentHelper& -QPDF::acroform() -{ - if (!m->acroform) { - m->acroform = std::make_unique(*this); - } - return *m->acroform; -} - -inline QPDFEmbeddedFileDocumentHelper& -QPDF::embedded_files() +QPDF::Doc::reconstructed_xref() const { - if (!m->embedded_files) { - m->embedded_files = std::make_unique(*this); - } - return *m->embedded_files; + return m.reconstructed_xref; } -inline QPDFOutlineDocumentHelper& -QPDF::outlines() +inline QPDF::Doc& +QPDF::doc() { - if (!m->outlines) { - m->outlines = std::make_unique(*this); - } - return *m->outlines; -} - -inline QPDFPageDocumentHelper& -QPDF::pages() -{ - if (!m->pages) { - m->pages = std::make_unique(*this); - } - return *m->pages; -} - -inline QPDFPageLabelDocumentHelper& -QPDF::page_labels() -{ - if (!m->page_labels) { - m->page_labels = std::make_unique(*this); - } - return *m->page_labels; + return m->doc; } // Throw a generic exception for unusual error conditions that do not be covered during CI testing. diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 8248b4c..a03c408 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -1,22 +1,9 @@ ignored-scope: libtests -QPDF err expected endobj 0 -QPDF err wrong objid/generation 0 -QPDF check objid 1 -QPDF check generation 1 -QPDF check obj 1 -QPDF object stream offsets not increasing 0 -QPDF ignore self-referential object stream 0 -QPDF object stream contains id < 1 0 QPDF hint table length direct 0 QPDF P absent in lindict 1 -QPDF expected n n obj 0 QPDF opt direct pages resource 1 -QPDF opt inheritable keys 0 QPDF opt no inheritable keys 0 -QPDF opt erase empty key ancestor 0 -QPDF opt resource inherited 0 QPDF opt page resource hides ancestor 0 -QPDF opt key ancestors depth > 1 0 QPDF opt loop detected 0 QPDF categorize pagemode present 1 QPDF categorize pagemode outlines 1 @@ -39,26 +26,13 @@ main QTest dictionary indirect 1 main QTest stream 0 QPDF lin write nshared_total > nshared_first_page 1 QPDFWriter encrypted hint stream 0 -QPDF opt inherited scalar 0 -QPDF xref reused object 0 QPDF xref gen > 0 1 -QPDF xref size mismatch 0 -QPDF not a pdf file 0 -QPDF can't find startxref 0 QPDF startxref more than 1024 before end 0 -QPDF invalid xref 0 -QPDF invalid xref entry 0 -QPDF missing trailer 0 -QPDF trailer lacks size 0 -QPDF trailer size not integer 0 -QPDF trailer prev not integer 0 QPDFParser bad brace 0 QPDFParser bad brace in parseRemainder 0 QPDFParser bad array close 0 QPDFParser bad array close in parseRemainder 0 QPDFParser bad dictionary close 0 -QPDFParser bad dictionary close in parseRemainder 0 -QPDF can't find xref 0 QPDFTokenizer bad ) 0 QPDFTokenizer bad > 0 QPDFTokenizer bad hexstring character 0 @@ -69,26 +43,15 @@ QPDFTokenizer bad name 2 0 QPDF UseOutlines but no Outlines 0 QPDFObjectHandle makeDirect loop 0 QPDFObjectHandle copy stream 1 -QPDF default for xref stream field 0 0 -QPDF prev key in xref stream dictionary 0 -QPDF prev key in trailer dictionary 0 -QPDF found xref stream 0 QPDF ignoring XRefStm in trailer 0 -QPDF xref deleted object 0 SF_FlateLzwDecode PNG filter 0 QPDF xref /Index is array 1 QPDFWriter encrypt object stream 0 QPDF exclude indirect length 0 QPDF exclude encryption dictionary 0 -QPDF loop detected traversing objects 0 -QPDF reconstructed xref table 0 -QPDF recovered in readObjectAtOffset 0 -QPDF recovered stream length 0 -QPDF found wrong endstream in recovery 0 QPDF_Stream pipeStreamData with null pipeline 0 QPDFJob unable to filter 0 QUtil non-trivial UTF-16 0 -QPDF xref overwrite invalid objgen 0 QPDF decoding error warning 0 qpdf-c called qpdf_init 0 qpdf-c called qpdf_cleanup 0 @@ -133,8 +96,6 @@ QPDF_encryption aes decode string 0 QPDFWriter forced version disabled encryption 0 qpdf-c called qpdf_set_r4_encryption_parameters_insecure 0 qpdf-c called qpdf_set_static_aes_IV 0 -QPDF ERR object stream with wrong type 0 -QPDF object gone after xref reconstruction 0 qpdf-c called qpdf_has_error 0 qpdf-c called qpdf_get_qpdf_version 0 QPDF_Stream pipe original stream data 0 @@ -148,11 +109,7 @@ QPDFObjectHandle append page contents 0 QPDF_Stream getRawStreamData 0 QPDF_Stream getStreamData 0 qpdf-c called qpdf_read_memory 0 -QPDF stream without newline 0 -QPDF stream with CR only 0 QPDF stream with CRNL 0 -QPDF stream with NL only 0 -QPDF replaceObject called with indirect object 0 QPDFWriter copy encrypt metadata 1 qpdf-c get_info_key 1 qpdf-c set_info_key to value 0 @@ -165,12 +122,9 @@ exercise processFile(FILE*) 0 exercise processMemoryFile 0 QPDF remove page 2 QPDF insert page 2 -QPDF updateAllPagesCache 0 -QPDF insert non-indirect page 0 QPDF insert indirect page 0 QPDF_Stream ERR shallow copy stream 0 QPDFObjectHandle newStream with string 0 -QPDF unknown key not inherited 0 QPDF_Stream provider length not provided 0 QPDF_Stream unknown stream length 0 QPDF replaceReserved 0 @@ -181,14 +135,12 @@ QPDF replace array 0 QPDF replace dictionary 0 QPDF replace stream 0 QPDF replace foreign indirect with null 0 -QPDF insert foreign page 0 QPDFWriter copy use_aes 1 QPDFParser indirect without context 0 QPDFObjectHandle trailing data in parse 0 QPDFTokenizer EOF reading token 0 QPDFTokenizer EOF reading appendable token 0 QPDFWriter extra header text no newline 0 -QPDF bogus 0 offset 0 QPDF global offset 0 QPDFWriter make Extensions direct 0 QPDFWriter make ADBE direct 1 @@ -202,29 +154,21 @@ qpdf-c called qpdf_set_r6_encryption_parameters 0 QPDFObjectHandle EOF in inline image 0 QPDFObjectHandle inline image token 0 QPDF not caching overridden objstm object 0 -QPDF_optimization indirect outlines 0 QPDF xref space 2 QPDFJob pages range omitted in middle 0 QPDFWriter standard deterministic ID 1 QPDFWriter linearized deterministic ID 1 qpdf-c called qpdf_set_deterministic_ID 0 QPDFParser invalid objgen 0 -QPDF object id 0 0 -QPDF recursion loop in resolve 0 QPDFParser treat word as string 0 QPDFParser treat word as string in parseRemainder 0 QPDFParser found fake 1 QPDFParser no val for last key 0 -QPDF resolve failure to null 0 QPDFObjectHandle errors in parsecontent 0 QPDFJob split-pages %d 0 QPDFJob split-pages .pdf 0 QPDFJob split-pages other 0 QPDFTokenizer allowing bad token 0 -QPDF ignore first space in xref entry 0 -QPDF ignore first extra space in xref entry 0 -QPDF ignore second extra space in xref entry 0 -QPDF ignore length error xref entry 0 QPDF_encryption pad short parameter 0 QPDFObjectHandle found old angle 1 QPDFTokenizer block long token 0 @@ -261,7 +205,6 @@ QPDFObjectHandle dictionary ignoring replaceKey 0 QPDFObjectHandle numeric non-numeric 0 QPDFObjectHandle erase array bounds 0 qpdf-c called qpdf_check_pdf 0 -QPDF xref loop 0 QPDFParser too deep 0 QPDFFormFieldObjectHelper TU present 0 QPDFFormFieldObjectHelper TM present 0 @@ -330,9 +273,6 @@ QPDFPageDocumentHelper ignore annotation with no appearance 0 QPDFFormFieldObjectHelper replaced BMC at EOF 0 QPDFFormFieldObjectHelper fallback Tf 0 QPDFPageObjectHelper copy shared attribute 1 -QPDF resolve duplicated page object 0 -QPDF handle direct page object 0 -QPDF missing mediabox 0 QPDF inherit mediabox 1 QPDFTokenizer finder found wrong word 0 QPDFTokenizer found EI by byte count 0 @@ -341,7 +281,6 @@ QPDFPageObjectHelper externalize inline image 0 QPDFPageObjectHelper keep inline image 0 QPDFJob image optimize colorspace 0 QPDFJob image optimize bits per component 0 -QPDF xref skipped space 0 QPDF eof skipping spaces before xref 1 QPDF_encryption user matches owner V < 5 0 QPDF_encryption same password 1 @@ -451,9 +390,7 @@ QPDFAcroFormDocumentHelper /DA parse error 0 QPDFAcroFormDocumentHelper AP parse error 1 QPDFJob copy fields not this file 0 QPDFJob copy fields non-first from orig 0 -QPDF resolve duplicated page in insert 0 QPDFWriter exclude from object stream 0 -QPDF_pages findPage not found 0 QPDFJob weak crypto error 0 qpdf-c called qpdf_oh_is_initialized 0 qpdf-c registered progress reporter 0 @@ -533,7 +470,6 @@ QPDF_json bad calledgetallpages 0 QPDF_json bad pushedinheritedpageresources 0 QPDFPageObjectHelper used fallback without copying 0 QPDF skipping cache for known unchecked object 0 -QPDF fix dangling triggered xref reconstruction 0 QPDF recover xref stream 0 QPDFJob json over/under no file 0 QPDF_Array copy 1