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 458  
459 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 461 unsigned int bytesNeeded(long long n);
484 462 void writeBinary(unsigned long long val, unsigned int bytes);
485 463 QPDFWriter& write(std::string_view str);
... ... @@ -487,6 +465,7 @@ class QPDFWriter
487 465 QPDFWriter& write(std::integral auto val);
488 466 QPDFWriter& write_name(std::string const& str);
489 467 QPDFWriter& write_string(std::string const& str, bool force_binary = false);
  468 + QPDFWriter& write_encrypted(std::string_view str);
490 469  
491 470 template <typename... Args>
492 471 QPDFWriter& write_qdf(Args&&... args);
... ... @@ -588,19 +567,8 @@ class QPDFWriter
588 567 // When filtering subsections, push additional pipelines to the stack. When ready to switch,
589 568 // activate the pipeline stack. When the passed in PipelinePopper goes out of scope, the stack
590 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 571 void adjustAESStreamLength(size_t& length);
602   - void pushEncryptionFilter(PipelinePopper&);
603   - void pushMD5Pipeline(PipelinePopper&);
604 572 void computeDeterministicIDData();
605 573  
606 574 class Members;
... ...
libqpdf/QPDFWriter.cc
... ... @@ -47,7 +47,217 @@ QPDFWriter::FunctionProgressReporter::~FunctionProgressReporter() // NOLINT
47 47 void
48 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 263 class QPDFWriter::Members
... ... @@ -66,7 +276,7 @@ class QPDFWriter::Members
66 276 char const* filename{"unspecified"};
67 277 FILE* file{nullptr};
68 278 bool close_file{false};
69   - Pl_Buffer* buffer_pipeline{nullptr};
  279 + std::unique_ptr<Pl_Buffer> buffer_pipeline{nullptr};
70 280 Buffer* output_buffer{nullptr};
71 281 bool normalize_content_set{false};
72 282 bool normalize_content{false};
... ... @@ -101,7 +311,7 @@ class QPDFWriter::Members
101 311 std::string extra_header_text;
102 312 int encryption_dict_objid{0};
103 313 std::string cur_data_key;
104   - std::list<std::shared_ptr<Pipeline>> to_delete;
  314 + std::unique_ptr<Pipeline> file_pl;
105 315 qpdf::pl::Count* pipeline{nullptr};
106 316 std::vector<QPDFObjectHandle> object_queue;
107 317 size_t object_queue_front{0};
... ... @@ -116,11 +326,8 @@ class QPDFWriter::Members
116 326 std::map<QPDFObjGen, int> page_object_to_seq;
117 327 std::map<QPDFObjGen, int> contents_to_page_seq;
118 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 330 bool deterministic_id{false};
123   - Pl_MD5* md5_pipeline{nullptr};
124 331 std::string deterministic_id_data;
125 332 bool did_write_setup{false};
126 333  
... ... @@ -136,7 +343,8 @@ class QPDFWriter::Members
136 343  
137 344 QPDFWriter::Members::Members(QPDF& pdf) :
138 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 398 m->filename = description;
191 399 m->file = file;
192 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 405 void
199 406 QPDFWriter::setOutputMemory()
200 407 {
201 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 413 Buffer*
... ... @@ -222,7 +428,7 @@ void
222 428 QPDFWriter::setOutputPipeline(Pipeline* p)
223 429 {
224 430 m->filename = "custom pipeline";
225   - initializePipelineStack(p);
  431 + m->pipeline_stack.initialize(p);
226 432 }
227 433  
228 434 void
... ... @@ -887,81 +1093,6 @@ QPDFWriter::write_no_qdf(Args&amp;&amp;... args)
887 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 1096 void
966 1097 QPDFWriter::adjustAESStreamLength(size_t& length)
967 1098 {
... ... @@ -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 1115 true,
985 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 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 1129 void
1002   -QPDFWriter::pushMD5Pipeline(PipelinePopper& pp)
  1130 +QPDFWriter::computeDeterministicIDData()
1003 1131 {
1004 1132 if (!m->id2.empty()) {
1005 1133 // Can't happen in the code
1006 1134 throw std::logic_error(
1007 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 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 1141 int
... ... @@ -1271,12 +1384,9 @@ QPDFWriter::willFilterStream(
1271 1384  
1272 1385 bool filtered = false;
1273 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 1390 try {
1281 1391 filtered = stream.pipeStreamData(
1282 1392 m->pipeline,
... ... @@ -1534,19 +1644,9 @@ QPDFWriter::unparseObject(
1534 1644 adjustAESStreamLength(m->cur_stream_length);
1535 1645 unparseObject(stream_dict, 0, flags, m->cur_stream_length, compress_stream);
1536 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 1650 } else if (tc == ::ot_string) {
1551 1651 std::string val;
1552 1652 if (m->encryption && !(flags & f_in_ostream) && !(flags & f_no_encryption) &&
... ... @@ -1626,8 +1726,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1626 1726 const bool compressed = m->compress_streams && !m->qdf_mode;
1627 1727 {
1628 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 1731 int count = -1;
1633 1732 for (auto const& obj: m->object_stream_to_objects[old_id]) {
... ... @@ -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 1772 // Adjust offsets to skip over comment before first object
1674 1773 first = offsets.at(0);
1675 1774 for (auto& iter: offsets) {
... ... @@ -1678,20 +1777,19 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1678 1777  
1679 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 1781 writeObjectStreamOffsets(offsets, first_obj);
1684 1782 first += m->pipeline->getCount();
1685 1783 }
1686 1784  
1687 1785 // Set up a stream to write the stream data into a buffer.
1688 1786 if (compressed) {
1689   - activatePipelineStack(
  1787 + m->pipeline_stack.activate(
1690 1788 pp_ostream,
1691 1789 pl::create<Pl_Flate>(
1692 1790 pl::create<pl::String>(stream_buffer_pass2), Pl_Flate::a_deflate));
1693 1791 } else {
1694   - activatePipelineStack(pp_ostream, stream_buffer_pass2);
  1792 + m->pipeline_stack.activate(pp_ostream, stream_buffer_pass2);
1695 1793 }
1696 1794 writeObjectStreamOffsets(offsets, first_obj);
1697 1795 write(stream_buffer_pass1);
... ... @@ -1720,19 +1818,11 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1720 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 1822 if (m->encryption) {
1725 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 1826 m->cur_data_key.clear();
1737 1827 closeObject(new_stream_id);
1738 1828 }
... ... @@ -2341,22 +2431,13 @@ QPDFWriter::writeHintStream(int hint_id)
2341 2431 }
2342 2432 adjustAESStreamLength(hlen);
2343 2433 write(" /Length ").write(hlen);
2344   - write(" >>\nstream\n");
  2434 + write(" >>\nstream\n").write_encrypted(hint_buffer);
2345 2435  
2346 2436 if (m->encryption) {
2347 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 2441 closeObject(hint_id);
2361 2442 }
2362 2443  
... ... @@ -2447,19 +2528,19 @@ QPDFWriter::writeXRefStream(
2447 2528 std::string xref_data;
2448 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 2532 if (compressed) {
2452   - m->count_buffer.clear();
  2533 + m->pipeline_stack.clear_buffer();
2453 2534 auto link = pl::create<pl::String>(xref_data);
2454 2535 if (!skip_compression) {
2455 2536 // Write the stream dictionary for compression but don't actually compress. This
2456 2537 // helps us with computation of padding for pass 1 of linearization.
2457 2538 link = pl::create<Pl_Flate>(std::move(link), Pl_Flate::a_deflate);
2458 2539 }
2459   - activatePipelineStack(
  2540 + m->pipeline_stack.activate(
2460 2541 pp_xref, pl::create<Pl_PNGFilter>(std::move(link), Pl_PNGFilter::a_encode, esize));
2461 2542 } else {
2462   - activatePipelineStack(pp_xref, xref_data);
  2543 + m->pipeline_stack.activate(pp_xref, xref_data);
2463 2544 }
2464 2545  
2465 2546 for (int i = first; i <= last; ++i) {
... ... @@ -2654,19 +2735,22 @@ QPDFWriter::writeLinearized()
2654 2735 // Write file in two passes. Part numbers refer to PDF spec 1.4.
2655 2736  
2656 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 2740 for (int pass: {1, 2}) {
2660 2741 if (pass == 1) {
2661 2742 if (!m->lin_pass1_filename.empty()) {
2662 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 2749 } else {
2666   - activatePipelineStack(*pp_pass1, true);
  2750 + m->pipeline_stack.activate(pp_pass1, true);
2667 2751 }
2668 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 2925 if (m->deterministic_id) {
2842 2926 QTC::TC("qpdf", "QPDFWriter linearized deterministic ID", need_xref_stream ? 0 : 1);
2843 2927 computeDeterministicIDData();
2844   - pp_md5 = nullptr;
2845   - qpdf_assert_debug(m->md5_pipeline == nullptr);
  2928 + pp_md5.pop();
2846 2929 }
2847 2930  
2848 2931 // Close first pass pipeline
2849 2932 file_size = m->pipeline->getCount();
2850   - pp_pass1 = nullptr;
  2933 + pp_pass1.pop();
2851 2934  
2852 2935 // Save hint offset since it will be set to zero by calling openObject.
2853 2936 qpdf_offset_t hint_offset1 = m->new_obj[hint_id].xref.getOffset();
2854 2937  
2855 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 2941 writeHintStream(hint_id);
2860 2942 }
2861 2943 hint_length = QIntC::to_offset(hint_buffer.size());
... ... @@ -2973,9 +3055,9 @@ QPDFWriter::registerProgressReporter(std::shared_ptr&lt;ProgressReporter&gt; pr)
2973 3055 void
2974 3056 QPDFWriter::writeStandard()
2975 3057 {
2976   - auto pp_md5 = PipelinePopper(this);
  3058 + auto pp_md5 = m->pipeline_stack.popper();
2977 3059 if (m->deterministic_id) {
2978   - pushMD5Pipeline(pp_md5);
  3060 + m->pipeline_stack.activate_md5(pp_md5);
2979 3061 }
2980 3062  
2981 3063 // Start writing
... ...
libqpdf/qpdf/Pipeline_private.hh
... ... @@ -173,6 +173,18 @@ namespace qpdf::pl
173 173 unsigned long id_{0};
174 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 188 } // namespace qpdf::pl
177 189  
178 190 #endif // PIPELINE_PRIVATE_HH
... ...