Commit 2dc837f40f781c271ae2f0461778678a10c11e86

Authored by m-holger
Committed by GitHub
2 parents 6324d438 c5bf0fdf

Merge pull request #1554 from m-holger/foreign_stream

Refactor foreign object copying
include/qpdf/ObjectHandle.hh
@@ -94,6 +94,7 @@ namespace qpdf @@ -94,6 +94,7 @@ namespace qpdf
94 inline QPDFObjGen id_gen() const; 94 inline QPDFObjGen id_gen() const;
95 inline bool indirect() const; 95 inline bool indirect() const;
96 inline bool null() const; 96 inline bool null() const;
  97 + inline qpdf_offset_t offset() const;
97 inline QPDF* qpdf() const; 98 inline QPDF* qpdf() const;
98 inline qpdf_object_type_e raw_type_code() const; 99 inline qpdf_object_type_e raw_type_code() const;
99 inline qpdf_object_type_e resolved_type_code() const; 100 inline qpdf_object_type_e resolved_type_code() const;
include/qpdf/QPDF.hh
@@ -742,7 +742,6 @@ class QPDF @@ -742,7 +742,6 @@ class QPDF
742 static std::string const qpdf_version; 742 static std::string const qpdf_version;
743 743
744 class ObjCache; 744 class ObjCache;
745 - class ObjCopier;  
746 class EncryptionParameters; 745 class EncryptionParameters;
747 class ForeignStreamData; 746 class ForeignStreamData;
748 class CopiedStreamDataProvider; 747 class CopiedStreamDataProvider;
@@ -775,8 +774,8 @@ class QPDF @@ -775,8 +774,8 @@ class QPDF
775 Pipeline* pipeline, 774 Pipeline* pipeline,
776 bool suppress_warnings, 775 bool suppress_warnings,
777 bool will_retry); 776 bool will_retry);
778 - bool pipeForeignStreamData(  
779 - std::shared_ptr<ForeignStreamData>, Pipeline*, bool suppress_warnings, bool will_retry); 777 + bool
  778 + pipeForeignStreamData(ForeignStreamData&, Pipeline*, bool suppress_warnings, bool will_retry);
780 static bool pipeStreamData( 779 static bool pipeStreamData(
781 std::shared_ptr<QPDF::EncryptionParameters> encp, 780 std::shared_ptr<QPDF::EncryptionParameters> encp,
782 std::shared_ptr<InputSource> file, 781 std::shared_ptr<InputSource> file,
@@ -790,27 +789,6 @@ class QPDF @@ -790,27 +789,6 @@ class QPDF
790 bool suppress_warnings, 789 bool suppress_warnings,
791 bool will_retry); 790 bool will_retry);
792 791
793 - // For QPDFWriter:  
794 -  
795 - std::map<QPDFObjGen, QPDFXRefEntry> const& getXRefTableInternal();  
796 - // Get a list of objects that would be permitted in an object stream.  
797 - template <typename T>  
798 - std::vector<T> getCompressibleObjGens();  
799 - std::vector<QPDFObjGen> getCompressibleObjVector();  
800 - std::vector<bool> getCompressibleObjSet();  
801 -  
802 - // methods to support page handling  
803 -  
804 - void getAllPagesInternal(  
805 - QPDFObjectHandle cur_pages,  
806 - QPDFObjGen::set& visited,  
807 - QPDFObjGen::set& seen,  
808 - bool media_box,  
809 - bool resources);  
810 - void insertPage(QPDFObjectHandle newpage, int pos);  
811 - void flattenPagesTree();  
812 - void insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate);  
813 -  
814 // methods to support encryption -- implemented in QPDF_encryption.cc 792 // methods to support encryption -- implemented in QPDF_encryption.cc
815 void initializeEncryption(); 793 void initializeEncryption();
816 static std::string 794 static std::string
@@ -827,9 +805,6 @@ class QPDF @@ -827,9 +805,6 @@ class QPDF
827 std::unique_ptr<Pipeline>& heap); 805 std::unique_ptr<Pipeline>& heap);
828 806
829 // Methods to support object copying 807 // Methods to support object copying
830 - void reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top);  
831 - QPDFObjectHandle  
832 - replaceForeignIndirectObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top);  
833 void copyStreamData(QPDFObjectHandle dest_stream, QPDFObjectHandle src_stream); 808 void copyStreamData(QPDFObjectHandle dest_stream, QPDFObjectHandle src_stream);
834 809
835 struct HPageOffsetEntry; 810 struct HPageOffsetEntry;
libqpdf/QPDF.cc
@@ -27,6 +27,8 @@ @@ -27,6 +27,8 @@
27 using namespace qpdf; 27 using namespace qpdf;
28 using namespace std::literals; 28 using namespace std::literals;
29 29
  30 +using Objects = QPDF::Doc::Objects;
  31 +
30 // This must be a fixed value. This API returns a const reference to it, and the C API relies on its 32 // This must be a fixed value. This API returns a const reference to it, and the C API relies on its
31 // being static as well. 33 // being static as well.
32 std::string const QPDF::qpdf_version(QPDF_VERSION); 34 std::string const QPDF::qpdf_version(QPDF_VERSION);
@@ -109,20 +111,14 @@ namespace @@ -109,20 +111,14 @@ namespace
109 } // namespace 111 } // namespace
110 112
111 QPDF::ForeignStreamData::ForeignStreamData( 113 QPDF::ForeignStreamData::ForeignStreamData(
112 - std::shared_ptr<EncryptionParameters> encp,  
113 - std::shared_ptr<InputSource> file,  
114 - QPDFObjGen foreign_og,  
115 - qpdf_offset_t offset,  
116 - size_t length,  
117 - QPDFObjectHandle local_dict,  
118 - bool is_root_metadata) :  
119 - encp(encp),  
120 - file(file),  
121 - foreign_og(foreign_og), 114 + Stream& foreign, qpdf_offset_t offset, QPDFObjectHandle local_dict) :
  115 + encp(foreign.qpdf()->m->encp),
  116 + file(foreign.qpdf()->m->file),
  117 + foreign_og(foreign.id_gen()),
122 offset(offset), 118 offset(offset),
123 - length(length), 119 + length(foreign.getLength()),
124 local_dict(local_dict), 120 local_dict(local_dict),
125 - is_root_metadata(is_root_metadata) 121 + is_root_metadata(foreign.isRootMetadata())
126 { 122 {
127 } 123 }
128 124
@@ -136,11 +132,11 @@ bool @@ -136,11 +132,11 @@ bool
136 QPDF::CopiedStreamDataProvider::provideStreamData( 132 QPDF::CopiedStreamDataProvider::provideStreamData(
137 QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry) 133 QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
138 { 134 {
139 - std::shared_ptr<ForeignStreamData> foreign_data = foreign_stream_data[og]; 135 + auto foreign_data = foreign_stream_data.find(og);
140 bool result = false; 136 bool result = false;
141 - if (foreign_data.get()) { 137 + if (foreign_data != foreign_stream_data.end()) {
142 result = destination_qpdf.pipeForeignStreamData( 138 result = destination_qpdf.pipeForeignStreamData(
143 - foreign_data, pipeline, suppress_warnings, will_retry); 139 + foreign_data->second, pipeline, suppress_warnings, will_retry);
144 QTC::TC("qpdf", "QPDF copy foreign with data", result ? 0 : 1); 140 QTC::TC("qpdf", "QPDF copy foreign with data", result ? 0 : 1);
145 } else { 141 } else {
146 auto foreign_stream = foreign_streams[og]; 142 auto foreign_stream = foreign_streams[og];
@@ -151,20 +147,6 @@ QPDF::CopiedStreamDataProvider::provideStreamData( @@ -151,20 +147,6 @@ QPDF::CopiedStreamDataProvider::provideStreamData(
151 return result; 147 return result;
152 } 148 }
153 149
154 -void  
155 -QPDF::CopiedStreamDataProvider::registerForeignStream(  
156 - QPDFObjGen const& local_og, QPDFObjectHandle foreign_stream)  
157 -{  
158 - this->foreign_streams[local_og] = foreign_stream;  
159 -}  
160 -  
161 -void  
162 -QPDF::CopiedStreamDataProvider::registerForeignStream(  
163 - QPDFObjGen const& local_og, std::shared_ptr<ForeignStreamData> foreign_stream)  
164 -{  
165 - this->foreign_stream_data[local_og] = foreign_stream;  
166 -}  
167 -  
168 QPDF::StringDecrypter::StringDecrypter(QPDF* qpdf, QPDFObjGen og) : 150 QPDF::StringDecrypter::StringDecrypter(QPDF* qpdf, QPDFObjGen og) :
169 qpdf(qpdf), 151 qpdf(qpdf),
170 og(og) 152 og(og)
@@ -184,8 +166,8 @@ QPDF::Members::Members(QPDF&amp; qpdf) : @@ -184,8 +166,8 @@ QPDF::Members::Members(QPDF&amp; qpdf) :
184 objects(doc.objects()), 166 objects(doc.objects()),
185 pages(doc.pages()), 167 pages(doc.pages()),
186 log(QPDFLogger::defaultLogger()), 168 log(QPDFLogger::defaultLogger()),
187 - file(new InvalidInputSource()),  
188 - encp(new EncryptionParameters) 169 + file(std::make_shared<InvalidInputSource>()),
  170 + encp(std::make_shared<EncryptionParameters>())
189 { 171 {
190 } 172 }
191 173
@@ -491,6 +473,25 @@ QPDF::getObjectByID(int objid, int generation) @@ -491,6 +473,25 @@ QPDF::getObjectByID(int objid, int generation)
491 QPDFObjectHandle 473 QPDFObjectHandle
492 QPDF::copyForeignObject(QPDFObjectHandle foreign) 474 QPDF::copyForeignObject(QPDFObjectHandle foreign)
493 { 475 {
  476 + return m->objects.foreign().copied(foreign);
  477 +}
  478 +
  479 +Objects ::Foreign::Copier&
  480 +Objects::Foreign::copier(QPDFObjectHandle const& foreign)
  481 +{
  482 + if (!foreign.isIndirect()) {
  483 + throw std::logic_error("QPDF::copyForeign called with direct object handle");
  484 + }
  485 + QPDF& other = *foreign.qpdf();
  486 + if (&other == &qpdf) {
  487 + throw std::logic_error("QPDF::copyForeign called with object from this QPDF");
  488 + }
  489 + return copiers.insert({other.getUniqueId(), {qpdf}}).first->second;
  490 +}
  491 +
  492 +QPDFObjectHandle
  493 +Objects::Foreign::Copier::copied(QPDFObjectHandle const& foreign)
  494 +{
494 // Here's an explanation of what's going on here. 495 // Here's an explanation of what's going on here.
495 // 496 //
496 // A QPDFObjectHandle that is an indirect object has an owning QPDF. The object ID and 497 // A QPDFObjectHandle that is an indirect object has an owning QPDF. The object ID and
@@ -499,7 +500,7 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign) @@ -499,7 +500,7 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign)
499 // references to the corresponding object in the local file. 500 // references to the corresponding object in the local file.
500 // 501 //
501 // To do this, we maintain mappings from foreign object IDs to local object IDs for each foreign 502 // To do this, we maintain mappings from foreign object IDs to local object IDs for each foreign
502 - // QPDF that we are copying from. The mapping is stored in an ObjCopier, which contains a 503 + // QPDF that we are copying from. The mapping is stored in an Foreign::Copier, which contains a
503 // mapping from the foreign ObjGen to the local QPDFObjectHandle. 504 // mapping from the foreign ObjGen to the local QPDFObjectHandle.
504 // 505 //
505 // To copy, we do a deep traversal of the foreign object with loop detection to discover all 506 // 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) @@ -525,218 +526,185 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign)
525 526
526 // Note that we explicitly allow use of copyForeignObject on page objects. It is a documented 527 // Note that we explicitly allow use of copyForeignObject on page objects. It is a documented
527 // use case to copy pages this way if the intention is to not update the pages tree. 528 // use case to copy pages this way if the intention is to not update the pages tree.
528 - if (!foreign.isIndirect()) {  
529 - QTC::TC("qpdf", "QPDF copyForeign direct");  
530 - throw std::logic_error("QPDF::copyForeign called with direct object handle");  
531 - }  
532 - QPDF& other = foreign.getQPDF();  
533 - if (&other == this) {  
534 - QTC::TC("qpdf", "QPDF copyForeign not foreign");  
535 - throw std::logic_error("QPDF::copyForeign called with object from this QPDF");  
536 - }  
537 529
538 - ObjCopier& obj_copier = m->object_copiers[other.m->unique_id];  
539 - if (!obj_copier.visiting.empty()) {  
540 - throw std::logic_error(  
541 - "obj_copier.visiting is not empty at the beginning of copyForeignObject");  
542 - } 530 + util::assertion(
  531 + visiting.empty(), "obj_copier.visiting is not empty at the beginning of copyForeignObject");
543 532
544 // Make sure we have an object in this file for every referenced object in the old file. 533 // Make sure we have an object in this file for every referenced object in the old file.
545 // obj_copier.object_map maps foreign QPDFObjGen to local objects. For everything new that we 534 // obj_copier.object_map maps foreign QPDFObjGen to local objects. For everything new that we
546 // have to copy, the local object will be a reservation, unless it is a stream, in which case 535 // have to copy, the local object will be a reservation, unless it is a stream, in which case
547 // the local object will already be a stream. 536 // the local object will already be a stream.
548 - reserveObjects(foreign, obj_copier, true); 537 + reserve_objects(foreign, true);
549 538
550 - if (!obj_copier.visiting.empty()) {  
551 - throw std::logic_error("obj_copier.visiting is not empty after reserving objects");  
552 - } 539 + util::assertion(visiting.empty(), "obj_copier.visiting is not empty after reserving objects");
553 540
554 // Copy any new objects and replace the reservations. 541 // Copy any new objects and replace the reservations.
555 - for (auto& to_copy: obj_copier.to_copy) {  
556 - QPDFObjectHandle copy = replaceForeignIndirectObjects(to_copy, obj_copier, true);  
557 - if (!to_copy.isStream()) {  
558 - QPDFObjGen og(to_copy.getObjGen());  
559 - replaceReserved(obj_copier.object_map[og], copy); 542 + for (auto& oh: to_copy) {
  543 + auto copy = replace_indirect_object(oh, true);
  544 + if (!oh.isStream()) {
  545 + qpdf.replaceReserved(object_map[oh], copy);
560 } 546 }
561 } 547 }
562 - obj_copier.to_copy.clear(); 548 + to_copy.clear();
563 549
564 auto og = foreign.getObjGen(); 550 auto og = foreign.getObjGen();
565 - if (!obj_copier.object_map.contains(og)) {  
566 - warn(damagedPDF(  
567 - other.getFilename() + " object " + og.unparse(' '),  
568 - foreign.getParsedOffset(), 551 + if (!object_map.contains(og)) {
  552 + qpdf.warn(qpdf.damagedPDF(
  553 + foreign.qpdf()->getFilename() + " object " + og.unparse(' '),
  554 + foreign.offset(),
569 "unexpected reference to /Pages object while copying foreign object; replacing with " 555 "unexpected reference to /Pages object while copying foreign object; replacing with "
570 "null")); 556 "null"));
571 return QPDFObjectHandle::newNull(); 557 return QPDFObjectHandle::newNull();
572 } 558 }
573 - return obj_copier.object_map[foreign.getObjGen()]; 559 + return object_map[foreign];
574 } 560 }
575 561
576 void 562 void
577 -QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) 563 +Objects::Foreign::Copier::reserve_objects(QPDFObjectHandle const& foreign, bool top)
578 { 564 {
579 - auto foreign_tc = foreign.getTypeCode();  
580 - if (foreign_tc == ::ot_reserved) {  
581 - throw std::logic_error("QPDF: attempting to copy a foreign reserved object");  
582 - } 565 + auto foreign_tc = foreign.type_code();
  566 + util::assertion(
  567 + foreign_tc != ::ot_reserved, "QPDF: attempting to copy a foreign reserved object");
583 568
584 if (foreign.isPagesObject()) { 569 if (foreign.isPagesObject()) {
585 return; 570 return;
586 } 571 }
587 572
588 - if (foreign.isIndirect()) { 573 + if (foreign.indirect()) {
589 QPDFObjGen foreign_og(foreign.getObjGen()); 574 QPDFObjGen foreign_og(foreign.getObjGen());
590 - if (!obj_copier.visiting.add(foreign_og)) { 575 + if (!visiting.add(foreign_og)) {
591 return; 576 return;
592 } 577 }
593 - if (obj_copier.object_map.contains(foreign_og)) {  
594 - if (!(top && foreign.isPageObject() && obj_copier.object_map[foreign_og].null())) {  
595 - obj_copier.visiting.erase(foreign); 578 + if (object_map.contains(foreign_og)) {
  579 + if (!(top && foreign.isPageObject() && object_map[foreign_og].null())) {
  580 + visiting.erase(foreign);
596 return; 581 return;
597 } 582 }
598 } else { 583 } else {
599 - obj_copier.object_map[foreign_og] =  
600 - foreign.isStream() ? newStream() : newIndirectNull(); 584 + object_map[foreign_og] = foreign.isStream() ? qpdf.newStream() : qpdf.newIndirectNull();
601 if (!top && foreign.isPageObject()) { 585 if (!top && foreign.isPageObject()) {
602 - obj_copier.visiting.erase(foreign_og); 586 + visiting.erase(foreign_og);
603 return; 587 return;
604 } 588 }
605 } 589 }
606 - obj_copier.to_copy.emplace_back(foreign); 590 + to_copy.emplace_back(foreign);
607 } 591 }
608 592
609 if (foreign_tc == ::ot_array) { 593 if (foreign_tc == ::ot_array) {
610 - for (auto const& item: foreign.as_array()) {  
611 - reserveObjects(item, obj_copier, false); 594 + for (auto const& item: Array(foreign)) {
  595 + reserve_objects(item);
612 } 596 }
613 } else if (foreign_tc == ::ot_dictionary) { 597 } else if (foreign_tc == ::ot_dictionary) {
614 - for (auto const& item: foreign.as_dictionary()) { 598 + for (auto const& item: Dictionary(foreign)) {
615 if (!item.second.null()) { 599 if (!item.second.null()) {
616 - reserveObjects(item.second, obj_copier, false); 600 + reserve_objects(item.second);
617 } 601 }
618 } 602 }
619 } else if (foreign_tc == ::ot_stream) { 603 } else if (foreign_tc == ::ot_stream) {
620 - reserveObjects(foreign.getDict(), obj_copier, false); 604 + reserve_objects(foreign.getDict());
621 } 605 }
622 606
623 - obj_copier.visiting.erase(foreign); 607 + visiting.erase(foreign);
624 } 608 }
625 609
626 QPDFObjectHandle 610 QPDFObjectHandle
627 -QPDF::replaceForeignIndirectObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top)  
628 -{  
629 - auto foreign_tc = foreign.getTypeCode();  
630 - QPDFObjectHandle result;  
631 - if ((!top) && foreign.isIndirect()) {  
632 - QTC::TC("qpdf", "QPDF replace indirect");  
633 - auto mapping = obj_copier.object_map.find(foreign.getObjGen());  
634 - if (mapping == obj_copier.object_map.end()) { 611 +Objects::Foreign::Copier::replace_indirect_object(QPDFObjectHandle const& foreign, bool top)
  612 +{
  613 + auto foreign_tc = foreign.type_code();
  614 +
  615 + if (!top && foreign.indirect()) {
  616 + auto mapping = object_map.find(foreign.id_gen());
  617 + if (mapping == object_map.end()) {
635 // This case would occur if this is a reference to a Pages object that we didn't 618 // This case would occur if this is a reference to a Pages object that we didn't
636 // traverse into. 619 // traverse into.
637 - QTC::TC("qpdf", "QPDF replace foreign indirect with null");  
638 - result = QPDFObjectHandle::newNull();  
639 - } else {  
640 - result = mapping->second; 620 + return QPDFObjectHandle::newNull();
641 } 621 }
642 - } else if (foreign_tc == ::ot_array) {  
643 - QTC::TC("qpdf", "QPDF replace array");  
644 - result = QPDFObjectHandle::newArray();  
645 - for (auto const& item: foreign.as_array()) {  
646 - result.appendItem(replaceForeignIndirectObjects(item, obj_copier, false)); 622 + return mapping->second;
  623 + }
  624 +
  625 + if (foreign_tc == ::ot_array) {
  626 + Array array = foreign;
  627 + std::vector<QPDFObjectHandle> result;
  628 + result.reserve(array.size());
  629 + for (auto const& item: array) {
  630 + result.emplace_back(replace_indirect_object(item));
647 } 631 }
648 - } else if (foreign_tc == ::ot_dictionary) {  
649 - QTC::TC("qpdf", "QPDF replace dictionary");  
650 - result = QPDFObjectHandle::newDictionary();  
651 - for (auto const& [key, value]: foreign.as_dictionary()) { 632 + return Array(std::move(result));
  633 + }
  634 +
  635 + if (foreign_tc == ::ot_dictionary) {
  636 + auto result = Dictionary::empty();
  637 + for (auto const& [key, value]: Dictionary(foreign)) {
652 if (!value.null()) { 638 if (!value.null()) {
653 - result.replaceKey(key, replaceForeignIndirectObjects(value, obj_copier, false)); 639 + result.replaceKey(key, replace_indirect_object(value));
654 } 640 }
655 } 641 }
656 - } else if (foreign_tc == ::ot_stream) {  
657 - QTC::TC("qpdf", "QPDF replace stream");  
658 - result = obj_copier.object_map[foreign.getObjGen()];  
659 - QPDFObjectHandle dict = result.getDict();  
660 - QPDFObjectHandle old_dict = foreign.getDict();  
661 - for (auto const& [key, value]: old_dict.as_dictionary()) { 642 + return result;
  643 + }
  644 +
  645 + if (foreign_tc == ::ot_stream) {
  646 + Stream stream = foreign;
  647 + Stream result = object_map[foreign];
  648 + auto dict = result.getDict();
  649 + for (auto const& [key, value]: stream.getDict()) {
662 if (!value.null()) { 650 if (!value.null()) {
663 - dict.replaceKey(key, replaceForeignIndirectObjects(value, obj_copier, false)); 651 + dict.replaceKey(key, replace_indirect_object(value));
664 } 652 }
665 } 653 }
666 - copyStreamData(result, foreign);  
667 - } else {  
668 - foreign.assertScalar();  
669 - result = foreign;  
670 - result.makeDirect();  
671 - }  
672 -  
673 - if (top && (!result.isStream()) && result.isIndirect()) {  
674 - throw std::logic_error("replacement for foreign object is indirect"); 654 + qpdf.copyStreamData(result, foreign);
  655 + return result;
675 } 656 }
676 657
  658 + foreign.assertScalar();
  659 + auto result = foreign;
  660 + result.makeDirect();
677 return result; 661 return result;
678 } 662 }
679 663
680 void 664 void
681 -QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign) 665 +QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign_oh)
682 { 666 {
683 // This method was originally written for copying foreign streams, but it is used by 667 // This method was originally written for copying foreign streams, but it is used by
684 - // QPDFObjectHandle to copy streams from the same QPDF object as well.  
685 -  
686 - QPDFObjectHandle dict = result.getDict();  
687 - QPDFObjectHandle old_dict = foreign.getDict();  
688 - if (m->copied_stream_data_provider == nullptr) {  
689 - m->copied_stream_data_provider = new CopiedStreamDataProvider(*this);  
690 - m->copied_streams =  
691 - std::shared_ptr<QPDFObjectHandle::StreamDataProvider>(m->copied_stream_data_provider); 668 + // Stream::copy to copy streams from the same QPDF object as well.
  669 +
  670 + Dictionary dict = result.getDict();
  671 + Dictionary old_dict = foreign_oh.getDict();
  672 + if (!m->copied_stream_data_provider) {
  673 + m->copied_stream_data_provider = std::make_shared<CopiedStreamDataProvider>(*this);
692 } 674 }
693 QPDFObjGen local_og(result.getObjGen()); 675 QPDFObjGen local_og(result.getObjGen());
694 // Copy information from the foreign stream so we can pipe its data later without keeping the 676 // Copy information from the foreign stream so we can pipe its data later without keeping the
695 // original QPDF object around. 677 // original QPDF object around.
696 678
697 QPDF& foreign_stream_qpdf = 679 QPDF& foreign_stream_qpdf =
698 - foreign.getQPDF("unable to retrieve owning qpdf from foreign stream"); 680 + foreign_oh.getQPDF("unable to retrieve owning qpdf from foreign stream");
699 681
700 - auto stream = foreign.as_stream();  
701 - if (!stream) { 682 + Stream foreign = foreign_oh;
  683 + if (!foreign) {
702 throw std::logic_error("unable to retrieve underlying stream object from foreign stream"); 684 throw std::logic_error("unable to retrieve underlying stream object from foreign stream");
703 } 685 }
704 - std::shared_ptr<Buffer> stream_buffer = stream.getStreamDataBuffer();  
705 - if ((foreign_stream_qpdf.m->immediate_copy_from) && (stream_buffer == nullptr)) { 686 + std::shared_ptr<Buffer> stream_buffer = foreign.getStreamDataBuffer();
  687 + if (foreign_stream_qpdf.m->immediate_copy_from && !stream_buffer) {
706 // Pull the stream data into a buffer before attempting the copy operation. Do it on the 688 // Pull the stream data into a buffer before attempting the copy operation. Do it on the
707 // source stream so that if the source stream is copied multiple times, we don't have to 689 // source stream so that if the source stream is copied multiple times, we don't have to
708 // keep duplicating the memory. 690 // keep duplicating the memory.
709 - QTC::TC("qpdf", "QPDF immediate copy stream data");  
710 foreign.replaceStreamData( 691 foreign.replaceStreamData(
711 - foreign.getRawStreamData(),  
712 - old_dict.getKey("/Filter"),  
713 - old_dict.getKey("/DecodeParms"));  
714 - stream_buffer = stream.getStreamDataBuffer(); 692 + foreign.getRawStreamData(), old_dict["/Filter"], old_dict["/DecodeParms"]);
  693 + stream_buffer = foreign.getStreamDataBuffer();
715 } 694 }
716 - std::shared_ptr<QPDFObjectHandle::StreamDataProvider> stream_provider =  
717 - stream.getStreamDataProvider();  
718 - if (stream_buffer.get()) {  
719 - QTC::TC("qpdf", "QPDF copy foreign stream with buffer");  
720 - result.replaceStreamData(  
721 - stream_buffer, dict.getKey("/Filter"), dict.getKey("/DecodeParms"));  
722 - } else if (stream_provider.get()) { 695 + auto stream_provider = foreign.getStreamDataProvider();
  696 + if (stream_buffer) {
  697 + result.replaceStreamData(stream_buffer, dict["/Filter"], dict["/DecodeParms"]);
  698 + } else if (stream_provider) {
723 // In this case, the remote stream's QPDF must stay in scope. 699 // In this case, the remote stream's QPDF must stay in scope.
724 - QTC::TC("qpdf", "QPDF copy foreign stream with provider");  
725 - m->copied_stream_data_provider->registerForeignStream(local_og, foreign); 700 + m->copied_stream_data_provider->registerForeignStream(local_og, foreign_oh);
726 result.replaceStreamData( 701 result.replaceStreamData(
727 - m->copied_streams, dict.getKey("/Filter"), dict.getKey("/DecodeParms")); 702 + m->copied_stream_data_provider, dict["/Filter"], dict["/DecodeParms"]);
728 } else { 703 } else {
729 - auto foreign_stream_data = std::make_shared<ForeignStreamData>(  
730 - foreign_stream_qpdf.m->encp,  
731 - foreign_stream_qpdf.m->file,  
732 - foreign,  
733 - foreign.getParsedOffset(),  
734 - stream.getLength(),  
735 - dict,  
736 - stream.isRootMetadata()); 704 + auto foreign_stream_data = ForeignStreamData(foreign, foreign_oh.offset(), dict);
737 m->copied_stream_data_provider->registerForeignStream(local_og, foreign_stream_data); 705 m->copied_stream_data_provider->registerForeignStream(local_og, foreign_stream_data);
738 result.replaceStreamData( 706 result.replaceStreamData(
739 - m->copied_streams, dict.getKey("/Filter"), dict.getKey("/DecodeParms")); 707 + m->copied_stream_data_provider, dict["/Filter"], dict["/DecodeParms"]);
740 } 708 }
741 } 709 }
742 710
@@ -820,11 +788,11 @@ QPDF::getRoot() @@ -820,11 +788,11 @@ QPDF::getRoot()
820 std::map<QPDFObjGen, QPDFXRefEntry> 788 std::map<QPDFObjGen, QPDFXRefEntry>
821 QPDF::getXRefTable() 789 QPDF::getXRefTable()
822 { 790 {
823 - return getXRefTableInternal(); 791 + return m->objects.getXRefTableInternal();
824 } 792 }
825 793
826 std::map<QPDFObjGen, QPDFXRefEntry> const& 794 std::map<QPDFObjGen, QPDFXRefEntry> const&
827 -QPDF::getXRefTableInternal() 795 +Objects::getXRefTableInternal()
828 { 796 {
829 if (!m->parsed) { 797 if (!m->parsed) {
830 throw std::logic_error("QPDF::getXRefTable called before parsing."); 798 throw std::logic_error("QPDF::getXRefTable called before parsing.");
@@ -927,23 +895,20 @@ QPDF::pipeStreamData( @@ -927,23 +895,20 @@ QPDF::pipeStreamData(
927 895
928 bool 896 bool
929 QPDF::pipeForeignStreamData( 897 QPDF::pipeForeignStreamData(
930 - std::shared_ptr<ForeignStreamData> foreign,  
931 - Pipeline* pipeline,  
932 - bool suppress_warnings,  
933 - bool will_retry) 898 + ForeignStreamData& foreign, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
934 { 899 {
935 - if (foreign->encp->encrypted) { 900 + if (foreign.encp->encrypted) {
936 QTC::TC("qpdf", "QPDF pipe foreign encrypted stream"); 901 QTC::TC("qpdf", "QPDF pipe foreign encrypted stream");
937 } 902 }
938 return pipeStreamData( 903 return pipeStreamData(
939 - foreign->encp,  
940 - foreign->file, 904 + foreign.encp,
  905 + foreign.file,
941 *this, 906 *this,
942 - foreign->foreign_og,  
943 - foreign->offset,  
944 - foreign->length,  
945 - foreign->local_dict,  
946 - foreign->is_root_metadata, 907 + foreign.foreign_og,
  908 + foreign.offset,
  909 + foreign.length,
  910 + foreign.local_dict,
  911 + foreign.is_root_metadata,
947 pipeline, 912 pipeline,
948 suppress_warnings, 913 suppress_warnings,
949 will_retry); 914 will_retry);
libqpdf/QPDFObjectHandle.cc
@@ -1690,7 +1690,7 @@ QPDFObjectHandle::parse( @@ -1690,7 +1690,7 @@ QPDFObjectHandle::parse(
1690 qpdf_offset_t 1690 qpdf_offset_t
1691 QPDFObjectHandle::getParsedOffset() const 1691 QPDFObjectHandle::getParsedOffset() const
1692 { 1692 {
1693 - return obj ? obj->getParsedOffset() : -1; 1693 + return offset();
1694 } 1694 }
1695 1695
1696 QPDFObjectHandle 1696 QPDFObjectHandle
@@ -1935,24 +1935,6 @@ QPDFObjectHandle::makeDirect(QPDFObjGen::set&amp; visited, bool stop_at_streams) @@ -1935,24 +1935,6 @@ QPDFObjectHandle::makeDirect(QPDFObjGen::set&amp; visited, bool stop_at_streams)
1935 visited.erase(cur_og); 1935 visited.erase(cur_og);
1936 } 1936 }
1937 1937
1938 -QPDFObjectHandle  
1939 -QPDFObjectHandle::copyStream()  
1940 -{  
1941 - assertStream();  
1942 - QPDFObjectHandle result = newStream(getOwningQPDF());  
1943 - QPDFObjectHandle dict = result.getDict();  
1944 - QPDFObjectHandle old_dict = getDict();  
1945 - for (auto& iter: QPDFDictItems(old_dict)) {  
1946 - if (iter.second.isIndirect()) {  
1947 - dict.replaceKey(iter.first, iter.second);  
1948 - } else {  
1949 - dict.replaceKey(iter.first, iter.second.shallowCopy());  
1950 - }  
1951 - }  
1952 - QPDF::Doc::StreamCopier::copyStreamData(getOwningQPDF(), result, *this);  
1953 - return result;  
1954 -}  
1955 -  
1956 void 1938 void
1957 QPDFObjectHandle::makeDirect(bool allow_streams) 1939 QPDFObjectHandle::makeDirect(bool allow_streams)
1958 { 1940 {
libqpdf/QPDFWriter.cc
@@ -320,7 +320,7 @@ class QPDF::Doc::Writer @@ -320,7 +320,7 @@ class QPDF::Doc::Writer
320 std::map<QPDFObjGen, QPDFXRefEntry> const& 320 std::map<QPDFObjGen, QPDFXRefEntry> const&
321 getXRefTable() 321 getXRefTable()
322 { 322 {
323 - return pdf.getXRefTableInternal(); 323 + return objects.getXRefTableInternal();
324 } 324 }
325 325
326 size_t 326 size_t
libqpdf/QPDF_Stream.cc
@@ -4,7 +4,6 @@ @@ -4,7 +4,6 @@
4 #include <qpdf/JSON_writer.hh> 4 #include <qpdf/JSON_writer.hh>
5 #include <qpdf/Pipeline.hh> 5 #include <qpdf/Pipeline.hh>
6 #include <qpdf/Pipeline_private.hh> 6 #include <qpdf/Pipeline_private.hh>
7 -#include <qpdf/Pl_Base64.hh>  
8 #include <qpdf/Pl_Buffer.hh> 7 #include <qpdf/Pl_Buffer.hh>
9 #include <qpdf/Pl_Count.hh> 8 #include <qpdf/Pl_Count.hh>
10 #include <qpdf/Pl_Discard.hh> 9 #include <qpdf/Pl_Discard.hh>
@@ -45,6 +44,12 @@ class QPDF::Doc::Streams @@ -45,6 +44,12 @@ class QPDF::Doc::Streams
45 return qpdf->pipeStreamData( 44 return qpdf->pipeStreamData(
46 og, offset, length, dict, is_root_metadata, pipeline, suppress_warnings, will_retry); 45 og, offset, length, dict, is_root_metadata, pipeline, suppress_warnings, will_retry);
47 } 46 }
  47 +
  48 + static void
  49 + copyStreamData(QPDF* qpdf, QPDFObjectHandle const& dest, QPDFObjectHandle const& src)
  50 + {
  51 + qpdf->copyStreamData(dest, src);
  52 + }
48 }; 53 };
49 54
50 namespace 55 namespace
@@ -207,6 +212,15 @@ Stream::Stream( @@ -207,6 +212,15 @@ Stream::Stream(
207 setDictDescription(); 212 setDictDescription();
208 } 213 }
209 214
  215 +Stream
  216 +Stream::copy() const
  217 +{
  218 + Stream result = qpdf()->newStream();
  219 + result.stream()->stream_dict = getDict().copy();
  220 + QPDF::Doc::Streams::copyStreamData(qpdf(), result, *this);
  221 + return result;
  222 +}
  223 +
210 void 224 void
211 Stream::registerStreamFilter( 225 Stream::registerStreamFilter(
212 std::string const& filter_name, std::function<std::shared_ptr<QPDFStreamFilter>()> factory) 226 std::string const& filter_name, std::function<std::shared_ptr<QPDFStreamFilter>()> factory)
@@ -350,12 +364,11 @@ Stream::getStreamData(qpdf_stream_decode_level_e decode_level) @@ -350,12 +364,11 @@ Stream::getStreamData(qpdf_stream_decode_level_e decode_level)
350 if (!filtered) { 364 if (!filtered) {
351 throw QPDFExc( 365 throw QPDFExc(
352 qpdf_e_unsupported, 366 qpdf_e_unsupported,
353 - obj->getQPDF()->getFilename(), 367 + qpdf()->getFilename(),
354 "", 368 "",
355 - obj->getParsedOffset(), 369 + offset(),
356 "getStreamData called on unfilterable stream"); 370 "getStreamData called on unfilterable stream");
357 } 371 }
358 - QTC::TC("qpdf", "QPDF_Stream getStreamData");  
359 return result; 372 return result;
360 } 373 }
361 374
@@ -367,23 +380,21 @@ Stream::getRawStreamData() @@ -367,23 +380,21 @@ Stream::getRawStreamData()
367 if (!pipeStreamData(&buf, nullptr, 0, qpdf_dl_none, false, false)) { 380 if (!pipeStreamData(&buf, nullptr, 0, qpdf_dl_none, false, false)) {
368 throw QPDFExc( 381 throw QPDFExc(
369 qpdf_e_unsupported, 382 qpdf_e_unsupported,
370 - obj->getQPDF()->getFilename(), 383 + qpdf()->getFilename(),
371 "", 384 "",
372 - obj->getParsedOffset(), 385 + offset(),
373 "error getting raw stream data"); 386 "error getting raw stream data");
374 } 387 }
375 - QTC::TC("qpdf", "QPDF_Stream getRawStreamData");  
376 return result; 388 return result;
377 } 389 }
378 390
379 bool 391 bool
380 Stream::isRootMetadata() const 392 Stream::isRootMetadata() const
381 { 393 {
382 - if (!getDict().isDictionaryOfType("/Metadata", "/XML")) { 394 + if (!stream()->stream_dict.isDictionaryOfType("/Metadata", "/XML")) {
383 return false; 395 return false;
384 } 396 }
385 - auto root_metadata = qpdf()->getRoot().getKey("/Metadata");  
386 - return root_metadata.isSameObjectAs(obj); 397 + return qpdf()->getRoot()["/Metadata"].isSameObjectAs(obj);
387 } 398 }
388 399
389 bool 400 bool
@@ -579,15 +590,13 @@ Stream::pipeStreamData( @@ -579,15 +590,13 @@ Stream::pipeStreamData(
579 s->stream_dict.replaceKey("/Length", QPDFObjectHandle::newInteger(actual_length)); 590 s->stream_dict.replaceKey("/Length", QPDFObjectHandle::newInteger(actual_length));
580 } 591 }
581 } else { 592 } else {
582 - if (obj->getParsedOffset() == 0) {  
583 - QTC::TC("qpdf", "QPDF_Stream pipe no stream data"); 593 + if (offset() == 0) {
584 throw std::logic_error("pipeStreamData called for stream with no data"); 594 throw std::logic_error("pipeStreamData called for stream with no data");
585 } 595 }
586 - QTC::TC("qpdf", "QPDF_Stream pipe original stream data");  
587 if (!QPDF::Doc::Streams::pipeStreamData( 596 if (!QPDF::Doc::Streams::pipeStreamData(
588 - obj->getQPDF(),  
589 - obj->getObjGen(),  
590 - obj->getParsedOffset(), 597 + qpdf(),
  598 + id_gen(),
  599 + offset(),
591 s->length, 600 s->length,
592 s->stream_dict, 601 s->stream_dict,
593 isRootMetadata(), 602 isRootMetadata(),
@@ -619,6 +628,16 @@ Stream::pipeStreamData( @@ -619,6 +628,16 @@ Stream::pipeStreamData(
619 628
620 void 629 void
621 Stream::replaceStreamData( 630 Stream::replaceStreamData(
  631 + std::string&& data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms)
  632 +{
  633 + auto s = stream();
  634 + s->stream_data = std::make_shared<Buffer>(std::move(data));
  635 + s->stream_provider = nullptr;
  636 + replaceFilterData(filter, decode_parms, s->stream_data->getSize());
  637 +}
  638 +
  639 +void
  640 +Stream::replaceStreamData(
622 std::shared_ptr<Buffer> data, 641 std::shared_ptr<Buffer> data,
623 QPDFObjectHandle const& filter, 642 QPDFObjectHandle const& filter,
624 QPDFObjectHandle const& decode_parms) 643 QPDFObjectHandle const& decode_parms)
@@ -626,7 +645,7 @@ Stream::replaceStreamData( @@ -626,7 +645,7 @@ Stream::replaceStreamData(
626 auto s = stream(); 645 auto s = stream();
627 s->stream_data = data; 646 s->stream_data = data;
628 s->stream_provider = nullptr; 647 s->stream_provider = nullptr;
629 - replaceFilterData(filter, decode_parms, data->getSize()); 648 + replaceFilterData(filter, decode_parms, data->size());
630 } 649 }
631 650
632 void 651 void
@@ -664,7 +683,7 @@ Stream::replaceFilterData( @@ -664,7 +683,7 @@ Stream::replaceFilterData(
664 void 683 void
665 Stream::warn(std::string const& message) 684 Stream::warn(std::string const& message)
666 { 685 {
667 - obj->getQPDF()->warn(qpdf_e_damaged_pdf, "", obj->getParsedOffset(), message); 686 + qpdf()->warn(qpdf_e_damaged_pdf, "", offset(), message);
668 } 687 }
669 688
670 QPDFObjectHandle 689 QPDFObjectHandle
@@ -772,12 +791,8 @@ void @@ -772,12 +791,8 @@ void
772 QPDFObjectHandle::replaceStreamData( 791 QPDFObjectHandle::replaceStreamData(
773 std::string const& data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) 792 std::string const& data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms)
774 { 793 {
775 - auto b = std::make_shared<Buffer>(data.length());  
776 - unsigned char* bp = b->getBuffer();  
777 - if (bp) {  
778 - memcpy(bp, data.c_str(), data.length());  
779 - }  
780 - as_stream(error).replaceStreamData(b, filter, decode_parms); 794 + std::string s(data);
  795 + as_stream(error).replaceStreamData(std::move(s), filter, decode_parms);
781 } 796 }
782 797
783 void 798 void
@@ -856,3 +871,9 @@ QPDFObjectHandle::getStreamJSON( @@ -856,3 +871,9 @@ QPDFObjectHandle::getStreamJSON(
856 { 871 {
857 return as_stream(error).getStreamJSON(json_version, json_data, decode_level, p, data_filename); 872 return as_stream(error).getStreamJSON(json_version, json_data, decode_level, p, data_filename);
858 } 873 }
  874 +
  875 +QPDFObjectHandle
  876 +QPDFObjectHandle::copyStream()
  877 +{
  878 + return as_stream(error).copy();
  879 +}
libqpdf/QPDF_json.cc
@@ -424,8 +424,7 @@ QPDF::JSONReactor::replaceObject(QPDFObjectHandle&amp;&amp; replacement, JSON const&amp; val @@ -424,8 +424,7 @@ QPDF::JSONReactor::replaceObject(QPDFObjectHandle&amp;&amp; replacement, JSON const&amp; val
424 auto og = tos.object.getObjGen(); 424 auto og = tos.object.getObjGen();
425 if (replacement.isIndirect() && !(replacement.isStream() && replacement.getObjGen() == og)) { 425 if (replacement.isIndirect() && !(replacement.isStream() && replacement.getObjGen() == og)) {
426 error( 426 error(
427 - replacement.getParsedOffset(),  
428 - "the value of an object may not be an indirect object reference"); 427 + replacement.offset(), "the value of an object may not be an indirect object reference");
429 return; 428 return;
430 } 429 }
431 pdf.replaceObject(og, replacement); 430 pdf.replaceObject(og, replacement);
@@ -885,7 +884,7 @@ QPDF::writeJSON( @@ -885,7 +884,7 @@ QPDF::writeJSON(
885 } else { 884 } else {
886 jw << "\n },\n \"" << key; 885 jw << "\n },\n \"" << key;
887 } 886 }
888 - if (auto stream = obj.as_stream()) { 887 + if (Stream stream = obj) {
889 jw << "\": {\n \"stream\": "; 888 jw << "\": {\n \"stream\": ";
890 if (json_stream_data == qpdf_sj_file) { 889 if (json_stream_data == qpdf_sj_file) {
891 writeJSONStreamFile( 890 writeJSONStreamFile(
libqpdf/QPDF_objects.cc
@@ -1634,7 +1634,7 @@ Objects::resolveObjectsInStream(int obj_stream_number) @@ -1634,7 +1634,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1634 } 1634 }
1635 m->resolved_object_streams.insert(obj_stream_number); 1635 m->resolved_object_streams.insert(obj_stream_number);
1636 // Force resolution of object stream 1636 // Force resolution of object stream
1637 - auto obj_stream = qpdf.getObject(obj_stream_number, 0).as_stream(); 1637 + Stream obj_stream = qpdf.getObject(obj_stream_number, 0);
1638 if (!obj_stream) { 1638 if (!obj_stream) {
1639 throw qpdf.damagedPDF( 1639 throw qpdf.damagedPDF(
1640 "object " + std::to_string(obj_stream_number) + " 0", 1640 "object " + std::to_string(obj_stream_number) + " 0",
libqpdf/qpdf/QPDFObjectHandle_private.hh
@@ -445,6 +445,23 @@ namespace qpdf @@ -445,6 +445,23 @@ namespace qpdf
445 { 445 {
446 } 446 }
447 447
  448 + Stream() = default;
  449 + Stream(Stream const&) = default;
  450 + Stream(Stream&&) = default;
  451 + Stream& operator=(Stream const&) = default;
  452 + Stream& operator=(Stream&&) = default;
  453 + ~Stream() = default;
  454 +
  455 + Stream(QPDFObjectHandle const& oh) :
  456 + BaseHandle(oh.type_code() == ::ot_stream ? oh : QPDFObjectHandle())
  457 + {
  458 + }
  459 +
  460 + Stream(QPDFObjectHandle&& oh) :
  461 + BaseHandle(oh.type_code() == ::ot_stream ? std::move(oh) : QPDFObjectHandle())
  462 + {
  463 + }
  464 +
448 Stream( 465 Stream(
449 QPDF& qpdf, 466 QPDF& qpdf,
450 QPDFObjGen og, 467 QPDFObjGen og,
@@ -452,10 +469,12 @@ namespace qpdf @@ -452,10 +469,12 @@ namespace qpdf
452 qpdf_offset_t offset, 469 qpdf_offset_t offset,
453 size_t length); 470 size_t length);
454 471
455 - QPDFObjectHandle 472 + Stream copy() const;
  473 +
  474 + Dictionary
456 getDict() const 475 getDict() const
457 { 476 {
458 - return stream()->stream_dict; 477 + return {stream()->stream_dict};
459 } 478 }
460 bool 479 bool
461 isDataModified() const 480 isDataModified() const
@@ -501,6 +520,10 @@ namespace qpdf @@ -501,6 +520,10 @@ namespace qpdf
501 std::string getStreamData(qpdf_stream_decode_level_e level); 520 std::string getStreamData(qpdf_stream_decode_level_e level);
502 std::string getRawStreamData(); 521 std::string getRawStreamData();
503 void replaceStreamData( 522 void replaceStreamData(
  523 + std::string&& data,
  524 + QPDFObjectHandle const& filter,
  525 + QPDFObjectHandle const& decode_parms);
  526 + void replaceStreamData(
504 std::shared_ptr<Buffer> data, 527 std::shared_ptr<Buffer> data,
505 QPDFObjectHandle const& filter, 528 QPDFObjectHandle const& filter,
506 QPDFObjectHandle const& decode_parms); 529 QPDFObjectHandle const& decode_parms);
@@ -657,6 +680,12 @@ namespace qpdf @@ -657,6 +680,12 @@ namespace qpdf
657 return !obj || type_code() == ::ot_null; 680 return !obj || type_code() == ::ot_null;
658 } 681 }
659 682
  683 + inline qpdf_offset_t
  684 + BaseHandle::offset() const
  685 + {
  686 + return obj ? obj->parsed_offset : -1;
  687 + }
  688 +
660 inline QPDF* 689 inline QPDF*
661 BaseHandle::qpdf() const 690 BaseHandle::qpdf() const
662 { 691 {
libqpdf/qpdf/QPDFObject_private.hh
@@ -438,11 +438,6 @@ class QPDFObject @@ -438,11 +438,6 @@ class QPDFObject
438 parsed_offset = offset; 438 parsed_offset = offset;
439 } 439 }
440 } 440 }
441 - qpdf_offset_t  
442 - getParsedOffset()  
443 - {  
444 - return parsed_offset;  
445 - }  
446 QPDF* 441 QPDF*
447 getQPDF() 442 getQPDF()
448 { 443 {
libqpdf/qpdf/QPDF_private.hh
@@ -13,10 +13,14 @@ @@ -13,10 +13,14 @@
13 13
14 using namespace qpdf; 14 using namespace qpdf;
15 15
16 -namespace qpdf::is 16 +namespace qpdf
17 { 17 {
18 - class OffsetBuffer;  
19 -} // namespace qpdf::is 18 + class Stream;
  19 + namespace is
  20 + {
  21 + class OffsetBuffer;
  22 + } // namespace is
  23 +} // namespace qpdf
20 24
21 class BitStream; 25 class BitStream;
22 class BitWriter; 26 class BitWriter;
@@ -40,14 +44,6 @@ class QPDF::ObjCache @@ -40,14 +44,6 @@ class QPDF::ObjCache
40 qpdf_offset_t end_after_space{0}; 44 qpdf_offset_t end_after_space{0};
41 }; 45 };
42 46
43 -class QPDF::ObjCopier  
44 -{  
45 - public:  
46 - std::map<QPDFObjGen, QPDFObjectHandle> object_map;  
47 - std::vector<QPDFObjectHandle> to_copy;  
48 - QPDFObjGen::set visiting;  
49 -};  
50 -  
51 class QPDF::EncryptionParameters 47 class QPDF::EncryptionParameters
52 { 48 {
53 friend class QPDF; 49 friend class QPDF;
@@ -98,14 +94,7 @@ class QPDF::ForeignStreamData @@ -98,14 +94,7 @@ class QPDF::ForeignStreamData
98 friend class QPDF; 94 friend class QPDF;
99 95
100 public: 96 public:
101 - ForeignStreamData(  
102 - std::shared_ptr<EncryptionParameters> encp,  
103 - std::shared_ptr<InputSource> file,  
104 - QPDFObjGen foreign_og,  
105 - qpdf_offset_t offset,  
106 - size_t length,  
107 - QPDFObjectHandle local_dict,  
108 - bool is_root_metadata); 97 + ForeignStreamData(Stream& foreign, qpdf_offset_t offset, QPDFObjectHandle local_dict);
109 98
110 private: 99 private:
111 std::shared_ptr<EncryptionParameters> encp; 100 std::shared_ptr<EncryptionParameters> encp;
@@ -117,20 +106,29 @@ class QPDF::ForeignStreamData @@ -117,20 +106,29 @@ class QPDF::ForeignStreamData
117 bool is_root_metadata{false}; 106 bool is_root_metadata{false};
118 }; 107 };
119 108
120 -class QPDF::CopiedStreamDataProvider: public QPDFObjectHandle::StreamDataProvider 109 +class QPDF::CopiedStreamDataProvider final: public QPDFObjectHandle::StreamDataProvider
121 { 110 {
122 public: 111 public:
123 CopiedStreamDataProvider(QPDF& destination_qpdf); 112 CopiedStreamDataProvider(QPDF& destination_qpdf);
124 - ~CopiedStreamDataProvider() override = default; 113 + ~CopiedStreamDataProvider() final = default;
125 bool provideStreamData( 114 bool provideStreamData(
126 - QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry) override;  
127 - void registerForeignStream(QPDFObjGen const& local_og, QPDFObjectHandle foreign_stream);  
128 - void registerForeignStream(QPDFObjGen const& local_og, std::shared_ptr<ForeignStreamData>); 115 + QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry) final;
  116 + void
  117 + registerForeignStream(QPDFObjGen const& local_og, QPDFObjectHandle foreign_stream)
  118 + {
  119 + foreign_streams.insert_or_assign(local_og, foreign_stream);
  120 + }
  121 +
  122 + void
  123 + registerForeignStream(QPDFObjGen local_og, ForeignStreamData foreign_stream)
  124 + {
  125 + foreign_stream_data.insert_or_assign(local_og, foreign_stream);
  126 + }
129 127
130 private: 128 private:
131 QPDF& destination_qpdf; 129 QPDF& destination_qpdf;
132 std::map<QPDFObjGen, QPDFObjectHandle> foreign_streams; 130 std::map<QPDFObjGen, QPDFObjectHandle> foreign_streams;
133 - std::map<QPDFObjGen, std::shared_ptr<ForeignStreamData>> foreign_stream_data; 131 + std::map<QPDFObjGen, ForeignStreamData> foreign_stream_data;
134 }; 132 };
135 133
136 class QPDF::StringDecrypter final: public QPDFObjectHandle::StringDecrypter 134 class QPDF::StringDecrypter final: public QPDFObjectHandle::StringDecrypter
@@ -572,6 +570,57 @@ class QPDF::Doc @@ -572,6 +570,57 @@ class QPDF::Doc
572 class Objects 570 class Objects
573 { 571 {
574 public: 572 public:
  573 + class Foreign
  574 + {
  575 + class Copier
  576 + {
  577 + public:
  578 + Copier(QPDF& qpdf) :
  579 + qpdf(qpdf)
  580 + {
  581 + }
  582 +
  583 + QPDFObjectHandle copied(QPDFObjectHandle const& foreign);
  584 +
  585 + private:
  586 + QPDFObjectHandle
  587 + replace_indirect_object(QPDFObjectHandle const& foreign, bool top = false);
  588 + void reserve_objects(QPDFObjectHandle const& foreign, bool top = false);
  589 +
  590 + QPDF& qpdf;
  591 + std::map<QPDFObjGen, QPDFObjectHandle> object_map;
  592 + std::vector<QPDFObjectHandle> to_copy;
  593 + QPDFObjGen::set visiting;
  594 + };
  595 +
  596 + public:
  597 + Foreign(QPDF& qpdf) :
  598 + qpdf(qpdf)
  599 + {
  600 + }
  601 +
  602 + Foreign() = delete;
  603 + Foreign(Foreign const&) = delete;
  604 + Foreign(Foreign&&) = delete;
  605 + Foreign& operator=(Foreign const&) = delete;
  606 + Foreign& operator=(Foreign&&) = delete;
  607 + ~Foreign() = default;
  608 +
  609 + // Return a local handle to the foreign object. Copy the foreign object if necessary.
  610 + QPDFObjectHandle
  611 + copied(QPDFObjectHandle const& foreign)
  612 + {
  613 + return copier(foreign).copied(foreign);
  614 + }
  615 +
  616 + private:
  617 + Copier& copier(QPDFObjectHandle const& foreign);
  618 +
  619 + QPDF& qpdf;
  620 + std::map<unsigned long long, Copier> copiers;
  621 + }; // class QPDF::Doc::Objects::Foreign
  622 +
  623 + public:
575 Objects() = delete; 624 Objects() = delete;
576 Objects(Objects const&) = delete; 625 Objects(Objects const&) = delete;
577 Objects(Objects&&) = delete; 626 Objects(Objects&&) = delete;
@@ -581,10 +630,17 @@ class QPDF::Doc @@ -581,10 +630,17 @@ class QPDF::Doc
581 630
582 Objects(QPDF& qpdf, QPDF::Members* m) : 631 Objects(QPDF& qpdf, QPDF::Members* m) :
583 qpdf(qpdf), 632 qpdf(qpdf),
584 - m(m) 633 + m(m),
  634 + foreign_(qpdf)
585 { 635 {
586 } 636 }
587 637
  638 + Foreign&
  639 + foreign()
  640 + {
  641 + return foreign_;
  642 + }
  643 +
588 void parse(char const* password); 644 void parse(char const* password);
589 std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og); 645 std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og);
590 void inParse(bool); 646 void inParse(bool);
@@ -607,6 +663,7 @@ class QPDF::Doc @@ -607,6 +663,7 @@ class QPDF::Doc
607 663
608 // For QPDFWriter: 664 // For QPDFWriter:
609 665
  666 + std::map<QPDFObjGen, QPDFXRefEntry> const& getXRefTableInternal();
610 // Get a list of objects that would be permitted in an object stream. 667 // Get a list of objects that would be permitted in an object stream.
611 template <typename T> 668 template <typename T>
612 std::vector<T> getCompressibleObjGens(); 669 std::vector<T> getCompressibleObjGens();
@@ -656,9 +713,10 @@ class QPDF::Doc @@ -656,9 +713,10 @@ class QPDF::Doc
656 bool isUnresolved(QPDFObjGen og); 713 bool isUnresolved(QPDFObjGen og);
657 void setLastObjectDescription(std::string const& description, QPDFObjGen og); 714 void setLastObjectDescription(std::string const& description, QPDFObjGen og);
658 715
659 - private:  
660 QPDF& qpdf; 716 QPDF& qpdf;
661 QPDF::Members* m; 717 QPDF::Members* m;
  718 +
  719 + Foreign foreign_;
662 }; // class QPDF::Doc::Objects 720 }; // class QPDF::Doc::Objects
663 721
664 // This class is used to represent a PDF Pages tree. 722 // This class is used to represent a PDF Pages tree.
@@ -699,19 +757,6 @@ class QPDF::Doc @@ -699,19 +757,6 @@ class QPDF::Doc
699 QPDF::Members* m; 757 QPDF::Members* m;
700 }; // class QPDF::Doc::Pages 758 }; // class QPDF::Doc::Pages
701 759
702 - // StreamCopier class is restricted to QPDFObjectHandle so it can copy stream data.  
703 - class StreamCopier  
704 - {  
705 - friend class QPDFObjectHandle;  
706 -  
707 - private:  
708 - static void  
709 - copyStreamData(QPDF* qpdf, QPDFObjectHandle const& dest, QPDFObjectHandle const& src)  
710 - {  
711 - qpdf->copyStreamData(dest, src);  
712 - }  
713 - };  
714 -  
715 Doc() = delete; 760 Doc() = delete;
716 Doc(Doc const&) = delete; 761 Doc(Doc const&) = delete;
717 Doc(Doc&&) = delete; 762 Doc(Doc&&) = delete;
@@ -853,10 +898,7 @@ class QPDF::Members @@ -853,10 +898,7 @@ class QPDF::Members
853 bool ever_pushed_inherited_attributes_to_pages{false}; 898 bool ever_pushed_inherited_attributes_to_pages{false};
854 bool ever_called_get_all_pages{false}; 899 bool ever_called_get_all_pages{false};
855 std::vector<QPDFExc> warnings; 900 std::vector<QPDFExc> warnings;
856 - std::map<unsigned long long, ObjCopier> object_copiers;  
857 - std::shared_ptr<QPDFObjectHandle::StreamDataProvider> copied_streams;  
858 - // copied_stream_data_provider is owned by copied_streams  
859 - CopiedStreamDataProvider* copied_stream_data_provider{nullptr}; 901 + std::shared_ptr<CopiedStreamDataProvider> copied_stream_data_provider;
860 bool reconstructed_xref{false}; 902 bool reconstructed_xref{false};
861 bool in_read_xref_stream{false}; 903 bool in_read_xref_stream{false};
862 bool fixed_dangling_refs{false}; 904 bool fixed_dangling_refs{false};
qpdf/qpdf.testcov
@@ -98,16 +98,12 @@ qpdf-c called qpdf_set_r4_encryption_parameters_insecure 0 @@ -98,16 +98,12 @@ qpdf-c called qpdf_set_r4_encryption_parameters_insecure 0
98 qpdf-c called qpdf_set_static_aes_IV 0 98 qpdf-c called qpdf_set_static_aes_IV 0
99 qpdf-c called qpdf_has_error 0 99 qpdf-c called qpdf_has_error 0
100 qpdf-c called qpdf_get_qpdf_version 0 100 qpdf-c called qpdf_get_qpdf_version 0
101 -QPDF_Stream pipe original stream data 0  
102 QPDF_Stream pipe replaced stream data 0 101 QPDF_Stream pipe replaced stream data 0
103 QPDF_Stream provider length mismatch 0 102 QPDF_Stream provider length mismatch 0
104 QPDFObjectHandle newStream 0 103 QPDFObjectHandle newStream 0
105 QPDFObjectHandle newStream with data 0 104 QPDFObjectHandle newStream with data 0
106 -QPDF_Stream pipe no stream data 0  
107 QPDFObjectHandle prepend page contents 0 105 QPDFObjectHandle prepend page contents 0
108 QPDFObjectHandle append page contents 0 106 QPDFObjectHandle append page contents 0
109 -QPDF_Stream getRawStreamData 0  
110 -QPDF_Stream getStreamData 0  
111 qpdf-c called qpdf_read_memory 0 107 qpdf-c called qpdf_read_memory 0
112 QPDF stream with CRNL 0 108 QPDF stream with CRNL 0
113 QPDFWriter copy encrypt metadata 1 109 QPDFWriter copy encrypt metadata 1
@@ -128,13 +124,6 @@ QPDFObjectHandle newStream with string 0 @@ -128,13 +124,6 @@ QPDFObjectHandle newStream with string 0
128 QPDF_Stream provider length not provided 0 124 QPDF_Stream provider length not provided 0
129 QPDF_Stream unknown stream length 0 125 QPDF_Stream unknown stream length 0
130 QPDF replaceReserved 0 126 QPDF replaceReserved 0
131 -QPDF copyForeign direct 0  
132 -QPDF copyForeign not foreign 0  
133 -QPDF replace indirect 0  
134 -QPDF replace array 0  
135 -QPDF replace dictionary 0  
136 -QPDF replace stream 0  
137 -QPDF replace foreign indirect with null 0  
138 QPDFWriter copy use_aes 1 127 QPDFWriter copy use_aes 1
139 QPDFParser indirect without context 0 128 QPDFParser indirect without context 0
140 QPDFObjectHandle trailing data in parse 0 129 QPDFObjectHandle trailing data in parse 0
@@ -256,9 +245,6 @@ QPDFJob image optimize no pipeline 0 @@ -256,9 +245,6 @@ QPDFJob image optimize no pipeline 0
256 QPDFJob image optimize no shrink 0 245 QPDFJob image optimize no shrink 0
257 QPDFJob image optimize too small 0 246 QPDFJob image optimize too small 0
258 QPDF pipe foreign encrypted stream 0 247 QPDF pipe foreign encrypted stream 0
259 -QPDF copy foreign stream with provider 0  
260 -QPDF copy foreign stream with buffer 0  
261 -QPDF immediate copy stream data 0  
262 QPDFJob copy same page more than once 1 248 QPDFJob copy same page more than once 1
263 QPDFPageObjectHelper bad token finding names 0 249 QPDFPageObjectHelper bad token finding names 0
264 QPDFJob password mode bytes 0 250 QPDFJob password mode bytes 0