Commit a5ec9dc0f1798f38605bdfd0d27dc793ead474be

Authored by m-holger
Committed by GitHub
2 parents f7e07240 737c190d

Merge pull request #1494 from m-holger/pl_stack

Refactor the QPDFWriter pipeline stack
include/qpdf/QPDFWriter.hh
@@ -458,28 +458,6 @@ class QPDFWriter @@ -458,28 +458,6 @@ class QPDFWriter
458 458
459 enum trailer_e { t_normal, t_lin_first, t_lin_second }; 459 enum trailer_e { t_normal, t_lin_first, t_lin_second };
460 460
461 - // An reference to a PipelinePopper instance is passed into activatePipelineStack. When the  
462 - // PipelinePopper goes out of scope, the pipeline stack is popped. PipelinePopper's destructor  
463 - // calls finish on the current pipeline and pops the pipeline stack until the top of stack is a  
464 - // previous active top of stack, and restores the pipeline to that point. It deletes any  
465 - // pipelines that it pops. If the bp argument is non-null and any of the stack items are of type  
466 - // Pl_Buffer, the buffer is retrieved.  
467 - class PipelinePopper  
468 - {  
469 - friend class QPDFWriter;  
470 -  
471 - public:  
472 - PipelinePopper(QPDFWriter* qw) :  
473 - qw(qw)  
474 - {  
475 - }  
476 - ~PipelinePopper();  
477 -  
478 - private:  
479 - QPDFWriter* qw{nullptr};  
480 - unsigned long stack_id{0};  
481 - };  
482 -  
483 unsigned int bytesNeeded(long long n); 461 unsigned int bytesNeeded(long long n);
484 void writeBinary(unsigned long long val, unsigned int bytes); 462 void writeBinary(unsigned long long val, unsigned int bytes);
485 QPDFWriter& write(std::string_view str); 463 QPDFWriter& write(std::string_view str);
@@ -487,6 +465,7 @@ class QPDFWriter @@ -487,6 +465,7 @@ class QPDFWriter
487 QPDFWriter& write(std::integral auto val); 465 QPDFWriter& write(std::integral auto val);
488 QPDFWriter& write_name(std::string const& str); 466 QPDFWriter& write_name(std::string const& str);
489 QPDFWriter& write_string(std::string const& str, bool force_binary = false); 467 QPDFWriter& write_string(std::string const& str, bool force_binary = false);
  468 + QPDFWriter& write_encrypted(std::string_view str);
490 469
491 template <typename... Args> 470 template <typename... Args>
492 QPDFWriter& write_qdf(Args&&... args); 471 QPDFWriter& write_qdf(Args&&... args);
@@ -588,19 +567,8 @@ class QPDFWriter @@ -588,19 +567,8 @@ class QPDFWriter
588 // When filtering subsections, push additional pipelines to the stack. When ready to switch, 567 // When filtering subsections, push additional pipelines to the stack. When ready to switch,
589 // activate the pipeline stack. When the passed in PipelinePopper goes out of scope, the stack 568 // activate the pipeline stack. When the passed in PipelinePopper goes out of scope, the stack
590 // is popped. 569 // is popped.
591 - Pipeline* pushPipeline(Pipeline*);  
592 - void activatePipelineStack(PipelinePopper& pp, std::string& str);  
593 - void activatePipelineStack(PipelinePopper& pp, std::unique_ptr<qpdf::pl::Link> link);  
594 - void activatePipelineStack(  
595 - PipelinePopper& pp,  
596 - bool discard = false,  
597 - std::string* str = nullptr,  
598 - std::unique_ptr<qpdf::pl::Link> link = nullptr);  
599 - void initializePipelineStack(Pipeline*);  
600 570
601 void adjustAESStreamLength(size_t& length); 571 void adjustAESStreamLength(size_t& length);
602 - void pushEncryptionFilter(PipelinePopper&);  
603 - void pushMD5Pipeline(PipelinePopper&);  
604 void computeDeterministicIDData(); 572 void computeDeterministicIDData();
605 573
606 class Members; 574 class Members;
libqpdf/QPDFWriter.cc
@@ -47,7 +47,217 @@ QPDFWriter::FunctionProgressReporter::~FunctionProgressReporter() // NOLINT @@ -47,7 +47,217 @@ QPDFWriter::FunctionProgressReporter::~FunctionProgressReporter() // NOLINT
47 void 47 void
48 QPDFWriter::FunctionProgressReporter::reportProgress(int progress) 48 QPDFWriter::FunctionProgressReporter::reportProgress(int progress)
49 { 49 {
50 - this->handler(progress); 50 + handler(progress);
  51 +}
  52 +
  53 +namespace
  54 +{
  55 + class Pl_stack
  56 + {
  57 + // A pipeline Popper is normally returned by Pl_stack::activate, or, if necessary, a
  58 + // reference to a Popper instance can be passed into activate. When the Popper goes out of
  59 + // scope, the pipeline stack is popped. This causes finish to be called on the current
  60 + // pipeline and the pipeline stack to be popped until the top of stack is a previous active
  61 + // top of stack and restores the pipeline to that point. It deletes any pipelines that it
  62 + // pops.
  63 + class Popper
  64 + {
  65 + friend class Pl_stack;
  66 +
  67 + public:
  68 + Popper() = default;
  69 + Popper(Popper const&) = delete;
  70 + Popper(Popper&& other) noexcept
  71 + {
  72 + // For MSVC, default pops the stack
  73 + if (this != &other) {
  74 + stack = other.stack;
  75 + stack_id = other.stack_id;
  76 + other.stack = nullptr;
  77 + other.stack_id = 0;
  78 + };
  79 + }
  80 + Popper& operator=(Popper const&) = delete;
  81 + Popper&
  82 + operator=(Popper&& other) noexcept
  83 + {
  84 + // For MSVC, default pops the stack
  85 + if (this != &other) {
  86 + stack = other.stack;
  87 + stack_id = other.stack_id;
  88 + other.stack = nullptr;
  89 + other.stack_id = 0;
  90 + };
  91 + return *this;
  92 + }
  93 +
  94 + ~Popper();
  95 +
  96 + // Manually pop pipeline from the pipeline stack.
  97 + void pop();
  98 +
  99 + private:
  100 + Popper(Pl_stack& stack) :
  101 + stack(&stack)
  102 + {
  103 + }
  104 +
  105 + Pl_stack* stack{nullptr};
  106 + unsigned long stack_id{0};
  107 + };
  108 +
  109 + public:
  110 + Pl_stack(pl::Count*& top) :
  111 + top(top)
  112 + {
  113 + }
  114 +
  115 + Popper
  116 + popper()
  117 + {
  118 + return {*this};
  119 + }
  120 +
  121 + void
  122 + initialize(Pipeline* p)
  123 + {
  124 + auto c = std::make_unique<pl::Count>(++last_id, p);
  125 + top = c.get();
  126 + stack.emplace_back(std::move(c));
  127 + }
  128 +
  129 + Popper
  130 + activate(std::string& str)
  131 + {
  132 + Popper pp{*this};
  133 + activate(pp, str);
  134 + return pp;
  135 + }
  136 +
  137 + void
  138 + activate(Popper& pp, std::string& str)
  139 + {
  140 + activate(pp, false, &str, nullptr);
  141 + }
  142 +
  143 + void
  144 + activate(Popper& pp, std::unique_ptr<pl::Link> link)
  145 + {
  146 + count_buffer.clear();
  147 + activate(pp, false, &count_buffer, std::move(link));
  148 + }
  149 +
  150 + Popper
  151 + activate(
  152 + bool discard = false,
  153 + std::string* str = nullptr,
  154 + std::unique_ptr<pl::Link> link = nullptr)
  155 + {
  156 + Popper pp{*this};
  157 + activate(pp, discard, str, std::move(link));
  158 + return pp;
  159 + }
  160 +
  161 + void
  162 + activate(
  163 + Popper& pp,
  164 + bool discard = false,
  165 + std::string* str = nullptr,
  166 + std::unique_ptr<pl::Link> link = nullptr)
  167 + {
  168 + std::unique_ptr<pl::Count> c;
  169 + if (link) {
  170 + c = std::make_unique<pl::Count>(++last_id, count_buffer, std::move(link));
  171 + } else if (discard) {
  172 + c = std::make_unique<pl::Count>(++last_id, nullptr);
  173 + } else if (!str) {
  174 + c = std::make_unique<pl::Count>(++last_id, top);
  175 + } else {
  176 + c = std::make_unique<pl::Count>(++last_id, *str);
  177 + }
  178 + pp.stack_id = last_id;
  179 + top = c.get();
  180 + stack.emplace_back(std::move(c));
  181 + }
  182 + void
  183 + activate_md5(Popper& pp)
  184 + {
  185 + qpdf_assert_debug(!md5_pipeline);
  186 + qpdf_assert_debug(md5_id == 0);
  187 + qpdf_assert_debug(top->getCount() == 0);
  188 + md5_pipeline = std::make_unique<Pl_MD5>("qpdf md5", top);
  189 + md5_pipeline->persistAcrossFinish(true);
  190 + // Special case code in pop clears m->md5_pipeline upon deletion.
  191 + auto c = std::make_unique<pl::Count>(++last_id, md5_pipeline.get());
  192 + pp.stack_id = last_id;
  193 + md5_id = last_id;
  194 + top = c.get();
  195 + stack.emplace_back(std::move(c));
  196 + }
  197 +
  198 + // Return the hex digest and disable the MD5 pipeline.
  199 + std::string
  200 + hex_digest()
  201 + {
  202 + qpdf_assert_debug(md5_pipeline);
  203 + auto digest = md5_pipeline->getHexDigest();
  204 + md5_pipeline->enable(false);
  205 + return digest;
  206 + }
  207 +
  208 + void
  209 + clear_buffer()
  210 + {
  211 + count_buffer.clear();
  212 + }
  213 +
  214 + private:
  215 + void
  216 + pop(unsigned long stack_id)
  217 + {
  218 + if (!stack_id) {
  219 + return;
  220 + }
  221 + qpdf_assert_debug(stack.size() >= 2);
  222 + top->finish();
  223 + qpdf_assert_debug(stack.back().get() == top);
  224 + // It used to be possible for this assertion to fail if writeLinearized exits by
  225 + // exception when deterministic ID. There are no longer any cases in which two
  226 + // dynamically allocated pipeline Popper objects ever exist at the same time, so the
  227 + // assertion will fail if they get popped out of order from automatic destruction.
  228 + qpdf_assert_debug(top->id() == stack_id);
  229 + if (stack_id == md5_id) {
  230 + md5_pipeline = nullptr;
  231 + md5_id = 0;
  232 + }
  233 + stack.pop_back();
  234 + top = stack.back().get();
  235 + }
  236 +
  237 + std::vector<std::unique_ptr<pl::Count>> stack;
  238 + pl::Count*& top;
  239 + std::unique_ptr<Pl_MD5> md5_pipeline{nullptr};
  240 + unsigned long last_id{0};
  241 + unsigned long md5_id{0};
  242 + std::string count_buffer;
  243 + };
  244 +} // namespace
  245 +
  246 +Pl_stack::Popper::~Popper()
  247 +{
  248 + if (stack) {
  249 + stack->pop(stack_id);
  250 + }
  251 +}
  252 +
  253 +void
  254 +Pl_stack::Popper::pop()
  255 +{
  256 + if (stack) {
  257 + stack->pop(stack_id);
  258 + }
  259 + stack_id = 0;
  260 + stack = nullptr;
51 } 261 }
52 262
53 class QPDFWriter::Members 263 class QPDFWriter::Members
@@ -66,7 +276,7 @@ class QPDFWriter::Members @@ -66,7 +276,7 @@ class QPDFWriter::Members
66 char const* filename{"unspecified"}; 276 char const* filename{"unspecified"};
67 FILE* file{nullptr}; 277 FILE* file{nullptr};
68 bool close_file{false}; 278 bool close_file{false};
69 - Pl_Buffer* buffer_pipeline{nullptr}; 279 + std::unique_ptr<Pl_Buffer> buffer_pipeline{nullptr};
70 Buffer* output_buffer{nullptr}; 280 Buffer* output_buffer{nullptr};
71 bool normalize_content_set{false}; 281 bool normalize_content_set{false};
72 bool normalize_content{false}; 282 bool normalize_content{false};
@@ -101,7 +311,7 @@ class QPDFWriter::Members @@ -101,7 +311,7 @@ class QPDFWriter::Members
101 std::string extra_header_text; 311 std::string extra_header_text;
102 int encryption_dict_objid{0}; 312 int encryption_dict_objid{0};
103 std::string cur_data_key; 313 std::string cur_data_key;
104 - std::list<std::shared_ptr<Pipeline>> to_delete; 314 + std::unique_ptr<Pipeline> file_pl;
105 qpdf::pl::Count* pipeline{nullptr}; 315 qpdf::pl::Count* pipeline{nullptr};
106 std::vector<QPDFObjectHandle> object_queue; 316 std::vector<QPDFObjectHandle> object_queue;
107 size_t object_queue_front{0}; 317 size_t object_queue_front{0};
@@ -116,11 +326,8 @@ class QPDFWriter::Members @@ -116,11 +326,8 @@ class QPDFWriter::Members
116 std::map<QPDFObjGen, int> page_object_to_seq; 326 std::map<QPDFObjGen, int> page_object_to_seq;
117 std::map<QPDFObjGen, int> contents_to_page_seq; 327 std::map<QPDFObjGen, int> contents_to_page_seq;
118 std::map<int, std::vector<QPDFObjGen>> object_stream_to_objects; 328 std::map<int, std::vector<QPDFObjGen>> object_stream_to_objects;
119 - std::vector<Pipeline*> pipeline_stack;  
120 - unsigned long next_stack_id{2};  
121 - std::string count_buffer; 329 + Pl_stack pipeline_stack;
122 bool deterministic_id{false}; 330 bool deterministic_id{false};
123 - Pl_MD5* md5_pipeline{nullptr};  
124 std::string deterministic_id_data; 331 std::string deterministic_id_data;
125 bool did_write_setup{false}; 332 bool did_write_setup{false};
126 333
@@ -136,7 +343,8 @@ class QPDFWriter::Members @@ -136,7 +343,8 @@ class QPDFWriter::Members
136 343
137 QPDFWriter::Members::Members(QPDF& pdf) : 344 QPDFWriter::Members::Members(QPDF& pdf) :
138 pdf(pdf), 345 pdf(pdf),
139 - root_og(pdf.getRoot().getObjGen().isIndirect() ? pdf.getRoot().getObjGen() : QPDFObjGen(-1, 0)) 346 + root_og(pdf.getRoot().getObjGen().isIndirect() ? pdf.getRoot().getObjGen() : QPDFObjGen(-1, 0)),
  347 + pipeline_stack(pipeline)
140 { 348 {
141 } 349 }
142 350
@@ -190,18 +398,16 @@ QPDFWriter::setOutputFile(char const* description, FILE* file, bool close_file) @@ -190,18 +398,16 @@ QPDFWriter::setOutputFile(char const* description, FILE* file, bool close_file)
190 m->filename = description; 398 m->filename = description;
191 m->file = file; 399 m->file = file;
192 m->close_file = close_file; 400 m->close_file = close_file;
193 - std::shared_ptr<Pipeline> p = std::make_shared<Pl_StdioFile>("qpdf output", file);  
194 - m->to_delete.push_back(p);  
195 - initializePipelineStack(p.get()); 401 + m->file_pl = std::make_unique<Pl_StdioFile>("qpdf output", file);
  402 + m->pipeline_stack.initialize(m->file_pl.get());
196 } 403 }
197 404
198 void 405 void
199 QPDFWriter::setOutputMemory() 406 QPDFWriter::setOutputMemory()
200 { 407 {
201 m->filename = "memory buffer"; 408 m->filename = "memory buffer";
202 - m->buffer_pipeline = new Pl_Buffer("qpdf output");  
203 - m->to_delete.push_back(std::shared_ptr<Pipeline>(m->buffer_pipeline));  
204 - initializePipelineStack(m->buffer_pipeline); 409 + m->buffer_pipeline = std::make_unique<Pl_Buffer>("qpdf output");
  410 + m->pipeline_stack.initialize(m->buffer_pipeline.get());
205 } 411 }
206 412
207 Buffer* 413 Buffer*
@@ -222,7 +428,7 @@ void @@ -222,7 +428,7 @@ void
222 QPDFWriter::setOutputPipeline(Pipeline* p) 428 QPDFWriter::setOutputPipeline(Pipeline* p)
223 { 429 {
224 m->filename = "custom pipeline"; 430 m->filename = "custom pipeline";
225 - initializePipelineStack(p); 431 + m->pipeline_stack.initialize(p);
226 } 432 }
227 433
228 void 434 void
@@ -887,81 +1093,6 @@ QPDFWriter::write_no_qdf(Args&amp;&amp;... args) @@ -887,81 +1093,6 @@ QPDFWriter::write_no_qdf(Args&amp;&amp;... args)
887 return *this; 1093 return *this;
888 } 1094 }
889 1095
890 -Pipeline*  
891 -QPDFWriter::pushPipeline(Pipeline* p)  
892 -{  
893 - qpdf_assert_debug(!dynamic_cast<pl::Count*>(p));  
894 - m->pipeline_stack.emplace_back(p);  
895 - return p;  
896 -}  
897 -  
898 -void  
899 -QPDFWriter::initializePipelineStack(Pipeline* p)  
900 -{  
901 - m->pipeline = new pl::Count(1, p);  
902 - m->to_delete.emplace_back(std::shared_ptr<Pipeline>(m->pipeline));  
903 - m->pipeline_stack.emplace_back(m->pipeline);  
904 -}  
905 -  
906 -void  
907 -QPDFWriter::activatePipelineStack(PipelinePopper& pp, std::string& str)  
908 -{  
909 - activatePipelineStack(pp, false, &str, nullptr);  
910 -}  
911 -  
912 -void  
913 -QPDFWriter::activatePipelineStack(PipelinePopper& pp, std::unique_ptr<pl::Link> link)  
914 -{  
915 - m->count_buffer.clear();  
916 - activatePipelineStack(pp, false, &m->count_buffer, std::move(link));  
917 -}  
918 -  
919 -void  
920 -QPDFWriter::activatePipelineStack(  
921 - PipelinePopper& pp, bool discard, std::string* str, std::unique_ptr<pl::Link> link)  
922 -{  
923 - pl::Count* c;  
924 - if (link) {  
925 - c = new pl::Count(m->next_stack_id, m->count_buffer, std::move(link));  
926 - } else if (discard) {  
927 - c = new pl::Count(m->next_stack_id, nullptr);  
928 - } else if (!str) {  
929 - c = new pl::Count(m->next_stack_id, m->pipeline_stack.back());  
930 - } else {  
931 - c = new pl::Count(m->next_stack_id, *str);  
932 - }  
933 - pp.stack_id = m->next_stack_id;  
934 - m->pipeline_stack.emplace_back(c);  
935 - m->pipeline = c;  
936 - ++m->next_stack_id;  
937 -}  
938 -  
939 -QPDFWriter::PipelinePopper::~PipelinePopper()  
940 -{  
941 - if (!stack_id) {  
942 - return;  
943 - }  
944 - qpdf_assert_debug(qw->m->pipeline_stack.size() >= 2);  
945 - qw->m->pipeline->finish();  
946 - qpdf_assert_debug(dynamic_cast<pl::Count*>(qw->m->pipeline_stack.back()) == qw->m->pipeline);  
947 - // It might be possible for this assertion to fail if writeLinearized exits by exception when  
948 - // deterministic ID, but I don't think so. As of this writing, this is the only case in which  
949 - // two dynamically allocated PipelinePopper objects ever exist at the same time, so the  
950 - // assertion will fail if they get popped out of order from automatic destruction.  
951 - qpdf_assert_debug(qw->m->pipeline->id() == stack_id);  
952 - delete qw->m->pipeline_stack.back();  
953 - qw->m->pipeline_stack.pop_back();  
954 - while (!dynamic_cast<pl::Count*>(qw->m->pipeline_stack.back())) {  
955 - Pipeline* p = qw->m->pipeline_stack.back();  
956 - if (dynamic_cast<Pl_MD5*>(p) == qw->m->md5_pipeline) {  
957 - qw->m->md5_pipeline = nullptr;  
958 - }  
959 - qw->m->pipeline_stack.pop_back();  
960 - delete p;  
961 - }  
962 - qw->m->pipeline = dynamic_cast<pl::Count*>(qw->m->pipeline_stack.back());  
963 -}  
964 -  
965 void 1096 void
966 QPDFWriter::adjustAESStreamLength(size_t& length) 1097 QPDFWriter::adjustAESStreamLength(size_t& length)
967 { 1098 {
@@ -972,57 +1103,39 @@ QPDFWriter::adjustAESStreamLength(size_t&amp; length) @@ -972,57 +1103,39 @@ QPDFWriter::adjustAESStreamLength(size_t&amp; length)
972 } 1103 }
973 } 1104 }
974 1105
975 -void  
976 -QPDFWriter::pushEncryptionFilter(PipelinePopper& pp)  
977 -{  
978 - if (m->encryption && !m->cur_data_key.empty()) {  
979 - Pipeline* p = nullptr;  
980 - if (m->encrypt_use_aes) {  
981 - p = new Pl_AES_PDF(  
982 - "aes stream encryption",  
983 - m->pipeline, 1106 +QPDFWriter&
  1107 +QPDFWriter::write_encrypted(std::string_view str)
  1108 +{
  1109 + if (!(m->encryption && !m->cur_data_key.empty())) {
  1110 + write(str);
  1111 + } else if (m->encrypt_use_aes) {
  1112 + write(
  1113 + pl::pipe<Pl_AES_PDF>(
  1114 + str,
984 true, 1115 true,
985 QUtil::unsigned_char_pointer(m->cur_data_key), 1116 QUtil::unsigned_char_pointer(m->cur_data_key),
986 - m->cur_data_key.length());  
987 - } else {  
988 - p = new Pl_RC4(  
989 - "rc4 stream encryption",  
990 - m->pipeline, 1117 + m->cur_data_key.length()));
  1118 + } else {
  1119 + write(
  1120 + pl::pipe<Pl_RC4>(
  1121 + str,
991 QUtil::unsigned_char_pointer(m->cur_data_key), 1122 QUtil::unsigned_char_pointer(m->cur_data_key),
992 - QIntC::to_int(m->cur_data_key.length()));  
993 - }  
994 - pushPipeline(p); 1123 + QIntC::to_int(m->cur_data_key.length())));
995 } 1124 }
996 - // Must call this unconditionally so we can call popPipelineStack to balance  
997 - // pushEncryptionFilter().  
998 - activatePipelineStack(pp); 1125 +
  1126 + return *this;
999 } 1127 }
1000 1128
1001 void 1129 void
1002 -QPDFWriter::pushMD5Pipeline(PipelinePopper& pp) 1130 +QPDFWriter::computeDeterministicIDData()
1003 { 1131 {
1004 if (!m->id2.empty()) { 1132 if (!m->id2.empty()) {
1005 // Can't happen in the code 1133 // Can't happen in the code
1006 throw std::logic_error( 1134 throw std::logic_error(
1007 "Deterministic ID computation enabled after ID generation has already occurred."); 1135 "Deterministic ID computation enabled after ID generation has already occurred.");
1008 } 1136 }
1009 - qpdf_assert_debug(m->deterministic_id);  
1010 - qpdf_assert_debug(m->md5_pipeline == nullptr);  
1011 - qpdf_assert_debug(m->pipeline->getCount() == 0);  
1012 - m->md5_pipeline = new Pl_MD5("qpdf md5", m->pipeline);  
1013 - m->md5_pipeline->persistAcrossFinish(true);  
1014 - // Special case code in popPipelineStack clears m->md5_pipeline upon deletion.  
1015 - pushPipeline(m->md5_pipeline);  
1016 - activatePipelineStack(pp);  
1017 -}  
1018 -  
1019 -void  
1020 -QPDFWriter::computeDeterministicIDData()  
1021 -{  
1022 - qpdf_assert_debug(m->md5_pipeline != nullptr);  
1023 qpdf_assert_debug(m->deterministic_id_data.empty()); 1137 qpdf_assert_debug(m->deterministic_id_data.empty());
1024 - m->deterministic_id_data = m->md5_pipeline->getHexDigest();  
1025 - m->md5_pipeline->enable(false); 1138 + m->deterministic_id_data = m->pipeline_stack.hex_digest();
1026 } 1139 }
1027 1140
1028 int 1141 int
@@ -1271,12 +1384,9 @@ QPDFWriter::willFilterStream( @@ -1271,12 +1384,9 @@ QPDFWriter::willFilterStream(
1271 1384
1272 bool filtered = false; 1385 bool filtered = false;
1273 for (bool first_attempt: {true, false}) { 1386 for (bool first_attempt: {true, false}) {
1274 - PipelinePopper pp_stream_data(this);  
1275 - if (stream_data != nullptr) {  
1276 - activatePipelineStack(pp_stream_data, *stream_data);  
1277 - } else {  
1278 - activatePipelineStack(pp_stream_data, true);  
1279 - } 1387 + auto pp_stream_data = stream_data ? m->pipeline_stack.activate(*stream_data)
  1388 + : m->pipeline_stack.activate(true);
  1389 +
1280 try { 1390 try {
1281 filtered = stream.pipeStreamData( 1391 filtered = stream.pipeStreamData(
1282 m->pipeline, 1392 m->pipeline,
@@ -1534,19 +1644,9 @@ QPDFWriter::unparseObject( @@ -1534,19 +1644,9 @@ QPDFWriter::unparseObject(
1534 adjustAESStreamLength(m->cur_stream_length); 1644 adjustAESStreamLength(m->cur_stream_length);
1535 unparseObject(stream_dict, 0, flags, m->cur_stream_length, compress_stream); 1645 unparseObject(stream_dict, 0, flags, m->cur_stream_length, compress_stream);
1536 char last_char = stream_data.empty() ? '\0' : stream_data.back(); 1646 char last_char = stream_data.empty() ? '\0' : stream_data.back();
1537 - write("\nstream\n");  
1538 - {  
1539 - PipelinePopper pp_enc(this);  
1540 - pushEncryptionFilter(pp_enc);  
1541 - write(stream_data);  
1542 - }  
1543 -  
1544 - if ((m->added_newline =  
1545 - m->newline_before_endstream || (m->qdf_mode && last_char != '\n'))) {  
1546 - write("\nendstream");  
1547 - } else {  
1548 - write("endstream");  
1549 - } 1647 + write("\nstream\n").write_encrypted(stream_data);
  1648 + m->added_newline = m->newline_before_endstream || (m->qdf_mode && last_char != '\n');
  1649 + write(m->added_newline ? "\nendstream" : "endstream");
1550 } else if (tc == ::ot_string) { 1650 } else if (tc == ::ot_string) {
1551 std::string val; 1651 std::string val;
1552 if (m->encryption && !(flags & f_in_ostream) && !(flags & f_no_encryption) && 1652 if (m->encryption && !(flags & f_in_ostream) && !(flags & f_no_encryption) &&
@@ -1626,8 +1726,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) @@ -1626,8 +1726,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1626 const bool compressed = m->compress_streams && !m->qdf_mode; 1726 const bool compressed = m->compress_streams && !m->qdf_mode;
1627 { 1727 {
1628 // Pass 1 1728 // Pass 1
1629 - PipelinePopper pp_ostream_pass1(this);  
1630 - activatePipelineStack(pp_ostream_pass1, stream_buffer_pass1); 1729 + auto pp_ostream_pass1 = m->pipeline_stack.activate(stream_buffer_pass1);
1631 1730
1632 int count = -1; 1731 int count = -1;
1633 for (auto const& obj: m->object_stream_to_objects[old_id]) { 1732 for (auto const& obj: m->object_stream_to_objects[old_id]) {
@@ -1669,7 +1768,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) @@ -1669,7 +1768,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1669 } 1768 }
1670 } 1769 }
1671 { 1770 {
1672 - PipelinePopper pp_ostream(this); 1771 + auto pp_ostream = m->pipeline_stack.popper();
1673 // Adjust offsets to skip over comment before first object 1772 // Adjust offsets to skip over comment before first object
1674 first = offsets.at(0); 1773 first = offsets.at(0);
1675 for (auto& iter: offsets) { 1774 for (auto& iter: offsets) {
@@ -1678,20 +1777,19 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) @@ -1678,20 +1777,19 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1678 1777
1679 // Take one pass at writing pairs of numbers so we can get their size information 1778 // Take one pass at writing pairs of numbers so we can get their size information
1680 { 1779 {
1681 - PipelinePopper pp_discard(this);  
1682 - activatePipelineStack(pp_discard, true); 1780 + auto pp_discard = m->pipeline_stack.activate(true);
1683 writeObjectStreamOffsets(offsets, first_obj); 1781 writeObjectStreamOffsets(offsets, first_obj);
1684 first += m->pipeline->getCount(); 1782 first += m->pipeline->getCount();
1685 } 1783 }
1686 1784
1687 // Set up a stream to write the stream data into a buffer. 1785 // Set up a stream to write the stream data into a buffer.
1688 if (compressed) { 1786 if (compressed) {
1689 - activatePipelineStack( 1787 + m->pipeline_stack.activate(
1690 pp_ostream, 1788 pp_ostream,
1691 pl::create<Pl_Flate>( 1789 pl::create<Pl_Flate>(
1692 pl::create<pl::String>(stream_buffer_pass2), Pl_Flate::a_deflate)); 1790 pl::create<pl::String>(stream_buffer_pass2), Pl_Flate::a_deflate));
1693 } else { 1791 } else {
1694 - activatePipelineStack(pp_ostream, stream_buffer_pass2); 1792 + m->pipeline_stack.activate(pp_ostream, stream_buffer_pass2);
1695 } 1793 }
1696 writeObjectStreamOffsets(offsets, first_obj); 1794 writeObjectStreamOffsets(offsets, first_obj);
1697 write(stream_buffer_pass1); 1795 write(stream_buffer_pass1);
@@ -1720,19 +1818,11 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) @@ -1720,19 +1818,11 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1720 unparseChild(extends, 1, f_in_ostream); 1818 unparseChild(extends, 1, f_in_ostream);
1721 } 1819 }
1722 } 1820 }
1723 - write_qdf("\n").write_no_qdf(" ").write(">>\nstream\n"); 1821 + write_qdf("\n").write_no_qdf(" ").write(">>\nstream\n").write_encrypted(stream_buffer_pass2);
1724 if (m->encryption) { 1822 if (m->encryption) {
1725 QTC::TC("qpdf", "QPDFWriter encrypt object stream"); 1823 QTC::TC("qpdf", "QPDFWriter encrypt object stream");
1726 } 1824 }
1727 - {  
1728 - PipelinePopper pp_enc(this);  
1729 - pushEncryptionFilter(pp_enc);  
1730 - write(stream_buffer_pass2);  
1731 - }  
1732 - if (m->newline_before_endstream) {  
1733 - write("\n");  
1734 - }  
1735 - write("endstream"); 1825 + write(m->newline_before_endstream ? "\nendstream" : "endstream");
1736 m->cur_data_key.clear(); 1826 m->cur_data_key.clear();
1737 closeObject(new_stream_id); 1827 closeObject(new_stream_id);
1738 } 1828 }
@@ -2341,22 +2431,13 @@ QPDFWriter::writeHintStream(int hint_id) @@ -2341,22 +2431,13 @@ QPDFWriter::writeHintStream(int hint_id)
2341 } 2431 }
2342 adjustAESStreamLength(hlen); 2432 adjustAESStreamLength(hlen);
2343 write(" /Length ").write(hlen); 2433 write(" /Length ").write(hlen);
2344 - write(" >>\nstream\n"); 2434 + write(" >>\nstream\n").write_encrypted(hint_buffer);
2345 2435
2346 if (m->encryption) { 2436 if (m->encryption) {
2347 QTC::TC("qpdf", "QPDFWriter encrypted hint stream"); 2437 QTC::TC("qpdf", "QPDFWriter encrypted hint stream");
2348 } 2438 }
2349 - char last_char = hint_buffer.empty() ? '\0' : hint_buffer.back();  
2350 - {  
2351 - PipelinePopper pp_enc(this);  
2352 - pushEncryptionFilter(pp_enc);  
2353 - write(hint_buffer);  
2354 - }  
2355 2439
2356 - if (last_char != '\n') {  
2357 - write("\n");  
2358 - }  
2359 - write("endstream"); 2440 + write(hint_buffer.empty() || hint_buffer.back() != '\n' ? "\nendstream" : "endstream");
2360 closeObject(hint_id); 2441 closeObject(hint_id);
2361 } 2442 }
2362 2443
@@ -2447,19 +2528,19 @@ QPDFWriter::writeXRefStream( @@ -2447,19 +2528,19 @@ QPDFWriter::writeXRefStream(
2447 std::string xref_data; 2528 std::string xref_data;
2448 const bool compressed = m->compress_streams && !m->qdf_mode; 2529 const bool compressed = m->compress_streams && !m->qdf_mode;
2449 { 2530 {
2450 - PipelinePopper pp_xref(this); 2531 + auto pp_xref = m->pipeline_stack.popper();
2451 if (compressed) { 2532 if (compressed) {
2452 - m->count_buffer.clear(); 2533 + m->pipeline_stack.clear_buffer();
2453 auto link = pl::create<pl::String>(xref_data); 2534 auto link = pl::create<pl::String>(xref_data);
2454 if (!skip_compression) { 2535 if (!skip_compression) {
2455 // Write the stream dictionary for compression but don't actually compress. This 2536 // Write the stream dictionary for compression but don't actually compress. This
2456 // helps us with computation of padding for pass 1 of linearization. 2537 // helps us with computation of padding for pass 1 of linearization.
2457 link = pl::create<Pl_Flate>(std::move(link), Pl_Flate::a_deflate); 2538 link = pl::create<Pl_Flate>(std::move(link), Pl_Flate::a_deflate);
2458 } 2539 }
2459 - activatePipelineStack( 2540 + m->pipeline_stack.activate(
2460 pp_xref, pl::create<Pl_PNGFilter>(std::move(link), Pl_PNGFilter::a_encode, esize)); 2541 pp_xref, pl::create<Pl_PNGFilter>(std::move(link), Pl_PNGFilter::a_encode, esize));
2461 } else { 2542 } else {
2462 - activatePipelineStack(pp_xref, xref_data); 2543 + m->pipeline_stack.activate(pp_xref, xref_data);
2463 } 2544 }
2464 2545
2465 for (int i = first; i <= last; ++i) { 2546 for (int i = first; i <= last; ++i) {
@@ -2654,19 +2735,22 @@ QPDFWriter::writeLinearized() @@ -2654,19 +2735,22 @@ QPDFWriter::writeLinearized()
2654 // Write file in two passes. Part numbers refer to PDF spec 1.4. 2735 // Write file in two passes. Part numbers refer to PDF spec 1.4.
2655 2736
2656 FILE* lin_pass1_file = nullptr; 2737 FILE* lin_pass1_file = nullptr;
2657 - auto pp_pass1 = std::make_unique<PipelinePopper>(this);  
2658 - auto pp_md5 = std::make_unique<PipelinePopper>(this); 2738 + auto pp_pass1 = m->pipeline_stack.popper();
  2739 + auto pp_md5 = m->pipeline_stack.popper();
2659 for (int pass: {1, 2}) { 2740 for (int pass: {1, 2}) {
2660 if (pass == 1) { 2741 if (pass == 1) {
2661 if (!m->lin_pass1_filename.empty()) { 2742 if (!m->lin_pass1_filename.empty()) {
2662 lin_pass1_file = QUtil::safe_fopen(m->lin_pass1_filename.c_str(), "wb"); 2743 lin_pass1_file = QUtil::safe_fopen(m->lin_pass1_filename.c_str(), "wb");
2663 - pushPipeline(new Pl_StdioFile("linearization pass1", lin_pass1_file));  
2664 - activatePipelineStack(*pp_pass1); 2744 + m->pipeline_stack.activate(
  2745 + pp_pass1,
  2746 + std::make_unique<pl::Link>(
  2747 + nullptr,
  2748 + std::make_unique<Pl_StdioFile>("linearization pass1", lin_pass1_file)));
2665 } else { 2749 } else {
2666 - activatePipelineStack(*pp_pass1, true); 2750 + m->pipeline_stack.activate(pp_pass1, true);
2667 } 2751 }
2668 if (m->deterministic_id) { 2752 if (m->deterministic_id) {
2669 - pushMD5Pipeline(*pp_md5); 2753 + m->pipeline_stack.activate_md5(pp_md5);
2670 } 2754 }
2671 } 2755 }
2672 2756
@@ -2841,21 +2925,19 @@ QPDFWriter::writeLinearized() @@ -2841,21 +2925,19 @@ QPDFWriter::writeLinearized()
2841 if (m->deterministic_id) { 2925 if (m->deterministic_id) {
2842 QTC::TC("qpdf", "QPDFWriter linearized deterministic ID", need_xref_stream ? 0 : 1); 2926 QTC::TC("qpdf", "QPDFWriter linearized deterministic ID", need_xref_stream ? 0 : 1);
2843 computeDeterministicIDData(); 2927 computeDeterministicIDData();
2844 - pp_md5 = nullptr;  
2845 - qpdf_assert_debug(m->md5_pipeline == nullptr); 2928 + pp_md5.pop();
2846 } 2929 }
2847 2930
2848 // Close first pass pipeline 2931 // Close first pass pipeline
2849 file_size = m->pipeline->getCount(); 2932 file_size = m->pipeline->getCount();
2850 - pp_pass1 = nullptr; 2933 + pp_pass1.pop();
2851 2934
2852 // Save hint offset since it will be set to zero by calling openObject. 2935 // Save hint offset since it will be set to zero by calling openObject.
2853 qpdf_offset_t hint_offset1 = m->new_obj[hint_id].xref.getOffset(); 2936 qpdf_offset_t hint_offset1 = m->new_obj[hint_id].xref.getOffset();
2854 2937
2855 // Write hint stream to a buffer 2938 // Write hint stream to a buffer
2856 { 2939 {
2857 - PipelinePopper pp_hint(this);  
2858 - activatePipelineStack(pp_hint, hint_buffer); 2940 + auto pp_hint = m->pipeline_stack.activate(hint_buffer);
2859 writeHintStream(hint_id); 2941 writeHintStream(hint_id);
2860 } 2942 }
2861 hint_length = QIntC::to_offset(hint_buffer.size()); 2943 hint_length = QIntC::to_offset(hint_buffer.size());
@@ -2973,9 +3055,9 @@ QPDFWriter::registerProgressReporter(std::shared_ptr&lt;ProgressReporter&gt; pr) @@ -2973,9 +3055,9 @@ QPDFWriter::registerProgressReporter(std::shared_ptr&lt;ProgressReporter&gt; pr)
2973 void 3055 void
2974 QPDFWriter::writeStandard() 3056 QPDFWriter::writeStandard()
2975 { 3057 {
2976 - auto pp_md5 = PipelinePopper(this); 3058 + auto pp_md5 = m->pipeline_stack.popper();
2977 if (m->deterministic_id) { 3059 if (m->deterministic_id) {
2978 - pushMD5Pipeline(pp_md5); 3060 + m->pipeline_stack.activate_md5(pp_md5);
2979 } 3061 }
2980 3062
2981 // Start writing 3063 // Start writing
libqpdf/qpdf/Pipeline_private.hh
@@ -173,6 +173,18 @@ namespace qpdf::pl @@ -173,6 +173,18 @@ namespace qpdf::pl
173 unsigned long id_{0}; 173 unsigned long id_{0};
174 bool pass_immediately_to_next{false}; 174 bool pass_immediately_to_next{false};
175 }; 175 };
  176 +
  177 + template <typename P, typename... Args>
  178 + std::string
  179 + pipe(std::string_view data, Args&&... args)
  180 + {
  181 + std::string result;
  182 + String s("", nullptr, result);
  183 + P pl("", &s, std::forward<Args>(args)...);
  184 + pl.write(reinterpret_cast<unsigned char const*>(data.data()), data.size());
  185 + pl.finish();
  186 + return result;
  187 + }
176 } // namespace qpdf::pl 188 } // namespace qpdf::pl
177 189
178 #endif // PIPELINE_PRIVATE_HH 190 #endif // PIPELINE_PRIVATE_HH