diff --git a/include/qpdf/ObjectHandle.hh b/include/qpdf/ObjectHandle.hh index 0bafc31..821acaf 100644 --- a/include/qpdf/ObjectHandle.hh +++ b/include/qpdf/ObjectHandle.hh @@ -94,6 +94,7 @@ namespace qpdf inline QPDFObjGen id_gen() const; inline bool indirect() const; inline bool null() const; + inline qpdf_offset_t offset() const; inline QPDF* qpdf() const; inline qpdf_object_type_e raw_type_code() const; inline qpdf_object_type_e resolved_type_code() const; diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index ba9f793..4dd52d2 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -742,7 +742,6 @@ class QPDF static std::string const qpdf_version; class ObjCache; - class ObjCopier; class EncryptionParameters; class ForeignStreamData; class CopiedStreamDataProvider; @@ -775,8 +774,8 @@ class QPDF Pipeline* pipeline, bool suppress_warnings, bool will_retry); - bool pipeForeignStreamData( - std::shared_ptr, Pipeline*, bool suppress_warnings, bool will_retry); + bool + pipeForeignStreamData(ForeignStreamData&, Pipeline*, bool suppress_warnings, bool will_retry); static bool pipeStreamData( std::shared_ptr encp, std::shared_ptr file, @@ -790,27 +789,6 @@ class QPDF bool suppress_warnings, bool will_retry); - // For QPDFWriter: - - std::map const& getXRefTableInternal(); - // Get a list of objects that would be permitted in an object stream. - template - std::vector getCompressibleObjGens(); - std::vector getCompressibleObjVector(); - std::vector getCompressibleObjSet(); - - // methods to support page handling - - 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); - // methods to support encryption -- implemented in QPDF_encryption.cc void initializeEncryption(); static std::string @@ -827,9 +805,6 @@ class QPDF std::unique_ptr& heap); // Methods to support object copying - void reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top); - QPDFObjectHandle - replaceForeignIndirectObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top); void copyStreamData(QPDFObjectHandle dest_stream, QPDFObjectHandle src_stream); struct HPageOffsetEntry; diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 2468da6..5a7b912 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -27,6 +27,8 @@ using namespace qpdf; using namespace std::literals; +using Objects = QPDF::Doc::Objects; + // This must be a fixed value. This API returns a const reference to it, and the C API relies on its // being static as well. std::string const QPDF::qpdf_version(QPDF_VERSION); @@ -109,20 +111,14 @@ namespace } // namespace QPDF::ForeignStreamData::ForeignStreamData( - std::shared_ptr encp, - std::shared_ptr file, - QPDFObjGen foreign_og, - qpdf_offset_t offset, - size_t length, - QPDFObjectHandle local_dict, - bool is_root_metadata) : - encp(encp), - file(file), - foreign_og(foreign_og), + Stream& foreign, qpdf_offset_t offset, QPDFObjectHandle local_dict) : + encp(foreign.qpdf()->m->encp), + file(foreign.qpdf()->m->file), + foreign_og(foreign.id_gen()), offset(offset), - length(length), + length(foreign.getLength()), local_dict(local_dict), - is_root_metadata(is_root_metadata) + is_root_metadata(foreign.isRootMetadata()) { } @@ -136,11 +132,11 @@ bool QPDF::CopiedStreamDataProvider::provideStreamData( QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry) { - std::shared_ptr foreign_data = foreign_stream_data[og]; + auto foreign_data = foreign_stream_data.find(og); bool result = false; - if (foreign_data.get()) { + if (foreign_data != foreign_stream_data.end()) { result = destination_qpdf.pipeForeignStreamData( - foreign_data, pipeline, suppress_warnings, will_retry); + foreign_data->second, pipeline, suppress_warnings, will_retry); QTC::TC("qpdf", "QPDF copy foreign with data", result ? 0 : 1); } else { auto foreign_stream = foreign_streams[og]; @@ -151,20 +147,6 @@ QPDF::CopiedStreamDataProvider::provideStreamData( return result; } -void -QPDF::CopiedStreamDataProvider::registerForeignStream( - QPDFObjGen const& local_og, QPDFObjectHandle foreign_stream) -{ - this->foreign_streams[local_og] = foreign_stream; -} - -void -QPDF::CopiedStreamDataProvider::registerForeignStream( - QPDFObjGen const& local_og, std::shared_ptr foreign_stream) -{ - this->foreign_stream_data[local_og] = foreign_stream; -} - QPDF::StringDecrypter::StringDecrypter(QPDF* qpdf, QPDFObjGen og) : qpdf(qpdf), og(og) @@ -184,8 +166,8 @@ QPDF::Members::Members(QPDF& qpdf) : objects(doc.objects()), pages(doc.pages()), log(QPDFLogger::defaultLogger()), - file(new InvalidInputSource()), - encp(new EncryptionParameters) + file(std::make_shared()), + encp(std::make_shared()) { } @@ -491,6 +473,25 @@ QPDF::getObjectByID(int objid, int generation) QPDFObjectHandle QPDF::copyForeignObject(QPDFObjectHandle foreign) { + return m->objects.foreign().copied(foreign); +} + +Objects ::Foreign::Copier& +Objects::Foreign::copier(QPDFObjectHandle const& foreign) +{ + if (!foreign.isIndirect()) { + throw std::logic_error("QPDF::copyForeign called with direct object handle"); + } + QPDF& other = *foreign.qpdf(); + if (&other == &qpdf) { + throw std::logic_error("QPDF::copyForeign called with object from this QPDF"); + } + return copiers.insert({other.getUniqueId(), {qpdf}}).first->second; +} + +QPDFObjectHandle +Objects::Foreign::Copier::copied(QPDFObjectHandle const& foreign) +{ // Here's an explanation of what's going on here. // // A QPDFObjectHandle that is an indirect object has an owning QPDF. The object ID and @@ -499,7 +500,7 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign) // references to the corresponding object in the local file. // // To do this, we maintain mappings from foreign object IDs to local object IDs for each foreign - // QPDF that we are copying from. The mapping is stored in an ObjCopier, which contains a + // QPDF that we are copying from. The mapping is stored in an Foreign::Copier, which contains a // mapping from the foreign ObjGen to the local QPDFObjectHandle. // // To copy, we do a deep traversal of the foreign object with loop detection to discover all @@ -525,218 +526,185 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign) // Note that we explicitly allow use of copyForeignObject on page objects. It is a documented // use case to copy pages this way if the intention is to not update the pages tree. - if (!foreign.isIndirect()) { - QTC::TC("qpdf", "QPDF copyForeign direct"); - throw std::logic_error("QPDF::copyForeign called with direct object handle"); - } - QPDF& other = foreign.getQPDF(); - if (&other == this) { - QTC::TC("qpdf", "QPDF copyForeign not foreign"); - throw std::logic_error("QPDF::copyForeign called with object from this QPDF"); - } - ObjCopier& obj_copier = m->object_copiers[other.m->unique_id]; - if (!obj_copier.visiting.empty()) { - throw std::logic_error( - "obj_copier.visiting is not empty at the beginning of copyForeignObject"); - } + util::assertion( + visiting.empty(), "obj_copier.visiting is not empty at the beginning of copyForeignObject"); // Make sure we have an object in this file for every referenced object in the old file. // obj_copier.object_map maps foreign QPDFObjGen to local objects. For everything new that we // have to copy, the local object will be a reservation, unless it is a stream, in which case // the local object will already be a stream. - reserveObjects(foreign, obj_copier, true); + reserve_objects(foreign, true); - if (!obj_copier.visiting.empty()) { - throw std::logic_error("obj_copier.visiting is not empty after reserving objects"); - } + util::assertion(visiting.empty(), "obj_copier.visiting is not empty after reserving objects"); // Copy any new objects and replace the reservations. - for (auto& to_copy: obj_copier.to_copy) { - QPDFObjectHandle copy = replaceForeignIndirectObjects(to_copy, obj_copier, true); - if (!to_copy.isStream()) { - QPDFObjGen og(to_copy.getObjGen()); - replaceReserved(obj_copier.object_map[og], copy); + for (auto& oh: to_copy) { + auto copy = replace_indirect_object(oh, true); + if (!oh.isStream()) { + qpdf.replaceReserved(object_map[oh], copy); } } - obj_copier.to_copy.clear(); + to_copy.clear(); auto og = foreign.getObjGen(); - if (!obj_copier.object_map.contains(og)) { - warn(damagedPDF( - other.getFilename() + " object " + og.unparse(' '), - foreign.getParsedOffset(), + if (!object_map.contains(og)) { + qpdf.warn(qpdf.damagedPDF( + foreign.qpdf()->getFilename() + " object " + og.unparse(' '), + foreign.offset(), "unexpected reference to /Pages object while copying foreign object; replacing with " "null")); return QPDFObjectHandle::newNull(); } - return obj_copier.object_map[foreign.getObjGen()]; + return object_map[foreign]; } void -QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) +Objects::Foreign::Copier::reserve_objects(QPDFObjectHandle const& foreign, bool top) { - auto foreign_tc = foreign.getTypeCode(); - if (foreign_tc == ::ot_reserved) { - throw std::logic_error("QPDF: attempting to copy a foreign reserved object"); - } + auto foreign_tc = foreign.type_code(); + util::assertion( + foreign_tc != ::ot_reserved, "QPDF: attempting to copy a foreign reserved object"); if (foreign.isPagesObject()) { return; } - if (foreign.isIndirect()) { + if (foreign.indirect()) { QPDFObjGen foreign_og(foreign.getObjGen()); - if (!obj_copier.visiting.add(foreign_og)) { + if (!visiting.add(foreign_og)) { return; } - if (obj_copier.object_map.contains(foreign_og)) { - if (!(top && foreign.isPageObject() && obj_copier.object_map[foreign_og].null())) { - obj_copier.visiting.erase(foreign); + if (object_map.contains(foreign_og)) { + if (!(top && foreign.isPageObject() && object_map[foreign_og].null())) { + visiting.erase(foreign); return; } } else { - obj_copier.object_map[foreign_og] = - foreign.isStream() ? newStream() : newIndirectNull(); + object_map[foreign_og] = foreign.isStream() ? qpdf.newStream() : qpdf.newIndirectNull(); if (!top && foreign.isPageObject()) { - obj_copier.visiting.erase(foreign_og); + visiting.erase(foreign_og); return; } } - obj_copier.to_copy.emplace_back(foreign); + to_copy.emplace_back(foreign); } if (foreign_tc == ::ot_array) { - for (auto const& item: foreign.as_array()) { - reserveObjects(item, obj_copier, false); + for (auto const& item: Array(foreign)) { + reserve_objects(item); } } else if (foreign_tc == ::ot_dictionary) { - for (auto const& item: foreign.as_dictionary()) { + for (auto const& item: Dictionary(foreign)) { if (!item.second.null()) { - reserveObjects(item.second, obj_copier, false); + reserve_objects(item.second); } } } else if (foreign_tc == ::ot_stream) { - reserveObjects(foreign.getDict(), obj_copier, false); + reserve_objects(foreign.getDict()); } - obj_copier.visiting.erase(foreign); + visiting.erase(foreign); } QPDFObjectHandle -QPDF::replaceForeignIndirectObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) -{ - auto foreign_tc = foreign.getTypeCode(); - QPDFObjectHandle result; - if ((!top) && foreign.isIndirect()) { - QTC::TC("qpdf", "QPDF replace indirect"); - auto mapping = obj_copier.object_map.find(foreign.getObjGen()); - if (mapping == obj_copier.object_map.end()) { +Objects::Foreign::Copier::replace_indirect_object(QPDFObjectHandle const& foreign, bool top) +{ + auto foreign_tc = foreign.type_code(); + + if (!top && foreign.indirect()) { + auto mapping = object_map.find(foreign.id_gen()); + if (mapping == object_map.end()) { // This case would occur if this is a reference to a Pages object that we didn't // traverse into. - QTC::TC("qpdf", "QPDF replace foreign indirect with null"); - result = QPDFObjectHandle::newNull(); - } else { - result = mapping->second; + return QPDFObjectHandle::newNull(); } - } else if (foreign_tc == ::ot_array) { - QTC::TC("qpdf", "QPDF replace array"); - result = QPDFObjectHandle::newArray(); - for (auto const& item: foreign.as_array()) { - result.appendItem(replaceForeignIndirectObjects(item, obj_copier, false)); + return mapping->second; + } + + if (foreign_tc == ::ot_array) { + Array array = foreign; + std::vector result; + result.reserve(array.size()); + for (auto const& item: array) { + result.emplace_back(replace_indirect_object(item)); } - } else if (foreign_tc == ::ot_dictionary) { - QTC::TC("qpdf", "QPDF replace dictionary"); - result = QPDFObjectHandle::newDictionary(); - for (auto const& [key, value]: foreign.as_dictionary()) { + return Array(std::move(result)); + } + + if (foreign_tc == ::ot_dictionary) { + auto result = Dictionary::empty(); + for (auto const& [key, value]: Dictionary(foreign)) { if (!value.null()) { - result.replaceKey(key, replaceForeignIndirectObjects(value, obj_copier, false)); + result.replaceKey(key, replace_indirect_object(value)); } } - } else if (foreign_tc == ::ot_stream) { - QTC::TC("qpdf", "QPDF replace stream"); - result = obj_copier.object_map[foreign.getObjGen()]; - QPDFObjectHandle dict = result.getDict(); - QPDFObjectHandle old_dict = foreign.getDict(); - for (auto const& [key, value]: old_dict.as_dictionary()) { + return result; + } + + if (foreign_tc == ::ot_stream) { + Stream stream = foreign; + Stream result = object_map[foreign]; + auto dict = result.getDict(); + for (auto const& [key, value]: stream.getDict()) { if (!value.null()) { - dict.replaceKey(key, replaceForeignIndirectObjects(value, obj_copier, false)); + dict.replaceKey(key, replace_indirect_object(value)); } } - copyStreamData(result, foreign); - } else { - foreign.assertScalar(); - result = foreign; - result.makeDirect(); - } - - if (top && (!result.isStream()) && result.isIndirect()) { - throw std::logic_error("replacement for foreign object is indirect"); + qpdf.copyStreamData(result, foreign); + return result; } + foreign.assertScalar(); + auto result = foreign; + result.makeDirect(); return result; } void -QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign) +QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign_oh) { // This method was originally written for copying foreign streams, but it is used by - // QPDFObjectHandle to copy streams from the same QPDF object as well. - - QPDFObjectHandle dict = result.getDict(); - QPDFObjectHandle old_dict = foreign.getDict(); - if (m->copied_stream_data_provider == nullptr) { - m->copied_stream_data_provider = new CopiedStreamDataProvider(*this); - m->copied_streams = - std::shared_ptr(m->copied_stream_data_provider); + // Stream::copy to copy streams from the same QPDF object as well. + + Dictionary dict = result.getDict(); + Dictionary old_dict = foreign_oh.getDict(); + if (!m->copied_stream_data_provider) { + m->copied_stream_data_provider = std::make_shared(*this); } QPDFObjGen local_og(result.getObjGen()); // Copy information from the foreign stream so we can pipe its data later without keeping the // original QPDF object around. QPDF& foreign_stream_qpdf = - foreign.getQPDF("unable to retrieve owning qpdf from foreign stream"); + foreign_oh.getQPDF("unable to retrieve owning qpdf from foreign stream"); - auto stream = foreign.as_stream(); - if (!stream) { + Stream foreign = foreign_oh; + if (!foreign) { throw std::logic_error("unable to retrieve underlying stream object from foreign stream"); } - std::shared_ptr stream_buffer = stream.getStreamDataBuffer(); - if ((foreign_stream_qpdf.m->immediate_copy_from) && (stream_buffer == nullptr)) { + std::shared_ptr stream_buffer = foreign.getStreamDataBuffer(); + if (foreign_stream_qpdf.m->immediate_copy_from && !stream_buffer) { // Pull the stream data into a buffer before attempting the copy operation. Do it on the // source stream so that if the source stream is copied multiple times, we don't have to // keep duplicating the memory. - QTC::TC("qpdf", "QPDF immediate copy stream data"); foreign.replaceStreamData( - foreign.getRawStreamData(), - old_dict.getKey("/Filter"), - old_dict.getKey("/DecodeParms")); - stream_buffer = stream.getStreamDataBuffer(); + foreign.getRawStreamData(), old_dict["/Filter"], old_dict["/DecodeParms"]); + stream_buffer = foreign.getStreamDataBuffer(); } - std::shared_ptr stream_provider = - stream.getStreamDataProvider(); - if (stream_buffer.get()) { - QTC::TC("qpdf", "QPDF copy foreign stream with buffer"); - result.replaceStreamData( - stream_buffer, dict.getKey("/Filter"), dict.getKey("/DecodeParms")); - } else if (stream_provider.get()) { + auto stream_provider = foreign.getStreamDataProvider(); + if (stream_buffer) { + result.replaceStreamData(stream_buffer, dict["/Filter"], dict["/DecodeParms"]); + } else if (stream_provider) { // In this case, the remote stream's QPDF must stay in scope. - QTC::TC("qpdf", "QPDF copy foreign stream with provider"); - m->copied_stream_data_provider->registerForeignStream(local_og, foreign); + m->copied_stream_data_provider->registerForeignStream(local_og, foreign_oh); result.replaceStreamData( - m->copied_streams, dict.getKey("/Filter"), dict.getKey("/DecodeParms")); + m->copied_stream_data_provider, dict["/Filter"], dict["/DecodeParms"]); } else { - auto foreign_stream_data = std::make_shared( - foreign_stream_qpdf.m->encp, - foreign_stream_qpdf.m->file, - foreign, - foreign.getParsedOffset(), - stream.getLength(), - dict, - stream.isRootMetadata()); + auto foreign_stream_data = ForeignStreamData(foreign, foreign_oh.offset(), dict); m->copied_stream_data_provider->registerForeignStream(local_og, foreign_stream_data); result.replaceStreamData( - m->copied_streams, dict.getKey("/Filter"), dict.getKey("/DecodeParms")); + m->copied_stream_data_provider, dict["/Filter"], dict["/DecodeParms"]); } } @@ -820,11 +788,11 @@ QPDF::getRoot() std::map QPDF::getXRefTable() { - return getXRefTableInternal(); + return m->objects.getXRefTableInternal(); } std::map const& -QPDF::getXRefTableInternal() +Objects::getXRefTableInternal() { if (!m->parsed) { throw std::logic_error("QPDF::getXRefTable called before parsing."); @@ -927,23 +895,20 @@ QPDF::pipeStreamData( bool QPDF::pipeForeignStreamData( - std::shared_ptr foreign, - Pipeline* pipeline, - bool suppress_warnings, - bool will_retry) + ForeignStreamData& foreign, Pipeline* pipeline, bool suppress_warnings, bool will_retry) { - if (foreign->encp->encrypted) { + if (foreign.encp->encrypted) { QTC::TC("qpdf", "QPDF pipe foreign encrypted stream"); } return pipeStreamData( - foreign->encp, - foreign->file, + foreign.encp, + foreign.file, *this, - foreign->foreign_og, - foreign->offset, - foreign->length, - foreign->local_dict, - foreign->is_root_metadata, + foreign.foreign_og, + foreign.offset, + foreign.length, + foreign.local_dict, + foreign.is_root_metadata, pipeline, suppress_warnings, will_retry); diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 20933a3..2abb4db 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -1690,7 +1690,7 @@ QPDFObjectHandle::parse( qpdf_offset_t QPDFObjectHandle::getParsedOffset() const { - return obj ? obj->getParsedOffset() : -1; + return offset(); } QPDFObjectHandle @@ -1935,24 +1935,6 @@ QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams) visited.erase(cur_og); } -QPDFObjectHandle -QPDFObjectHandle::copyStream() -{ - assertStream(); - QPDFObjectHandle result = newStream(getOwningQPDF()); - QPDFObjectHandle dict = result.getDict(); - QPDFObjectHandle old_dict = getDict(); - for (auto& iter: QPDFDictItems(old_dict)) { - if (iter.second.isIndirect()) { - dict.replaceKey(iter.first, iter.second); - } else { - dict.replaceKey(iter.first, iter.second.shallowCopy()); - } - } - QPDF::Doc::StreamCopier::copyStreamData(getOwningQPDF(), result, *this); - return result; -} - void QPDFObjectHandle::makeDirect(bool allow_streams) { diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index 2ab21f1..1052e8f 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -320,7 +320,7 @@ class QPDF::Doc::Writer std::map const& getXRefTable() { - return pdf.getXRefTableInternal(); + return objects.getXRefTableInternal(); } size_t diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc index 5b2c47e..99332ea 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -45,6 +44,12 @@ class QPDF::Doc::Streams return qpdf->pipeStreamData( og, offset, length, dict, is_root_metadata, pipeline, suppress_warnings, will_retry); } + + static void + copyStreamData(QPDF* qpdf, QPDFObjectHandle const& dest, QPDFObjectHandle const& src) + { + qpdf->copyStreamData(dest, src); + } }; namespace @@ -207,6 +212,15 @@ Stream::Stream( setDictDescription(); } +Stream +Stream::copy() const +{ + Stream result = qpdf()->newStream(); + result.stream()->stream_dict = getDict().copy(); + QPDF::Doc::Streams::copyStreamData(qpdf(), result, *this); + return result; +} + void Stream::registerStreamFilter( std::string const& filter_name, std::function()> factory) @@ -350,12 +364,11 @@ Stream::getStreamData(qpdf_stream_decode_level_e decode_level) if (!filtered) { throw QPDFExc( qpdf_e_unsupported, - obj->getQPDF()->getFilename(), + qpdf()->getFilename(), "", - obj->getParsedOffset(), + offset(), "getStreamData called on unfilterable stream"); } - QTC::TC("qpdf", "QPDF_Stream getStreamData"); return result; } @@ -367,23 +380,21 @@ Stream::getRawStreamData() if (!pipeStreamData(&buf, nullptr, 0, qpdf_dl_none, false, false)) { throw QPDFExc( qpdf_e_unsupported, - obj->getQPDF()->getFilename(), + qpdf()->getFilename(), "", - obj->getParsedOffset(), + offset(), "error getting raw stream data"); } - QTC::TC("qpdf", "QPDF_Stream getRawStreamData"); return result; } bool Stream::isRootMetadata() const { - if (!getDict().isDictionaryOfType("/Metadata", "/XML")) { + if (!stream()->stream_dict.isDictionaryOfType("/Metadata", "/XML")) { return false; } - auto root_metadata = qpdf()->getRoot().getKey("/Metadata"); - return root_metadata.isSameObjectAs(obj); + return qpdf()->getRoot()["/Metadata"].isSameObjectAs(obj); } bool @@ -579,15 +590,13 @@ Stream::pipeStreamData( s->stream_dict.replaceKey("/Length", QPDFObjectHandle::newInteger(actual_length)); } } else { - if (obj->getParsedOffset() == 0) { - QTC::TC("qpdf", "QPDF_Stream pipe no stream data"); + if (offset() == 0) { throw std::logic_error("pipeStreamData called for stream with no data"); } - QTC::TC("qpdf", "QPDF_Stream pipe original stream data"); if (!QPDF::Doc::Streams::pipeStreamData( - obj->getQPDF(), - obj->getObjGen(), - obj->getParsedOffset(), + qpdf(), + id_gen(), + offset(), s->length, s->stream_dict, isRootMetadata(), @@ -619,6 +628,16 @@ Stream::pipeStreamData( void Stream::replaceStreamData( + std::string&& data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) +{ + auto s = stream(); + s->stream_data = std::make_shared(std::move(data)); + s->stream_provider = nullptr; + replaceFilterData(filter, decode_parms, s->stream_data->getSize()); +} + +void +Stream::replaceStreamData( std::shared_ptr data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) @@ -626,7 +645,7 @@ Stream::replaceStreamData( auto s = stream(); s->stream_data = data; s->stream_provider = nullptr; - replaceFilterData(filter, decode_parms, data->getSize()); + replaceFilterData(filter, decode_parms, data->size()); } void @@ -664,7 +683,7 @@ Stream::replaceFilterData( void Stream::warn(std::string const& message) { - obj->getQPDF()->warn(qpdf_e_damaged_pdf, "", obj->getParsedOffset(), message); + qpdf()->warn(qpdf_e_damaged_pdf, "", offset(), message); } QPDFObjectHandle @@ -772,12 +791,8 @@ void QPDFObjectHandle::replaceStreamData( std::string const& data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) { - auto b = std::make_shared(data.length()); - unsigned char* bp = b->getBuffer(); - if (bp) { - memcpy(bp, data.c_str(), data.length()); - } - as_stream(error).replaceStreamData(b, filter, decode_parms); + std::string s(data); + as_stream(error).replaceStreamData(std::move(s), filter, decode_parms); } void @@ -856,3 +871,9 @@ QPDFObjectHandle::getStreamJSON( { return as_stream(error).getStreamJSON(json_version, json_data, decode_level, p, data_filename); } + +QPDFObjectHandle +QPDFObjectHandle::copyStream() +{ + return as_stream(error).copy(); +} diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc index b6f4f17..e4a61f9 100644 --- a/libqpdf/QPDF_json.cc +++ b/libqpdf/QPDF_json.cc @@ -424,8 +424,7 @@ QPDF::JSONReactor::replaceObject(QPDFObjectHandle&& replacement, JSON const& val auto og = tos.object.getObjGen(); if (replacement.isIndirect() && !(replacement.isStream() && replacement.getObjGen() == og)) { error( - replacement.getParsedOffset(), - "the value of an object may not be an indirect object reference"); + replacement.offset(), "the value of an object may not be an indirect object reference"); return; } pdf.replaceObject(og, replacement); @@ -885,7 +884,7 @@ QPDF::writeJSON( } else { jw << "\n },\n \"" << key; } - if (auto stream = obj.as_stream()) { + if (Stream stream = obj) { jw << "\": {\n \"stream\": "; if (json_stream_data == qpdf_sj_file) { writeJSONStreamFile( diff --git a/libqpdf/QPDF_objects.cc b/libqpdf/QPDF_objects.cc index 7a0726e..8d399f3 100644 --- a/libqpdf/QPDF_objects.cc +++ b/libqpdf/QPDF_objects.cc @@ -1634,7 +1634,7 @@ Objects::resolveObjectsInStream(int obj_stream_number) } m->resolved_object_streams.insert(obj_stream_number); // Force resolution of object stream - auto obj_stream = qpdf.getObject(obj_stream_number, 0).as_stream(); + Stream obj_stream = qpdf.getObject(obj_stream_number, 0); if (!obj_stream) { throw qpdf.damagedPDF( "object " + std::to_string(obj_stream_number) + " 0", diff --git a/libqpdf/qpdf/QPDFObjectHandle_private.hh b/libqpdf/qpdf/QPDFObjectHandle_private.hh index b16232f..2073456 100644 --- a/libqpdf/qpdf/QPDFObjectHandle_private.hh +++ b/libqpdf/qpdf/QPDFObjectHandle_private.hh @@ -445,6 +445,23 @@ namespace qpdf { } + Stream() = default; + Stream(Stream const&) = default; + Stream(Stream&&) = default; + Stream& operator=(Stream const&) = default; + Stream& operator=(Stream&&) = default; + ~Stream() = default; + + Stream(QPDFObjectHandle const& oh) : + BaseHandle(oh.type_code() == ::ot_stream ? oh : QPDFObjectHandle()) + { + } + + Stream(QPDFObjectHandle&& oh) : + BaseHandle(oh.type_code() == ::ot_stream ? std::move(oh) : QPDFObjectHandle()) + { + } + Stream( QPDF& qpdf, QPDFObjGen og, @@ -452,10 +469,12 @@ namespace qpdf qpdf_offset_t offset, size_t length); - QPDFObjectHandle + Stream copy() const; + + Dictionary getDict() const { - return stream()->stream_dict; + return {stream()->stream_dict}; } bool isDataModified() const @@ -501,6 +520,10 @@ namespace qpdf std::string getStreamData(qpdf_stream_decode_level_e level); std::string getRawStreamData(); void replaceStreamData( + std::string&& data, + QPDFObjectHandle const& filter, + QPDFObjectHandle const& decode_parms); + void replaceStreamData( std::shared_ptr data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms); @@ -657,6 +680,12 @@ namespace qpdf return !obj || type_code() == ::ot_null; } + inline qpdf_offset_t + BaseHandle::offset() const + { + return obj ? obj->parsed_offset : -1; + } + inline QPDF* BaseHandle::qpdf() const { diff --git a/libqpdf/qpdf/QPDFObject_private.hh b/libqpdf/qpdf/QPDFObject_private.hh index 03ccad9..6de7158 100644 --- a/libqpdf/qpdf/QPDFObject_private.hh +++ b/libqpdf/qpdf/QPDFObject_private.hh @@ -438,11 +438,6 @@ class QPDFObject parsed_offset = offset; } } - qpdf_offset_t - getParsedOffset() - { - return parsed_offset; - } QPDF* getQPDF() { diff --git a/libqpdf/qpdf/QPDF_private.hh b/libqpdf/qpdf/QPDF_private.hh index 6646ff1..8832cbd 100644 --- a/libqpdf/qpdf/QPDF_private.hh +++ b/libqpdf/qpdf/QPDF_private.hh @@ -13,10 +13,14 @@ using namespace qpdf; -namespace qpdf::is +namespace qpdf { - class OffsetBuffer; -} // namespace qpdf::is + class Stream; + namespace is + { + class OffsetBuffer; + } // namespace is +} // namespace qpdf class BitStream; class BitWriter; @@ -40,14 +44,6 @@ class QPDF::ObjCache qpdf_offset_t end_after_space{0}; }; -class QPDF::ObjCopier -{ - public: - std::map object_map; - std::vector to_copy; - QPDFObjGen::set visiting; -}; - class QPDF::EncryptionParameters { friend class QPDF; @@ -98,14 +94,7 @@ class QPDF::ForeignStreamData friend class QPDF; public: - ForeignStreamData( - std::shared_ptr encp, - std::shared_ptr file, - QPDFObjGen foreign_og, - qpdf_offset_t offset, - size_t length, - QPDFObjectHandle local_dict, - bool is_root_metadata); + ForeignStreamData(Stream& foreign, qpdf_offset_t offset, QPDFObjectHandle local_dict); private: std::shared_ptr encp; @@ -117,20 +106,29 @@ class QPDF::ForeignStreamData bool is_root_metadata{false}; }; -class QPDF::CopiedStreamDataProvider: public QPDFObjectHandle::StreamDataProvider +class QPDF::CopiedStreamDataProvider final: public QPDFObjectHandle::StreamDataProvider { public: CopiedStreamDataProvider(QPDF& destination_qpdf); - ~CopiedStreamDataProvider() override = default; + ~CopiedStreamDataProvider() final = default; bool provideStreamData( - QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry) override; - void registerForeignStream(QPDFObjGen const& local_og, QPDFObjectHandle foreign_stream); - void registerForeignStream(QPDFObjGen const& local_og, std::shared_ptr); + QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry) final; + void + registerForeignStream(QPDFObjGen const& local_og, QPDFObjectHandle foreign_stream) + { + foreign_streams.insert_or_assign(local_og, foreign_stream); + } + + void + registerForeignStream(QPDFObjGen local_og, ForeignStreamData foreign_stream) + { + foreign_stream_data.insert_or_assign(local_og, foreign_stream); + } private: QPDF& destination_qpdf; std::map foreign_streams; - std::map> foreign_stream_data; + std::map foreign_stream_data; }; class QPDF::StringDecrypter final: public QPDFObjectHandle::StringDecrypter @@ -572,6 +570,57 @@ class QPDF::Doc class Objects { public: + class Foreign + { + class Copier + { + public: + Copier(QPDF& qpdf) : + qpdf(qpdf) + { + } + + QPDFObjectHandle copied(QPDFObjectHandle const& foreign); + + private: + QPDFObjectHandle + replace_indirect_object(QPDFObjectHandle const& foreign, bool top = false); + void reserve_objects(QPDFObjectHandle const& foreign, bool top = false); + + QPDF& qpdf; + std::map object_map; + std::vector to_copy; + QPDFObjGen::set visiting; + }; + + public: + Foreign(QPDF& qpdf) : + qpdf(qpdf) + { + } + + Foreign() = delete; + Foreign(Foreign const&) = delete; + Foreign(Foreign&&) = delete; + Foreign& operator=(Foreign const&) = delete; + Foreign& operator=(Foreign&&) = delete; + ~Foreign() = default; + + // Return a local handle to the foreign object. Copy the foreign object if necessary. + QPDFObjectHandle + copied(QPDFObjectHandle const& foreign) + { + return copier(foreign).copied(foreign); + } + + private: + Copier& copier(QPDFObjectHandle const& foreign); + + QPDF& qpdf; + std::map copiers; + }; // class QPDF::Doc::Objects::Foreign + + public: Objects() = delete; Objects(Objects const&) = delete; Objects(Objects&&) = delete; @@ -581,10 +630,17 @@ class QPDF::Doc Objects(QPDF& qpdf, QPDF::Members* m) : qpdf(qpdf), - m(m) + m(m), + foreign_(qpdf) { } + Foreign& + foreign() + { + return foreign_; + } + void parse(char const* password); std::shared_ptr const& resolve(QPDFObjGen og); void inParse(bool); @@ -607,6 +663,7 @@ class QPDF::Doc // For QPDFWriter: + std::map const& getXRefTableInternal(); // Get a list of objects that would be permitted in an object stream. template std::vector getCompressibleObjGens(); @@ -656,9 +713,10 @@ class QPDF::Doc bool isUnresolved(QPDFObjGen og); void setLastObjectDescription(std::string const& description, QPDFObjGen og); - private: QPDF& qpdf; QPDF::Members* m; + + Foreign foreign_; }; // class QPDF::Doc::Objects // This class is used to represent a PDF Pages tree. @@ -699,19 +757,6 @@ class QPDF::Doc 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; @@ -853,10 +898,7 @@ class QPDF::Members bool ever_pushed_inherited_attributes_to_pages{false}; bool ever_called_get_all_pages{false}; std::vector warnings; - std::map object_copiers; - std::shared_ptr copied_streams; - // copied_stream_data_provider is owned by copied_streams - CopiedStreamDataProvider* copied_stream_data_provider{nullptr}; + std::shared_ptr copied_stream_data_provider; bool reconstructed_xref{false}; bool in_read_xref_stream{false}; bool fixed_dangling_refs{false}; diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index a03c408..e4d3e82 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -98,16 +98,12 @@ qpdf-c called qpdf_set_r4_encryption_parameters_insecure 0 qpdf-c called qpdf_set_static_aes_IV 0 qpdf-c called qpdf_has_error 0 qpdf-c called qpdf_get_qpdf_version 0 -QPDF_Stream pipe original stream data 0 QPDF_Stream pipe replaced stream data 0 QPDF_Stream provider length mismatch 0 QPDFObjectHandle newStream 0 QPDFObjectHandle newStream with data 0 -QPDF_Stream pipe no stream data 0 QPDFObjectHandle prepend page contents 0 QPDFObjectHandle append page contents 0 -QPDF_Stream getRawStreamData 0 -QPDF_Stream getStreamData 0 qpdf-c called qpdf_read_memory 0 QPDF stream with CRNL 0 QPDFWriter copy encrypt metadata 1 @@ -128,13 +124,6 @@ QPDFObjectHandle newStream with string 0 QPDF_Stream provider length not provided 0 QPDF_Stream unknown stream length 0 QPDF replaceReserved 0 -QPDF copyForeign direct 0 -QPDF copyForeign not foreign 0 -QPDF replace indirect 0 -QPDF replace array 0 -QPDF replace dictionary 0 -QPDF replace stream 0 -QPDF replace foreign indirect with null 0 QPDFWriter copy use_aes 1 QPDFParser indirect without context 0 QPDFObjectHandle trailing data in parse 0 @@ -256,9 +245,6 @@ QPDFJob image optimize no pipeline 0 QPDFJob image optimize no shrink 0 QPDFJob image optimize too small 0 QPDF pipe foreign encrypted stream 0 -QPDF copy foreign stream with provider 0 -QPDF copy foreign stream with buffer 0 -QPDF immediate copy stream data 0 QPDFJob copy same page more than once 1 QPDFPageObjectHelper bad token finding names 0 QPDFJob password mode bytes 0