Coverage Report

Created: 2026-07-16 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/QPDFWriter.cc
Line
Count
Source
1
#include <qpdf/qpdf-config.h> // include early for large file support
2
3
#include <qpdf/QPDFWriter_private.hh>
4
5
#include <qpdf/MD5.hh>
6
#include <qpdf/Pl_AES_PDF.hh>
7
#include <qpdf/Pl_Flate.hh>
8
#include <qpdf/Pl_MD5.hh>
9
#include <qpdf/Pl_PNGFilter.hh>
10
#include <qpdf/Pl_RC4.hh>
11
#include <qpdf/Pl_StdioFile.hh>
12
#include <qpdf/QIntC.hh>
13
#include <qpdf/QPDFObjectHandle_private.hh>
14
#include <qpdf/QPDFObject_private.hh>
15
#include <qpdf/QPDF_private.hh>
16
#include <qpdf/QTC.hh>
17
#include <qpdf/QUtil.hh>
18
#include <qpdf/RC4.hh>
19
#include <qpdf/Util.hh>
20
21
#include <algorithm>
22
#include <concepts>
23
#include <cstdlib>
24
#include <stdexcept>
25
#include <tuple>
26
27
using namespace std::literals;
28
using namespace qpdf;
29
30
using Encryption = impl::Doc::Encryption;
31
using Config = Writer::Config;
32
33
QPDFWriter::ProgressReporter::~ProgressReporter() // NOLINT (modernize-use-equals-default)
34
0
{
35
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
36
0
}
37
38
QPDFWriter::FunctionProgressReporter::FunctionProgressReporter(std::function<void(int)> handler) :
39
0
    handler(handler)
40
0
{
41
0
}
42
43
QPDFWriter::FunctionProgressReporter::~FunctionProgressReporter() // NOLINT
44
                                                                  // (modernize-use-equals-default)
45
0
{
46
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
47
0
}
48
49
void
50
QPDFWriter::FunctionProgressReporter::reportProgress(int progress)
51
0
{
52
0
    handler(progress);
53
0
}
54
55
namespace
56
{
57
    class Pl_stack
58
    {
59
        // A pipeline Popper is normally returned by Pl_stack::activate, or, if necessary, a
60
        // reference to a Popper instance can be passed into activate. When the Popper goes out of
61
        // scope, the pipeline stack is popped. This causes finish to be called on the current
62
        // pipeline and the pipeline stack to be popped until the top of stack is a previous active
63
        // top of stack and restores the pipeline to that point. It deletes any pipelines that it
64
        // pops.
65
        class Popper
66
        {
67
            friend class Pl_stack;
68
69
          public:
70
            Popper() = default;
71
            Popper(Popper const&) = delete;
72
            Popper(Popper&& other) noexcept
73
0
            {
74
0
                // For MSVC, default pops the stack
75
0
                if (this != &other) {
76
0
                    stack = other.stack;
77
0
                    stack_id = other.stack_id;
78
0
                    other.stack = nullptr;
79
0
                    other.stack_id = 0;
80
0
                };
81
0
            }
82
            Popper& operator=(Popper const&) = delete;
83
            Popper&
84
            operator=(Popper&& other) noexcept
85
0
            {
86
0
                // For MSVC, default pops the stack
87
0
                if (this != &other) {
88
0
                    stack = other.stack;
89
0
                    stack_id = other.stack_id;
90
0
                    other.stack = nullptr;
91
0
                    other.stack_id = 0;
92
0
                };
93
0
                return *this;
94
0
            }
95
96
            ~Popper();
97
98
            // Manually pop pipeline from the pipeline stack.
99
            void pop();
100
101
          private:
102
            Popper(Pl_stack& stack) :
103
794k
                stack(&stack)
104
794k
            {
105
794k
            }
106
107
            Pl_stack* stack{nullptr};
108
            unsigned long stack_id{0};
109
        };
110
111
      public:
112
        Pl_stack(pl::Count*& top) :
113
71.7k
            top(top)
114
71.7k
        {
115
71.7k
        }
116
117
        Popper
118
        popper()
119
95.7k
        {
120
95.7k
            return {*this};
121
95.7k
        }
122
123
        void
124
        initialize(Pipeline* p)
125
71.7k
        {
126
71.7k
            auto c = std::make_unique<pl::Count>(++last_id, p);
127
71.7k
            top = c.get();
128
71.7k
            stack.emplace_back(std::move(c));
129
71.7k
        }
130
131
        Popper
132
        activate(std::string& str)
133
653k
        {
134
653k
            Popper pp{*this};
135
653k
            activate(pp, str);
136
653k
            return pp;
137
653k
        }
138
139
        void
140
        activate(Popper& pp, std::string& str)
141
653k
        {
142
653k
            activate(pp, false, &str, nullptr);
143
653k
        }
144
145
        void
146
        activate(Popper& pp, std::unique_ptr<Pipeline> next)
147
0
        {
148
0
            count_buffer.clear();
149
0
            activate(pp, false, &count_buffer, std::move(next));
150
0
        }
151
152
        Popper
153
        activate(
154
            bool discard = false,
155
            std::string* str = nullptr,
156
            std::unique_ptr<Pipeline> next = nullptr)
157
44.3k
        {
158
44.3k
            Popper pp{*this};
159
44.3k
            activate(pp, discard, str, std::move(next));
160
44.3k
            return pp;
161
44.3k
        }
162
163
        void
164
        activate(
165
            Popper& pp,
166
            bool discard = false,
167
            std::string* str = nullptr,
168
            std::unique_ptr<Pipeline> next = nullptr)
169
728k
        {
170
728k
            std::unique_ptr<pl::Count> c;
171
728k
            if (next) {
172
0
                c = std::make_unique<pl::Count>(++last_id, count_buffer, std::move(next));
173
728k
            } else if (discard) {
174
74.9k
                c = std::make_unique<pl::Count>(++last_id, nullptr);
175
653k
            } else if (!str) {
176
0
                c = std::make_unique<pl::Count>(++last_id, top);
177
653k
            } else {
178
653k
                c = std::make_unique<pl::Count>(++last_id, *str);
179
653k
            }
180
728k
            pp.stack_id = last_id;
181
728k
            top = c.get();
182
728k
            stack.emplace_back(std::move(c));
183
728k
        }
184
        void
185
        activate_md5(Popper& pp)
186
33.3k
        {
187
33.3k
            qpdf_assert_debug(!md5_pipeline);
188
33.3k
            qpdf_assert_debug(md5_id == 0);
189
33.3k
            qpdf_assert_debug(top->getCount() == 0);
190
33.3k
            md5_pipeline = std::make_unique<Pl_MD5>("qpdf md5", top);
191
33.3k
            md5_pipeline->persistAcrossFinish(true);
192
            // Special case code in pop clears m->md5_pipeline upon deletion.
193
33.3k
            auto c = std::make_unique<pl::Count>(++last_id, md5_pipeline.get());
194
33.3k
            pp.stack_id = last_id;
195
33.3k
            md5_id = last_id;
196
33.3k
            top = c.get();
197
33.3k
            stack.emplace_back(std::move(c));
198
33.3k
        }
199
200
        // Return the hex digest and disable the MD5 pipeline.
201
        std::string
202
        hex_digest()
203
32.2k
        {
204
32.2k
            qpdf_assert_debug(md5_pipeline);
205
32.2k
            auto digest = md5_pipeline->getHexDigest();
206
32.2k
            md5_pipeline->enable(false);
207
32.2k
            return digest;
208
32.2k
        }
209
210
        void
211
        clear_buffer()
212
0
        {
213
0
            count_buffer.clear();
214
0
        }
215
216
      private:
217
        void
218
        pop(unsigned long stack_id)
219
794k
        {
220
794k
            if (!stack_id) {
221
31.7k
                return;
222
31.7k
            }
223
762k
            qpdf_assert_debug(stack.size() >= 2);
224
762k
            top->finish();
225
762k
            qpdf_assert_debug(stack.back().get() == top);
226
            // It used to be possible for this assertion to fail if writeLinearized exits by
227
            // exception when deterministic ID. There are no longer any cases in which two
228
            // dynamically allocated pipeline Popper objects ever exist at the same time, so the
229
            // assertion will fail if they get popped out of order from automatic destruction.
230
762k
            qpdf_assert_debug(top->id() == stack_id);
231
762k
            if (stack_id == md5_id) {
232
33.3k
                md5_pipeline = nullptr;
233
33.3k
                md5_id = 0;
234
33.3k
            }
235
762k
            stack.pop_back();
236
762k
            top = stack.back().get();
237
762k
        }
238
239
        std::vector<std::unique_ptr<pl::Count>> stack;
240
        pl::Count*& top;
241
        std::unique_ptr<Pl_MD5> md5_pipeline{nullptr};
242
        unsigned long last_id{0};
243
        unsigned long md5_id{0};
244
        std::string count_buffer;
245
    };
246
} // namespace
247
248
Pl_stack::Popper::~Popper()
249
794k
{
250
794k
    if (stack) {
251
750k
        stack->pop(stack_id);
252
750k
    }
253
794k
}
254
255
void
256
Pl_stack::Popper::pop()
257
43.4k
{
258
43.4k
    if (stack) {
259
43.4k
        stack->pop(stack_id);
260
43.4k
    }
261
43.4k
    stack_id = 0;
262
43.4k
    stack = nullptr;
263
43.4k
}
264
265
namespace qpdf::impl
266
{
267
    // Writer class is restricted to QPDFWriter so that only it can call certain methods.
268
    class Writer: protected Doc::Common
269
    {
270
      public:
271
        // flags used by unparseObject
272
        static int const f_stream = 1 << 0;
273
        static int const f_filtered = 1 << 1;
274
        static int const f_in_ostream = 1 << 2;
275
        static int const f_hex_string = 1 << 3;
276
        static int const f_no_encryption = 1 << 4;
277
278
        enum trailer_e { t_normal, t_lin_first, t_lin_second };
279
280
        Writer() = delete;
281
        Writer(Writer const&) = delete;
282
        Writer(Writer&&) = delete;
283
        Writer& operator=(Writer const&) = delete;
284
        Writer& operator=(Writer&&) = delete;
285
        ~Writer()
286
71.7k
        {
287
71.7k
            if (file && close_file) {
288
0
                fclose(file);
289
0
            }
290
71.7k
            delete output_buffer;
291
71.7k
        }
292
        Writer(QPDF& qpdf, QPDFWriter& w) :
293
71.7k
            Common(qpdf.doc()),
294
71.7k
            lin(qpdf.doc().linearization()),
295
71.7k
            cfg(true),
296
71.7k
            root_og(qpdf.getRoot().indirect() ? qpdf.getRoot().id_gen() : QPDFObjGen(-1, 0)),
297
71.7k
            pipeline_stack(pipeline)
298
71.7k
        {
299
71.7k
        }
300
301
        void write();
302
        std::map<QPDFObjGen, QPDFXRefEntry> getWrittenXRefTable();
303
        void setMinimumPDFVersion(std::string const& version, int extension_level = 0);
304
        void copyEncryptionParameters(QPDF&);
305
        void doWriteSetup();
306
        void prepareFileForWrite();
307
308
        void disableIncompatibleEncryption(int major, int minor, int extension_level);
309
        void interpretR3EncryptionParameters(
310
            bool allow_accessibility,
311
            bool allow_extract,
312
            bool allow_assemble,
313
            bool allow_annotate_and_form,
314
            bool allow_form_filling,
315
            bool allow_modify_other,
316
            qpdf_r3_print_e print,
317
            qpdf_r3_modify_e modify);
318
        void setEncryptionParameters(char const* user_password, char const* owner_password);
319
        void setEncryptionMinimumVersion();
320
        void parseVersion(std::string const& version, int& major, int& minor) const;
321
        int compareVersions(int major1, int minor1, int major2, int minor2) const;
322
        void generateID(bool encrypted);
323
        std::string getOriginalID1();
324
        void initializeTables(size_t extra = 0);
325
        void preserveObjectStreams();
326
        void generateObjectStreams();
327
        void initializeSpecialStreams();
328
        void enqueue(QPDFObjectHandle const& object);
329
        void enqueueObjectsStandard();
330
        void enqueueObjectsPCLm();
331
        void enqueuePart(std::vector<QPDFObjectHandle>& part);
332
        void assignCompressedObjectNumbers(QPDFObjGen og);
333
        Dictionary trimmed_trailer();
334
335
        // Returns tuple<filter, compress_stream, is_root_metadata>
336
        std::tuple<const bool, const bool, const bool>
337
        will_filter_stream(QPDFObjectHandle stream, std::string* stream_data);
338
339
        // Test whether stream would be filtered if it were written.
340
        bool will_filter_stream(QPDFObjectHandle stream);
341
        unsigned int bytesNeeded(long long n);
342
        void writeBinary(unsigned long long val, unsigned int bytes);
343
        Writer& write(std::string_view str);
344
        Writer& write(size_t count, char c);
345
        Writer& write(std::integral auto val);
346
        Writer& write_name(std::string const& str);
347
        Writer& write_string(std::string const& str, bool force_binary = false);
348
        Writer& write_encrypted(std::string_view str);
349
350
        template <typename... Args>
351
        Writer& write_qdf(Args&&... args);
352
        template <typename... Args>
353
        Writer& write_no_qdf(Args&&... args);
354
        void writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj);
355
        void writeObjectStream(QPDFObjectHandle object);
356
        void writeObject(QPDFObjectHandle object, int object_stream_index = -1);
357
        void writeTrailer(
358
            trailer_e which,
359
            int size,
360
            bool xref_stream,
361
            qpdf_offset_t prev,
362
            int linearization_pass);
363
        void unparseObject(
364
            QPDFObjectHandle object,
365
            size_t level,
366
            int flags,
367
            // for stream dictionaries
368
            size_t stream_length = 0,
369
            bool compress = false);
370
        void unparseChild(QPDFObjectHandle const& child, size_t level, int flags);
371
        int openObject(int objid = 0);
372
        void closeObject(int objid);
373
        void writeStandard();
374
        void writeLinearized();
375
        void writeEncryptionDictionary();
376
        void writeHeader();
377
        void writeHintStream(int hint_id);
378
        qpdf_offset_t writeXRefTable(trailer_e which, int first, int last, int size);
379
        qpdf_offset_t writeXRefTable(
380
            trailer_e which,
381
            int first,
382
            int last,
383
            int size,
384
            // for linearization
385
            qpdf_offset_t prev,
386
            bool suppress_offsets,
387
            int hint_id,
388
            qpdf_offset_t hint_offset,
389
            qpdf_offset_t hint_length,
390
            int linearization_pass);
391
        qpdf_offset_t writeXRefStream(
392
            int objid,
393
            int max_id,
394
            qpdf_offset_t max_offset,
395
            trailer_e which,
396
            int first,
397
            int last,
398
            int size);
399
        qpdf_offset_t writeXRefStream(
400
            int objid,
401
            int max_id,
402
            qpdf_offset_t max_offset,
403
            trailer_e which,
404
            int first,
405
            int last,
406
            int size,
407
            // for linearization
408
            qpdf_offset_t prev,
409
            int hint_id,
410
            qpdf_offset_t hint_offset,
411
            qpdf_offset_t hint_length,
412
            bool skip_compression,
413
            int linearization_pass);
414
415
        void setDataKey(int objid);
416
        void indicateProgress(bool decrement, bool finished);
417
        size_t calculateXrefStreamPadding(qpdf_offset_t xref_bytes);
418
419
        void adjustAESStreamLength(size_t& length);
420
        void computeDeterministicIDData();
421
422
      protected:
423
        Doc::Linearization& lin;
424
425
        qpdf::Writer::Config cfg;
426
427
        QPDFObjGen root_og{-1, 0};
428
        char const* filename{"unspecified"};
429
        FILE* file{nullptr};
430
        bool close_file{false};
431
        std::unique_ptr<Pl_Buffer> buffer_pipeline{nullptr};
432
        Buffer* output_buffer{nullptr};
433
434
        std::unique_ptr<QPDF::Doc::Encryption> encryption;
435
        std::string encryption_key;
436
437
        std::string id1; // for /ID key of
438
        std::string id2; // trailer dictionary
439
        std::string final_pdf_version;
440
        int final_extension_level{0};
441
        std::string min_pdf_version;
442
        int min_extension_level{0};
443
        int encryption_dict_objid{0};
444
        std::string cur_data_key;
445
        std::unique_ptr<Pipeline> file_pl;
446
        qpdf::pl::Count* pipeline{nullptr};
447
        std::vector<QPDFObjectHandle> object_queue;
448
        size_t object_queue_front{0};
449
        QPDFWriter::ObjTable obj;
450
        QPDFWriter::NewObjTable new_obj;
451
        int next_objid{1};
452
        int cur_stream_length_id{0};
453
        size_t cur_stream_length{0};
454
        bool added_newline{false};
455
        size_t max_ostream_index{0};
456
        std::set<QPDFObjGen> normalized_streams;
457
        std::map<QPDFObjGen, int> page_object_to_seq;
458
        std::map<QPDFObjGen, int> contents_to_page_seq;
459
        std::map<int, std::vector<QPDFObjGen>> object_stream_to_objects;
460
        Pl_stack pipeline_stack;
461
        std::string deterministic_id_data;
462
        bool did_write_setup{false};
463
464
        // For progress reporting
465
        std::shared_ptr<QPDFWriter::ProgressReporter> progress_reporter;
466
        int events_expected{0};
467
        int events_seen{0};
468
        int next_progress_report{0};
469
    }; // class qpdf::impl::Writer
470
471
} // namespace qpdf::impl
472
473
class QPDFWriter::Members: impl::Writer
474
{
475
    friend class QPDFWriter;
476
    friend class qpdf::Writer;
477
478
  public:
479
    Members(QPDFWriter& w, QPDF& qpdf) :
480
71.7k
        impl::Writer(qpdf, w)
481
71.7k
    {
482
71.7k
    }
483
};
484
485
qpdf::Writer::Writer(QPDF& qpdf, Config cfg) :
486
0
    QPDFWriter(qpdf)
487
0
{
488
0
    m->cfg = cfg;
489
0
}
490
QPDFWriter::QPDFWriter(QPDF& pdf) :
491
71.7k
    m(std::make_shared<Members>(*this, pdf))
492
71.7k
{
493
71.7k
}
494
495
QPDFWriter::QPDFWriter(QPDF& pdf, char const* filename) :
496
0
    m(std::make_shared<Members>(*this, pdf))
497
0
{
498
0
    setOutputFilename(filename);
499
0
}
500
501
QPDFWriter::QPDFWriter(QPDF& pdf, char const* description, FILE* file, bool close_file) :
502
0
    m(std::make_shared<Members>(*this, pdf))
503
0
{
504
0
    setOutputFile(description, file, close_file);
505
0
}
506
507
void
508
QPDFWriter::setOutputFilename(char const* filename)
509
0
{
510
0
    char const* description = filename;
511
0
    FILE* f = nullptr;
512
0
    bool close_file = false;
513
0
    if (filename == nullptr) {
514
0
        description = "standard output";
515
0
        f = stdout;
516
0
        QUtil::binary_stdout();
517
0
    } else {
518
0
        f = QUtil::safe_fopen(filename, "wb+");
519
0
        close_file = true;
520
0
    }
521
0
    setOutputFile(description, f, close_file);
522
0
}
523
524
void
525
QPDFWriter::setOutputFile(char const* description, FILE* file, bool close_file)
526
0
{
527
0
    m->filename = description;
528
0
    m->file = file;
529
0
    m->close_file = close_file;
530
0
    m->file_pl = std::make_unique<Pl_StdioFile>("qpdf output", file);
531
0
    m->pipeline_stack.initialize(m->file_pl.get());
532
0
}
533
534
void
535
QPDFWriter::setOutputMemory()
536
0
{
537
0
    m->filename = "memory buffer";
538
0
    m->buffer_pipeline = std::make_unique<Pl_Buffer>("qpdf output");
539
0
    m->pipeline_stack.initialize(m->buffer_pipeline.get());
540
0
}
541
542
Buffer*
543
QPDFWriter::getBuffer()
544
0
{
545
0
    Buffer* result = m->output_buffer;
546
0
    m->output_buffer = nullptr;
547
0
    return result;
548
0
}
549
550
std::shared_ptr<Buffer>
551
QPDFWriter::getBufferSharedPointer()
552
0
{
553
0
    return std::shared_ptr<Buffer>(getBuffer());
554
0
}
555
556
void
557
QPDFWriter::setOutputPipeline(Pipeline* p)
558
71.7k
{
559
71.7k
    m->filename = "custom pipeline";
560
71.7k
    m->pipeline_stack.initialize(p);
561
71.7k
}
562
563
void
564
QPDFWriter::setObjectStreamMode(qpdf_object_stream_e mode)
565
35.4k
{
566
35.4k
    m->cfg.object_streams(mode);
567
35.4k
}
568
569
void
570
QPDFWriter::setStreamDataMode(qpdf_stream_data_e mode)
571
0
{
572
0
    m->cfg.stream_data(mode);
573
0
}
574
575
Config&
576
Config::stream_data(qpdf_stream_data_e mode)
577
0
{
578
0
    switch (mode) {
579
0
    case qpdf_s_uncompress:
580
0
        decode_level(std::max(qpdf_dl_generalized, decode_level_));
581
0
        compress_streams(false);
582
0
        return *this;
583
584
0
    case qpdf_s_preserve:
585
0
        decode_level(qpdf_dl_none);
586
0
        compress_streams(false);
587
0
        return *this;
588
589
0
    case qpdf_s_compress:
590
0
        decode_level(std::max(qpdf_dl_generalized, decode_level_));
591
0
        compress_streams(true);
592
0
    }
593
0
    return *this;
594
0
}
595
596
void
597
QPDFWriter::setCompressStreams(bool val)
598
0
{
599
0
    m->cfg.compress_streams(val);
600
0
}
601
602
Config&
603
Config::compress_streams(bool val)
604
18.3k
{
605
18.3k
    if (pclm_) {
606
0
        usage("compress_streams cannot be set when pclm is set");
607
0
        return *this;
608
0
    }
609
18.3k
    compress_streams_set_ = true;
610
18.3k
    compress_streams_ = val;
611
18.3k
    return *this;
612
18.3k
}
613
614
void
615
QPDFWriter::setDecodeLevel(qpdf_stream_decode_level_e val)
616
71.7k
{
617
71.7k
    m->cfg.decode_level(val);
618
71.7k
}
619
620
Config&
621
Config::decode_level(qpdf_stream_decode_level_e val)
622
71.7k
{
623
71.7k
    if (pclm_) {
624
0
        usage("stream_decode_level cannot be set when pclm is set");
625
0
        return *this;
626
0
    }
627
71.7k
    decode_level_set_ = true;
628
71.7k
    decode_level_ = val;
629
71.7k
    return *this;
630
71.7k
}
631
632
void
633
QPDFWriter::setRecompressFlate(bool val)
634
0
{
635
0
    m->cfg.recompress_flate(val);
636
0
}
637
638
void
639
QPDFWriter::setContentNormalization(bool val)
640
0
{
641
0
    m->cfg.normalize_content(val);
642
0
}
643
644
void
645
QPDFWriter::setQDFMode(bool val)
646
18.3k
{
647
18.3k
    m->cfg.qdf(val);
648
18.3k
}
649
650
Config&
651
Config::qdf(bool val)
652
55.4k
{
653
55.4k
    if (pclm_ || linearize_) {
654
37.0k
        usage("qdf cannot be set when linearize or pclm are set");
655
37.0k
    }
656
55.4k
    if (preserve_encryption_) {
657
55.4k
        usage("preserve_encryption cannot be set when qdf is set");
658
55.4k
    }
659
55.4k
    qdf_ = val;
660
55.4k
    if (val) {
661
18.3k
        if (!normalize_content_set_) {
662
18.3k
            normalize_content(true);
663
18.3k
        }
664
18.3k
        if (!compress_streams_set_) {
665
18.3k
            compress_streams(false);
666
18.3k
        }
667
18.3k
        if (!decode_level_set_) {
668
0
            decode_level(qpdf_dl_generalized);
669
0
        }
670
18.3k
        preserve_encryption_ = false;
671
        // Generate indirect stream lengths for qdf mode since fix-qdf uses them for storing
672
        // recomputed stream length data. Certain streams such as object streams, xref streams, and
673
        // hint streams always get direct stream lengths.
674
18.3k
        direct_stream_lengths_ = false;
675
18.3k
    }
676
55.4k
    return *this;
677
55.4k
}
678
679
void
680
QPDFWriter::setPreserveUnreferencedObjects(bool val)
681
0
{
682
0
    m->cfg.preserve_unreferenced(val);
683
0
}
684
685
void
686
QPDFWriter::setNewlineBeforeEndstream(bool val)
687
0
{
688
0
    m->cfg.newline_before_endstream(val);
689
0
}
690
691
void
692
QPDFWriter::setMinimumPDFVersion(std::string const& version, int extension_level)
693
0
{
694
0
    m->setMinimumPDFVersion(version, extension_level);
695
0
}
696
697
void
698
impl::Writer::setMinimumPDFVersion(std::string const& version, int extension_level)
699
124k
{
700
124k
    bool set_version = false;
701
124k
    bool set_extension_level = false;
702
124k
    if (min_pdf_version.empty()) {
703
71.4k
        set_version = true;
704
71.4k
        set_extension_level = true;
705
71.4k
    } else {
706
53.5k
        int old_major = 0;
707
53.5k
        int old_minor = 0;
708
53.5k
        int min_major = 0;
709
53.5k
        int min_minor = 0;
710
53.5k
        parseVersion(version, old_major, old_minor);
711
53.5k
        parseVersion(min_pdf_version, min_major, min_minor);
712
53.5k
        int compare = compareVersions(old_major, old_minor, min_major, min_minor);
713
53.5k
        if (compare > 0) {
714
3.08k
            QTC::TC("qpdf", "QPDFWriter increasing minimum version", extension_level == 0 ? 0 : 1);
715
3.08k
            set_version = true;
716
3.08k
            set_extension_level = true;
717
50.4k
        } else if (compare == 0) {
718
2.00k
            if (extension_level > min_extension_level) {
719
57
                set_extension_level = true;
720
57
            }
721
2.00k
        }
722
53.5k
    }
723
724
124k
    if (set_version) {
725
74.4k
        min_pdf_version = version;
726
74.4k
    }
727
124k
    if (set_extension_level) {
728
74.5k
        min_extension_level = extension_level;
729
74.5k
    }
730
124k
}
731
732
void
733
QPDFWriter::setMinimumPDFVersion(PDFVersion const& v)
734
0
{
735
0
    std::string version;
736
0
    int extension_level;
737
0
    v.getVersion(version, extension_level);
738
0
    setMinimumPDFVersion(version, extension_level);
739
0
}
740
741
void
742
QPDFWriter::forcePDFVersion(std::string const& version, int extension_level)
743
0
{
744
0
    m->cfg.forced_pdf_version(version, extension_level);
745
0
}
746
747
void
748
QPDFWriter::setExtraHeaderText(std::string const& text)
749
0
{
750
0
    m->cfg.extra_header_text(text);
751
0
}
752
753
Config&
754
Config::extra_header_text(std::string const& val)
755
0
{
756
0
    extra_header_text_ = val;
757
0
    if (!extra_header_text_.empty() && extra_header_text_.back() != '\n') {
758
0
        extra_header_text_ += "\n";
759
0
    } else {
760
0
        QTC::TC("qpdf", "QPDFWriter extra header text no newline");
761
0
    }
762
0
    return *this;
763
0
}
764
765
void
766
QPDFWriter::setStaticID(bool val)
767
34.3k
{
768
34.3k
    m->cfg.static_id(val);
769
34.3k
}
770
771
void
772
QPDFWriter::setDeterministicID(bool val)
773
37.4k
{
774
37.4k
    m->cfg.deterministic_id(val);
775
37.4k
}
776
777
void
778
QPDFWriter::setStaticAesIV(bool val)
779
0
{
780
0
    if (val) {
781
0
        Pl_AES_PDF::useStaticIV();
782
0
    }
783
0
}
784
785
void
786
QPDFWriter::setSuppressOriginalObjectIDs(bool val)
787
0
{
788
0
    m->cfg.no_original_object_ids(val);
789
0
}
790
791
void
792
QPDFWriter::setPreserveEncryption(bool val)
793
0
{
794
0
    m->cfg.preserve_encryption(val);
795
0
}
796
797
void
798
QPDFWriter::setLinearization(bool val)
799
37.0k
{
800
37.0k
    m->cfg.linearize(val);
801
37.0k
}
802
803
Config&
804
Config::linearize(bool val)
805
37.0k
{
806
37.0k
    if (pclm_ || qdf_) {
807
0
        usage("linearize cannot be set when qdf or pclm are set");
808
0
        return *this;
809
0
    }
810
37.0k
    linearize_ = val;
811
37.0k
    return *this;
812
37.0k
}
813
814
void
815
QPDFWriter::setLinearizationPass1Filename(std::string const& filename)
816
0
{
817
0
    m->cfg.linearize_pass1(filename);
818
0
}
819
820
void
821
QPDFWriter::setPCLm(bool val)
822
0
{
823
0
    m->cfg.pclm(val);
824
0
}
825
826
Config&
827
Config::pclm(bool val)
828
0
{
829
0
    if (decode_level_set_ || compress_streams_set_ || linearize_) {
830
0
        usage(
831
0
            "pclm cannot be set when stream_decode_level, compress_streams, linearize or qdf are "
832
0
            "set");
833
0
        return *this;
834
0
    }
835
0
    pclm_ = val;
836
0
    if (val) {
837
0
        decode_level_ = qpdf_dl_none;
838
0
        compress_streams_ = false;
839
0
        linearize_ = false;
840
0
    }
841
842
0
    return *this;
843
0
}
844
845
void
846
QPDFWriter::setR2EncryptionParametersInsecure(
847
    char const* user_password,
848
    char const* owner_password,
849
    bool allow_print,
850
    bool allow_modify,
851
    bool allow_extract,
852
    bool allow_annotate)
853
0
{
854
0
    m->encryption = std::make_unique<Encryption>(1, 2, 5, true);
855
0
    if (!allow_print) {
856
0
        m->encryption->setP(3, false);
857
0
    }
858
0
    if (!allow_modify) {
859
0
        m->encryption->setP(4, false);
860
0
    }
861
0
    if (!allow_extract) {
862
0
        m->encryption->setP(5, false);
863
0
    }
864
0
    if (!allow_annotate) {
865
0
        m->encryption->setP(6, false);
866
0
    }
867
0
    m->setEncryptionParameters(user_password, owner_password);
868
0
}
869
870
void
871
QPDFWriter::setR3EncryptionParametersInsecure(
872
    char const* user_password,
873
    char const* owner_password,
874
    bool allow_accessibility,
875
    bool allow_extract,
876
    bool allow_assemble,
877
    bool allow_annotate_and_form,
878
    bool allow_form_filling,
879
    bool allow_modify_other,
880
    qpdf_r3_print_e print)
881
16.3k
{
882
16.3k
    m->encryption = std::make_unique<Encryption>(2, 3, 16, true);
883
16.3k
    m->interpretR3EncryptionParameters(
884
16.3k
        allow_accessibility,
885
16.3k
        allow_extract,
886
16.3k
        allow_assemble,
887
16.3k
        allow_annotate_and_form,
888
16.3k
        allow_form_filling,
889
16.3k
        allow_modify_other,
890
16.3k
        print,
891
16.3k
        qpdf_r3m_all);
892
16.3k
    m->setEncryptionParameters(user_password, owner_password);
893
16.3k
}
894
895
void
896
QPDFWriter::setR4EncryptionParametersInsecure(
897
    char const* user_password,
898
    char const* owner_password,
899
    bool allow_accessibility,
900
    bool allow_extract,
901
    bool allow_assemble,
902
    bool allow_annotate_and_form,
903
    bool allow_form_filling,
904
    bool allow_modify_other,
905
    qpdf_r3_print_e print,
906
    bool encrypt_metadata,
907
    bool use_aes)
908
0
{
909
0
    m->encryption = std::make_unique<Encryption>(4, 4, 16, encrypt_metadata);
910
0
    m->cfg.encrypt_use_aes(use_aes);
911
0
    m->interpretR3EncryptionParameters(
912
0
        allow_accessibility,
913
0
        allow_extract,
914
0
        allow_assemble,
915
0
        allow_annotate_and_form,
916
0
        allow_form_filling,
917
0
        allow_modify_other,
918
0
        print,
919
0
        qpdf_r3m_all);
920
0
    m->setEncryptionParameters(user_password, owner_password);
921
0
}
922
923
void
924
QPDFWriter::setR5EncryptionParameters(
925
    char const* user_password,
926
    char const* owner_password,
927
    bool allow_accessibility,
928
    bool allow_extract,
929
    bool allow_assemble,
930
    bool allow_annotate_and_form,
931
    bool allow_form_filling,
932
    bool allow_modify_other,
933
    qpdf_r3_print_e print,
934
    bool encrypt_metadata)
935
0
{
936
0
    m->encryption = std::make_unique<Encryption>(5, 5, 32, encrypt_metadata);
937
0
    m->cfg.encrypt_use_aes(true);
938
0
    m->interpretR3EncryptionParameters(
939
0
        allow_accessibility,
940
0
        allow_extract,
941
0
        allow_assemble,
942
0
        allow_annotate_and_form,
943
0
        allow_form_filling,
944
0
        allow_modify_other,
945
0
        print,
946
0
        qpdf_r3m_all);
947
0
    m->setEncryptionParameters(user_password, owner_password);
948
0
}
949
950
void
951
QPDFWriter::setR6EncryptionParameters(
952
    char const* user_password,
953
    char const* owner_password,
954
    bool allow_accessibility,
955
    bool allow_extract,
956
    bool allow_assemble,
957
    bool allow_annotate_and_form,
958
    bool allow_form_filling,
959
    bool allow_modify_other,
960
    qpdf_r3_print_e print,
961
    bool encrypt_metadata)
962
17.9k
{
963
17.9k
    m->encryption = std::make_unique<Encryption>(5, 6, 32, encrypt_metadata);
964
17.9k
    m->interpretR3EncryptionParameters(
965
17.9k
        allow_accessibility,
966
17.9k
        allow_extract,
967
17.9k
        allow_assemble,
968
17.9k
        allow_annotate_and_form,
969
17.9k
        allow_form_filling,
970
17.9k
        allow_modify_other,
971
17.9k
        print,
972
17.9k
        qpdf_r3m_all);
973
17.9k
    m->cfg.encrypt_use_aes(true);
974
17.9k
    m->setEncryptionParameters(user_password, owner_password);
975
17.9k
}
976
977
void
978
impl::Writer::interpretR3EncryptionParameters(
979
    bool allow_accessibility,
980
    bool allow_extract,
981
    bool allow_assemble,
982
    bool allow_annotate_and_form,
983
    bool allow_form_filling,
984
    bool allow_modify_other,
985
    qpdf_r3_print_e print,
986
    qpdf_r3_modify_e modify)
987
34.3k
{
988
    // Acrobat 5 security options:
989
990
    // Checkboxes:
991
    //   Enable Content Access for the Visually Impaired
992
    //   Allow Content Copying and Extraction
993
994
    // Allowed changes menu:
995
    //   None
996
    //   Only Document Assembly
997
    //   Only Form Field Fill-in or Signing
998
    //   Comment Authoring, Form Field Fill-in or Signing
999
    //   General Editing, Comment and Form Field Authoring
1000
1001
    // Allowed printing menu:
1002
    //   None
1003
    //   Low Resolution
1004
    //   Full printing
1005
1006
    // Meanings of bits in P when R >= 3
1007
    //
1008
    //  3: low-resolution printing
1009
    //  4: document modification except as controlled by 6, 9, and 11
1010
    //  5: extraction
1011
    //  6: add/modify annotations (comment), fill in forms
1012
    //     if 4+6 are set, also allows modification of form fields
1013
    //  9: fill in forms even if 6 is clear
1014
    // 10: accessibility; ignored by readers, should always be set
1015
    // 11: document assembly even if 4 is clear
1016
    // 12: high-resolution printing
1017
34.3k
    if (!allow_accessibility && encryption->getR() <= 3) {
1018
        // Bit 10 is deprecated and should always be set.  This used to mean accessibility.  There
1019
        // is no way to disable accessibility with R > 3.
1020
0
        encryption->setP(10, false);
1021
0
    }
1022
34.3k
    if (!allow_extract) {
1023
0
        encryption->setP(5, false);
1024
0
    }
1025
1026
34.3k
    switch (print) {
1027
0
    case qpdf_r3p_none:
1028
0
        encryption->setP(3, false); // any printing
1029
0
        [[fallthrough]];
1030
0
    case qpdf_r3p_low:
1031
0
        encryption->setP(12, false); // high resolution printing
1032
0
        [[fallthrough]];
1033
34.3k
    case qpdf_r3p_full:
1034
34.3k
        break;
1035
        // no default so gcc warns for missing cases
1036
34.3k
    }
1037
1038
    // Modify options. The qpdf_r3_modify_e options control groups of bits and lack the full
1039
    // flexibility of the spec. This is unfortunate, but it's been in the API for ages, and we're
1040
    // stuck with it. See also allow checks below to control the bits individually.
1041
1042
    // NOT EXERCISED IN TEST SUITE
1043
34.3k
    switch (modify) {
1044
0
    case qpdf_r3m_none:
1045
0
        encryption->setP(11, false); // document assembly
1046
0
        [[fallthrough]];
1047
0
    case qpdf_r3m_assembly:
1048
0
        encryption->setP(9, false); // filling in form fields
1049
0
        [[fallthrough]];
1050
0
    case qpdf_r3m_form:
1051
0
        encryption->setP(6, false); // modify annotations, fill in form fields
1052
0
        [[fallthrough]];
1053
0
    case qpdf_r3m_annotate:
1054
0
        encryption->setP(4, false); // other modifications
1055
0
        [[fallthrough]];
1056
34.3k
    case qpdf_r3m_all:
1057
34.3k
        break;
1058
        // no default so gcc warns for missing cases
1059
34.3k
    }
1060
    // END NOT EXERCISED IN TEST SUITE
1061
1062
34.3k
    if (!allow_assemble) {
1063
0
        encryption->setP(11, false);
1064
0
    }
1065
34.3k
    if (!allow_annotate_and_form) {
1066
0
        encryption->setP(6, false);
1067
0
    }
1068
34.3k
    if (!allow_form_filling) {
1069
0
        encryption->setP(9, false);
1070
0
    }
1071
34.3k
    if (!allow_modify_other) {
1072
0
        encryption->setP(4, false);
1073
0
    }
1074
34.3k
}
1075
1076
void
1077
impl::Writer::setEncryptionParameters(char const* user_password, char const* owner_password)
1078
34.3k
{
1079
34.3k
    generateID(true);
1080
34.3k
    encryption->setId1(id1);
1081
34.3k
    encryption_key = encryption->compute_parameters(user_password, owner_password);
1082
34.3k
    setEncryptionMinimumVersion();
1083
34.3k
}
1084
1085
void
1086
QPDFWriter::copyEncryptionParameters(QPDF& qpdf)
1087
0
{
1088
0
    m->copyEncryptionParameters(qpdf);
1089
0
}
1090
1091
void
1092
impl::Writer::copyEncryptionParameters(QPDF& qpdf)
1093
19.0k
{
1094
19.0k
    cfg.preserve_encryption(false);
1095
19.0k
    QPDFObjectHandle trailer = qpdf.getTrailer();
1096
19.0k
    if (trailer.hasKey("/Encrypt")) {
1097
157
        generateID(true);
1098
157
        id1 = trailer.getKey("/ID").getArrayItem(0).getStringValue();
1099
157
        QPDFObjectHandle encrypt = trailer.getKey("/Encrypt");
1100
157
        int V = encrypt.getKey("/V").getIntValueAsInt();
1101
157
        int key_len = 5;
1102
157
        if (V > 1) {
1103
0
            key_len = encrypt.getKey("/Length").getIntValueAsInt() / 8;
1104
0
        }
1105
157
        const bool encrypt_metadata =
1106
157
            encrypt.hasKey("/EncryptMetadata") && encrypt.getKey("/EncryptMetadata").isBool()
1107
157
            ? encrypt.getKey("/EncryptMetadata").getBoolValue()
1108
157
            : true;
1109
157
        if (V >= 4) {
1110
            // When copying encryption parameters, use AES even if the original file did not.
1111
            // Acrobat doesn't create files with V >= 4 that don't use AES, and the logic of
1112
            // figuring out whether AES is used or not is complicated with /StmF, /StrF, and /EFF
1113
            // all potentially having different values.
1114
0
            cfg.encrypt_use_aes(true);
1115
0
        }
1116
157
        QTC::TC("qpdf", "QPDFWriter copy encrypt metadata", encrypt_metadata ? 0 : 1);
1117
157
        QTC::TC("qpdf", "QPDFWriter copy use_aes", cfg.encrypt_use_aes() ? 0 : 1);
1118
1119
157
        encryption = std::make_unique<Encryption>(
1120
157
            V,
1121
157
            encrypt.getKey("/R").getIntValueAsInt(),
1122
157
            key_len,
1123
157
            static_cast<int>(encrypt.getKey("/P").getIntValue()),
1124
157
            encrypt.getKey("/O").getStringValue(),
1125
157
            encrypt.getKey("/U").getStringValue(),
1126
157
            V < 5 ? "" : encrypt.getKey("/OE").getStringValue(),
1127
157
            V < 5 ? "" : encrypt.getKey("/UE").getStringValue(),
1128
157
            V < 5 ? "" : encrypt.getKey("/Perms").getStringValue(),
1129
157
            id1, // id1 == the other file's id1
1130
157
            encrypt_metadata);
1131
157
        encryption_key = V >= 5 ? qpdf.getEncryptionKey()
1132
157
                                : encryption->compute_encryption_key(qpdf.getPaddedUserPassword());
1133
157
        setEncryptionMinimumVersion();
1134
157
    }
1135
19.0k
}
1136
1137
void
1138
impl::Writer::disableIncompatibleEncryption(int major, int minor, int extension_level)
1139
0
{
1140
0
    if (!encryption) {
1141
0
        return;
1142
0
    }
1143
0
    if (compareVersions(major, minor, 1, 3) < 0) {
1144
0
        encryption = nullptr;
1145
0
        return;
1146
0
    }
1147
0
    int V = encryption->getV();
1148
0
    int R = encryption->getR();
1149
0
    if (compareVersions(major, minor, 1, 4) < 0) {
1150
0
        if (V > 1 || R > 2) {
1151
0
            encryption = nullptr;
1152
0
        }
1153
0
    } else if (compareVersions(major, minor, 1, 5) < 0) {
1154
0
        if (V > 2 || R > 3) {
1155
0
            encryption = nullptr;
1156
0
        }
1157
0
    } else if (compareVersions(major, minor, 1, 6) < 0) {
1158
0
        if (cfg.encrypt_use_aes()) {
1159
0
            encryption = nullptr;
1160
0
        }
1161
0
    } else if (
1162
0
        (compareVersions(major, minor, 1, 7) < 0) ||
1163
0
        ((compareVersions(major, minor, 1, 7) == 0) && extension_level < 3)) {
1164
0
        if (V >= 5 || R >= 5) {
1165
0
            encryption = nullptr;
1166
0
        }
1167
0
    }
1168
1169
0
    if (!encryption) {
1170
0
        QTC::TC("qpdf", "QPDFWriter forced version disabled encryption");
1171
0
    }
1172
0
}
1173
1174
void
1175
impl::Writer::parseVersion(std::string const& version, int& major, int& minor) const
1176
107k
{
1177
107k
    major = QUtil::string_to_int(version.c_str());
1178
107k
    minor = 0;
1179
107k
    size_t p = version.find('.');
1180
107k
    if ((p != std::string::npos) && (version.length() > p)) {
1181
107k
        minor = QUtil::string_to_int(version.substr(p + 1).c_str());
1182
107k
    }
1183
107k
    std::string tmp = std::to_string(major) + "." + std::to_string(minor);
1184
107k
    if (tmp != version) {
1185
        // The version number in the input is probably invalid. This happens with some files that
1186
        // are designed to exercise bugs, such as files in the fuzzer corpus. Unfortunately
1187
        // QPDFWriter doesn't have a way to give a warning, so we just ignore this case.
1188
658
    }
1189
107k
}
1190
1191
int
1192
impl::Writer::compareVersions(int major1, int minor1, int major2, int minor2) const
1193
53.4k
{
1194
53.4k
    if (major1 < major2) {
1195
518
        return -1;
1196
518
    }
1197
52.9k
    if (major1 > major2) {
1198
518
        return 1;
1199
518
    }
1200
52.4k
    if (minor1 < minor2) {
1201
47.8k
        return -1;
1202
47.8k
    }
1203
4.56k
    return minor1 > minor2 ? 1 : 0;
1204
52.4k
}
1205
1206
void
1207
impl::Writer::setEncryptionMinimumVersion()
1208
34.3k
{
1209
34.3k
    auto const R = encryption->getR();
1210
34.3k
    if (R >= 6) {
1211
17.9k
        setMinimumPDFVersion("1.7", 8);
1212
17.9k
    } else if (R == 5) {
1213
0
        setMinimumPDFVersion("1.7", 3);
1214
16.3k
    } else if (R == 4) {
1215
0
        setMinimumPDFVersion(cfg.encrypt_use_aes() ? "1.6" : "1.5");
1216
16.3k
    } else if (R == 3) {
1217
16.3k
        setMinimumPDFVersion("1.4");
1218
16.3k
    } else {
1219
0
        setMinimumPDFVersion("1.3");
1220
0
    }
1221
34.3k
}
1222
1223
void
1224
impl::Writer::setDataKey(int objid)
1225
1.05M
{
1226
1.05M
    if (encryption) {
1227
633k
        cur_data_key = QPDF::compute_data_key(
1228
633k
            encryption_key,
1229
633k
            objid,
1230
633k
            0,
1231
633k
            cfg.encrypt_use_aes(),
1232
633k
            encryption->getV(),
1233
633k
            encryption->getR());
1234
633k
    }
1235
1.05M
}
1236
1237
unsigned int
1238
impl::Writer::bytesNeeded(long long n)
1239
175k
{
1240
175k
    unsigned int bytes = 0;
1241
401k
    while (n) {
1242
226k
        ++bytes;
1243
226k
        n >>= 8;
1244
226k
    }
1245
175k
    return bytes;
1246
175k
}
1247
1248
void
1249
impl::Writer::writeBinary(unsigned long long val, unsigned int bytes)
1250
3.01M
{
1251
3.01M
    if (bytes > sizeof(unsigned long long)) {
1252
0
        throw std::logic_error("QPDFWriter::writeBinary called with too many bytes");
1253
0
    }
1254
3.01M
    unsigned char data[sizeof(unsigned long long)];
1255
7.38M
    for (unsigned int i = 0; i < bytes; ++i) {
1256
4.37M
        data[bytes - i - 1] = static_cast<unsigned char>(val & 0xff);
1257
4.37M
        val >>= 8;
1258
4.37M
    }
1259
3.01M
    pipeline->write(data, bytes);
1260
3.01M
}
1261
1262
impl::Writer&
1263
impl::Writer::write(std::string_view str)
1264
93.5M
{
1265
93.5M
    pipeline->write(str);
1266
93.5M
    return *this;
1267
93.5M
}
1268
1269
impl::Writer&
1270
impl::Writer::write(std::integral auto val)
1271
6.13M
{
1272
6.13M
    pipeline->write(std::to_string(val));
1273
6.13M
    return *this;
1274
6.13M
}
_ZN4qpdf4impl6Writer5writeITkNSt3__18integralEiEERS1_T_
Line
Count
Source
1271
4.17M
{
1272
4.17M
    pipeline->write(std::to_string(val));
1273
4.17M
    return *this;
1274
4.17M
}
_ZN4qpdf4impl6Writer5writeITkNSt3__18integralExEERS1_T_
Line
Count
Source
1271
1.29M
{
1272
1.29M
    pipeline->write(std::to_string(val));
1273
1.29M
    return *this;
1274
1.29M
}
_ZN4qpdf4impl6Writer5writeITkNSt3__18integralEmEERS1_T_
Line
Count
Source
1271
488k
{
1272
488k
    pipeline->write(std::to_string(val));
1273
488k
    return *this;
1274
488k
}
_ZN4qpdf4impl6Writer5writeITkNSt3__18integralEjEERS1_T_
Line
Count
Source
1271
174k
{
1272
174k
    pipeline->write(std::to_string(val));
1273
174k
    return *this;
1274
174k
}
1275
1276
impl::Writer&
1277
impl::Writer::write(size_t count, char c)
1278
133k
{
1279
133k
    pipeline->write(count, c);
1280
133k
    return *this;
1281
133k
}
1282
1283
impl::Writer&
1284
impl::Writer::write_name(std::string const& str)
1285
4.19M
{
1286
4.19M
    pipeline->write(Name::normalize(str));
1287
4.19M
    return *this;
1288
4.19M
}
1289
1290
impl::Writer&
1291
impl::Writer::write_string(std::string const& str, bool force_binary)
1292
365k
{
1293
365k
    pipeline->write(QPDF_String(str).unparse(force_binary));
1294
365k
    return *this;
1295
365k
}
1296
1297
template <typename... Args>
1298
impl::Writer&
1299
impl::Writer::write_qdf(Args&&... args)
1300
3.55M
{
1301
3.55M
    if (cfg.qdf()) {
1302
451k
        pipeline->write(std::forward<Args>(args)...);
1303
451k
    }
1304
3.55M
    return *this;
1305
3.55M
}
qpdf::impl::Writer& qpdf::impl::Writer::write_qdf<char const (&) [2]>(char const (&) [2])
Line
Count
Source
1300
2.68M
{
1301
2.68M
    if (cfg.qdf()) {
1302
361k
        pipeline->write(std::forward<Args>(args)...);
1303
361k
    }
1304
2.68M
    return *this;
1305
2.68M
}
qpdf::impl::Writer& qpdf::impl::Writer::write_qdf<char const (&) [3]>(char const (&) [3])
Line
Count
Source
1300
624k
{
1301
624k
    if (cfg.qdf()) {
1302
53.3k
        pipeline->write(std::forward<Args>(args)...);
1303
53.3k
    }
1304
624k
    return *this;
1305
624k
}
qpdf::impl::Writer& qpdf::impl::Writer::write_qdf<char const (&) [4]>(char const (&) [4])
Line
Count
Source
1300
151k
{
1301
151k
    if (cfg.qdf()) {
1302
17.8k
        pipeline->write(std::forward<Args>(args)...);
1303
17.8k
    }
1304
151k
    return *this;
1305
151k
}
qpdf::impl::Writer& qpdf::impl::Writer::write_qdf<char const (&) [11]>(char const (&) [11])
Line
Count
Source
1300
94.1k
{
1301
94.1k
    if (cfg.qdf()) {
1302
18.1k
        pipeline->write(std::forward<Args>(args)...);
1303
18.1k
    }
1304
94.1k
    return *this;
1305
94.1k
}
1306
1307
template <typename... Args>
1308
impl::Writer&
1309
impl::Writer::write_no_qdf(Args&&... args)
1310
1.26M
{
1311
1.26M
    if (!cfg.qdf()) {
1312
1.13M
        pipeline->write(std::forward<Args>(args)...);
1313
1.13M
    }
1314
1.26M
    return *this;
1315
1.26M
}
qpdf::impl::Writer& qpdf::impl::Writer::write_no_qdf<char const (&) [2]>(char const (&) [2])
Line
Count
Source
1310
1.11M
{
1311
1.11M
    if (!cfg.qdf()) {
1312
997k
        pipeline->write(std::forward<Args>(args)...);
1313
997k
    }
1314
1.11M
    return *this;
1315
1.11M
}
qpdf::impl::Writer& qpdf::impl::Writer::write_no_qdf<char const (&) [4]>(char const (&) [4])
Line
Count
Source
1310
151k
{
1311
151k
    if (!cfg.qdf()) {
1312
133k
        pipeline->write(std::forward<Args>(args)...);
1313
133k
    }
1314
151k
    return *this;
1315
151k
}
1316
1317
void
1318
impl::Writer::adjustAESStreamLength(size_t& length)
1319
358k
{
1320
358k
    if (encryption && !cur_data_key.empty() && cfg.encrypt_use_aes()) {
1321
        // Stream length will be padded with 1 to 16 bytes to end up as a multiple of 16.  It will
1322
        // also be prepended by 16 bits of random data.
1323
117k
        length += 32 - (length & 0xf);
1324
117k
    }
1325
358k
}
1326
1327
impl::Writer&
1328
impl::Writer::write_encrypted(std::string_view str)
1329
356k
{
1330
356k
    if (!(encryption && !cur_data_key.empty())) {
1331
178k
        write(str);
1332
178k
    } else if (cfg.encrypt_use_aes()) {
1333
116k
        write(pl::pipe<Pl_AES_PDF>(str, true, cur_data_key));
1334
116k
    } else {
1335
61.2k
        write(pl::pipe<Pl_RC4>(str, cur_data_key));
1336
61.2k
    }
1337
1338
356k
    return *this;
1339
356k
}
1340
1341
void
1342
impl::Writer::computeDeterministicIDData()
1343
32.2k
{
1344
32.2k
    if (!id2.empty()) {
1345
        // Can't happen in the code
1346
0
        throw std::logic_error(
1347
0
            "Deterministic ID computation enabled after ID generation has already occurred.");
1348
0
    }
1349
32.2k
    qpdf_assert_debug(deterministic_id_data.empty());
1350
32.2k
    deterministic_id_data = pipeline_stack.hex_digest();
1351
32.2k
}
1352
1353
int
1354
impl::Writer::openObject(int objid)
1355
1.26M
{
1356
1.26M
    if (objid == 0) {
1357
15.9k
        objid = next_objid++;
1358
15.9k
    }
1359
1.26M
    new_obj[objid].xref = QPDFXRefEntry(pipeline->getCount());
1360
1.26M
    write(objid).write(" 0 obj\n");
1361
1.26M
    return objid;
1362
1.26M
}
1363
1364
void
1365
impl::Writer::closeObject(int objid)
1366
1.26M
{
1367
    // Write a newline before endobj as it makes the file easier to repair.
1368
1.26M
    write("\nendobj\n").write_qdf("\n");
1369
1.26M
    auto& no = new_obj[objid];
1370
1.26M
    no.length = pipeline->getCount() - no.xref.getOffset();
1371
1.26M
}
1372
1373
void
1374
impl::Writer::assignCompressedObjectNumbers(QPDFObjGen og)
1375
428k
{
1376
428k
    int objid = og.getObj();
1377
428k
    if (og.getGen() != 0 || !object_stream_to_objects.contains(objid)) {
1378
        // This is not an object stream.
1379
400k
        return;
1380
400k
    }
1381
1382
    // Reserve numbers for the objects that belong to this object stream.
1383
309k
    for (auto const& iter: object_stream_to_objects[objid]) {
1384
309k
        obj[iter].renumber = next_objid++;
1385
309k
    }
1386
28.6k
}
1387
1388
void
1389
impl::Writer::enqueue(QPDFObjectHandle const& object)
1390
209M
{
1391
209M
    if (object.indirect()) {
1392
1.64M
        util::assertion(
1393
            // This owner check can only be done for indirect objects. It is possible for a direct
1394
            // object to have an owning QPDF that is from another file if a direct QPDFObjectHandle
1395
            // from one file was insert into another file without copying. Doing that is safe even
1396
            // if the original QPDF gets destroyed, which just disconnects the QPDFObjectHandle from
1397
            // its owner.
1398
1.64M
            object.qpdf() == &qpdf,
1399
1.64M
            "QPDFObjectHandle from different QPDF found while writing.  "
1400
1.64M
            "Use QPDF::copyForeignObject to add objects from another file." //
1401
1.64M
        );
1402
1403
1.64M
        if (cfg.qdf() && object.isStreamOfType("/XRef")) {
1404
            // As a special case, do not output any extraneous XRef streams in QDF mode. Doing so
1405
            // will confuse fix-qdf, which expects to see only one XRef stream at the end of the
1406
            // file. This case can occur when creating a QDF from a file with object streams when
1407
            // preserving unreferenced objects since the old cross reference streams are not
1408
            // actually referenced by object number.
1409
1.53k
            return;
1410
1.53k
        }
1411
1412
1.64M
        QPDFObjGen og = object.getObjGen();
1413
1.64M
        auto& o = obj[og];
1414
1415
1.64M
        if (o.renumber == 0) {
1416
755k
            if (o.object_stream > 0) {
1417
                // This is in an object stream.  Don't process it here.  Instead, enqueue the object
1418
                // stream.  Object streams always have generation 0.
1419
                // Detect loops by storing invalid object ID -1, which will get overwritten later.
1420
7.36k
                o.renumber = -1;
1421
7.36k
                enqueue(qpdf.getObject(o.object_stream, 0));
1422
748k
            } else {
1423
748k
                object_queue.emplace_back(object);
1424
748k
                o.renumber = next_objid++;
1425
1426
748k
                if (og.getGen() == 0 && object_stream_to_objects.contains(og.getObj())) {
1427
                    // For linearized files, uncompressed objects go at end, and we take care of
1428
                    // assigning numbers to them elsewhere.
1429
27.9k
                    if (!cfg.linearize()) {
1430
5.93k
                        assignCompressedObjectNumbers(og);
1431
5.93k
                    }
1432
720k
                } else if (!cfg.direct_stream_lengths() && object.isStream()) {
1433
                    // reserve next object ID for length
1434
44.5k
                    ++next_objid;
1435
44.5k
                }
1436
748k
            }
1437
755k
        }
1438
1.64M
        return;
1439
1.64M
    }
1440
1441
207M
    if (cfg.linearize()) {
1442
505
        return;
1443
505
    }
1444
1445
207M
    if (Array array = object) {
1446
178M
        for (auto& item: array) {
1447
178M
            enqueue(item);
1448
178M
        }
1449
1.44M
        return;
1450
1.44M
    }
1451
1452
206M
    for (auto const& item: Dictionary(object)) {
1453
3.66M
        if (!item.second.null()) {
1454
2.96M
            enqueue(item.second);
1455
2.96M
        }
1456
3.66M
    }
1457
206M
}
1458
1459
void
1460
impl::Writer::unparseChild(QPDFObjectHandle const& child, size_t level, int flags)
1461
35.7M
{
1462
35.7M
    if (!cfg.linearize()) {
1463
27.1M
        enqueue(child);
1464
27.1M
    }
1465
35.7M
    if (child.indirect()) {
1466
2.07M
        write(obj[child].renumber).write(" 0 R");
1467
33.6M
    } else {
1468
33.6M
        unparseObject(child, level, flags);
1469
33.6M
    }
1470
35.7M
}
1471
1472
void
1473
impl::Writer::writeTrailer(
1474
    trailer_e which, int size, bool xref_stream, qpdf_offset_t prev, int linearization_pass)
1475
151k
{
1476
151k
    auto trailer = trimmed_trailer();
1477
151k
    if (xref_stream) {
1478
58.3k
        cur_data_key.clear();
1479
93.2k
    } else {
1480
93.2k
        write("trailer <<");
1481
93.2k
    }
1482
151k
    write_qdf("\n");
1483
151k
    if (which == t_lin_second) {
1484
58.0k
        write(" /Size ").write(size);
1485
93.5k
    } else {
1486
194k
        for (auto const& [key, value]: trailer) {
1487
194k
            if (value.null()) {
1488
38.2k
                continue;
1489
38.2k
            }
1490
155k
            write_qdf("  ").write_no_qdf(" ").write_name(key).write(" ");
1491
155k
            if (key == "/Size") {
1492
17.0k
                write(size);
1493
17.0k
                if (which == t_lin_first) {
1494
11.2k
                    write(" /Prev ");
1495
11.2k
                    qpdf_offset_t pos = pipeline->getCount();
1496
11.2k
                    write(prev).write(QIntC::to_size(pos - pipeline->getCount() + 21), ' ');
1497
11.2k
                }
1498
138k
            } else {
1499
138k
                unparseChild(value, 1, 0);
1500
138k
            }
1501
155k
            write_qdf("\n");
1502
155k
        }
1503
93.5k
    }
1504
1505
    // Write ID
1506
151k
    write_qdf(" ").write(" /ID [");
1507
151k
    if (linearization_pass == 1) {
1508
59.7k
        std::string original_id1 = getOriginalID1();
1509
59.7k
        if (original_id1.empty()) {
1510
55.2k
            write("<00000000000000000000000000000000>");
1511
55.2k
        } else {
1512
            // Write a string of zeroes equal in length to the representation of the original ID.
1513
            // While writing the original ID would have the same number of bytes, it would cause a
1514
            // change to the deterministic ID generated by older versions of the software that
1515
            // hard-coded the length of the ID to 16 bytes.
1516
4.53k
            size_t len = QPDF_String(original_id1).unparse(true).length() - 2;
1517
4.53k
            write("<").write(len, '0').write(">");
1518
4.53k
        }
1519
59.7k
        write("<00000000000000000000000000000000>");
1520
91.8k
    } else {
1521
91.8k
        if (linearization_pass == 0 && cfg.deterministic_id()) {
1522
17.8k
            computeDeterministicIDData();
1523
17.8k
        }
1524
91.8k
        generateID(encryption.get());
1525
91.8k
        write_string(id1, true).write_string(id2, true);
1526
91.8k
    }
1527
151k
    write("]");
1528
1529
151k
    if (which != t_lin_second) {
1530
        // Write reference to encryption dictionary
1531
93.5k
        if (encryption) {
1532
46.1k
            write(" /Encrypt ").write(encryption_dict_objid).write(" 0 R");
1533
46.1k
        }
1534
93.5k
    }
1535
1536
151k
    write_qdf("\n>>").write_no_qdf(" >>");
1537
151k
}
1538
1539
bool
1540
impl::Writer::will_filter_stream(QPDFObjectHandle stream)
1541
98.1k
{
1542
98.1k
    std::string s;
1543
98.1k
    [[maybe_unused]] auto [filter, ignore1, ignore2] = will_filter_stream(stream, &s);
1544
98.1k
    return filter;
1545
98.1k
}
1546
1547
std::tuple<const bool, const bool, const bool>
1548
impl::Writer::will_filter_stream(QPDFObjectHandle stream, std::string* stream_data)
1549
383k
{
1550
383k
    const bool is_root_metadata = stream.isRootMetadata();
1551
383k
    bool filter = false;
1552
383k
    auto decode_level = cfg.decode_level();
1553
383k
    int encode_flags = 0;
1554
383k
    Dictionary stream_dict = stream.getDict();
1555
1556
383k
    if (stream.getFilterOnWrite()) {
1557
319k
        filter = stream.isDataModified() || cfg.compress_streams() || decode_level != qpdf_dl_none;
1558
319k
        if (cfg.compress_streams()) {
1559
            // Don't filter if the stream is already compressed with FlateDecode. This way we don't
1560
            // make it worse if the original file used a better Flate algorithm, and we don't spend
1561
            // time and CPU cycles uncompressing and recompressing stuff. This can be overridden
1562
            // with setRecompressFlate(true).
1563
275k
            Name Filter = stream_dict["/Filter"];
1564
275k
            if (Filter && !cfg.recompress_flate() && !stream.isDataModified() &&
1565
80.2k
                (Filter == "/FlateDecode" || Filter == "/Fl")) {
1566
38.5k
                filter = false;
1567
38.5k
            }
1568
275k
        }
1569
319k
        if (is_root_metadata && (!encryption || !encryption->getEncryptMetadata())) {
1570
659
            filter = true;
1571
659
            decode_level = qpdf_dl_all;
1572
318k
        } else if (cfg.normalize_content() && normalized_streams.contains(stream)) {
1573
6.23k
            encode_flags = qpdf_ef_normalize;
1574
6.23k
            filter = true;
1575
312k
        } else if (filter && cfg.compress_streams()) {
1576
236k
            encode_flags = qpdf_ef_compress;
1577
236k
        }
1578
319k
    }
1579
1580
    // Disable compression for empty streams to improve compatibility
1581
383k
    if (Integer(stream_dict["/Length"]) == 0) {
1582
16.3k
        filter = true;
1583
16.3k
        encode_flags = 0;
1584
16.3k
    }
1585
1586
477k
    for (bool first_attempt: {true, false}) {
1587
477k
        auto pp_stream_data =
1588
477k
            stream_data ? pipeline_stack.activate(*stream_data) : pipeline_stack.activate(true);
1589
1590
477k
        try {
1591
477k
            if (stream.pipeStreamData(
1592
477k
                    pipeline,
1593
477k
                    filter ? encode_flags : 0,
1594
477k
                    filter ? decode_level : qpdf_dl_none,
1595
477k
                    false,
1596
477k
                    first_attempt)) {
1597
197k
                return {true, encode_flags & qpdf_ef_compress, is_root_metadata};
1598
197k
            }
1599
279k
            if (!filter) {
1600
185k
                break;
1601
185k
            }
1602
279k
        } catch (std::runtime_error& e) {
1603
353
            if (!(filter && first_attempt)) {
1604
118
                throw std::runtime_error(
1605
118
                    "error while getting stream data for " + stream.unparse() + ": " + e.what());
1606
118
            }
1607
235
            stream.warn("error while getting stream data: "s + e.what());
1608
235
            stream.warn("qpdf will attempt to write the damaged stream unchanged");
1609
235
        }
1610
        // Try again
1611
94.4k
        filter = false;
1612
94.4k
        stream.setFilterOnWrite(false);
1613
94.4k
        if (stream_data) {
1614
94.4k
            stream_data->clear();
1615
94.4k
        }
1616
94.4k
    }
1617
185k
    return {false, false, is_root_metadata};
1618
383k
}
1619
1620
void
1621
impl::Writer::unparseObject(
1622
    QPDFObjectHandle object, size_t level, int flags, size_t stream_length, bool compress)
1623
35.4M
{
1624
35.4M
    QPDFObjGen old_og = object.getObjGen();
1625
35.4M
    int child_flags = flags & ~f_stream;
1626
    // For non-qdf, "indent" and "indent_large" are a single space between tokens. For qdf, they
1627
    // include the preceding newline.
1628
35.4M
    std::string indent_large = " ";
1629
35.4M
    if (cfg.qdf()) {
1630
25.9M
        indent_large.append(2 * (level + 1), ' ');
1631
25.9M
        indent_large[0] = '\n';
1632
25.9M
    }
1633
35.4M
    std::string_view indent{indent_large.data(), cfg.qdf() ? indent_large.size() - 2 : 1};
1634
1635
35.4M
    if (auto const tc = object.getTypeCode(); tc == ::ot_array) {
1636
        // Note: PDF spec 1.4 implementation note 121 states that Acrobat requires a space after the
1637
        // [ in the /H key of the linearization parameter dictionary.  We'll do this unconditionally
1638
        // for all arrays because it looks nicer and doesn't make the files that much bigger.
1639
672k
        write("[");
1640
31.5M
        for (auto const& item: object.as_array()) {
1641
31.5M
            write(indent_large);
1642
31.5M
            unparseChild(item, level + 1, child_flags);
1643
31.5M
        }
1644
672k
        write(indent).write("]");
1645
34.7M
    } else if (tc == ::ot_dictionary) {
1646
        // Handle special cases for specific dictionaries.
1647
1648
1.37M
        if (old_og == root_og) {
1649
            // Extensions dictionaries.
1650
1651
            // We have one of several cases:
1652
            //
1653
            // * We need ADBE
1654
            //    - We already have Extensions
1655
            //       - If it has the right ADBE, preserve it
1656
            //       - Otherwise, replace ADBE
1657
            //    - We don't have Extensions: create one from scratch
1658
            // * We don't want ADBE
1659
            //    - We already have Extensions
1660
            //       - If it only has ADBE, remove it
1661
            //       - If it has other things, keep those and remove ADBE
1662
            //    - We have no extensions: no action required
1663
            //
1664
            // Before writing, we guarantee that /Extensions, if present, is direct through the ADBE
1665
            // dictionary, so we can modify in place.
1666
1667
93.1k
            auto extensions = object.getKey("/Extensions");
1668
93.1k
            const bool has_extensions = extensions.isDictionary();
1669
93.1k
            const bool need_extensions_adbe = final_extension_level > 0;
1670
1671
93.1k
            if (has_extensions || need_extensions_adbe) {
1672
                // Make a shallow copy of this object so we can modify it safely without affecting
1673
                // the original. This code has logic to skip certain keys in agreement with
1674
                // prepareFileForWrite and with skip_stream_parameters so that replacing them
1675
                // doesn't leave unreferenced objects in the output. We can use unsafeShallowCopy
1676
                // here because all we are doing is removing or replacing top-level keys.
1677
32.6k
                object = object.unsafeShallowCopy();
1678
32.6k
                if (!has_extensions) {
1679
28.3k
                    extensions = QPDFObjectHandle();
1680
28.3k
                }
1681
1682
32.6k
                const bool have_extensions_adbe = extensions && extensions.hasKey("/ADBE");
1683
32.6k
                const bool have_extensions_other =
1684
32.6k
                    extensions && extensions.getKeys().size() > (have_extensions_adbe ? 1u : 0u);
1685
1686
32.6k
                if (need_extensions_adbe) {
1687
29.9k
                    if (!(have_extensions_other || have_extensions_adbe)) {
1688
                        // We need Extensions and don't have it.  Create it here.
1689
28.3k
                        QTC::TC("qpdf", "QPDFWriter create Extensions", cfg.qdf() ? 0 : 1);
1690
28.3k
                        extensions = object.replaceKeyAndGetNew(
1691
28.3k
                            "/Extensions", QPDFObjectHandle::newDictionary());
1692
28.3k
                    }
1693
29.9k
                } else if (!have_extensions_other) {
1694
                    // We have Extensions dictionary and don't want one.
1695
1.21k
                    if (have_extensions_adbe) {
1696
1.04k
                        QTC::TC("qpdf", "QPDFWriter remove existing Extensions");
1697
1.04k
                        object.removeKey("/Extensions");
1698
1.04k
                        extensions = QPDFObjectHandle(); // uninitialized
1699
1.04k
                    }
1700
1.21k
                }
1701
1702
32.6k
                if (extensions) {
1703
31.6k
                    QTC::TC("qpdf", "QPDFWriter preserve Extensions");
1704
31.6k
                    QPDFObjectHandle adbe = extensions.getKey("/ADBE");
1705
31.6k
                    if (adbe.isDictionary() &&
1706
1.59k
                        adbe.getKey("/BaseVersion").isNameAndEquals("/" + final_pdf_version) &&
1707
989
                        adbe.getKey("/ExtensionLevel").isInteger() &&
1708
980
                        (adbe.getKey("/ExtensionLevel").getIntValue() == final_extension_level)) {
1709
30.8k
                    } else {
1710
30.8k
                        if (need_extensions_adbe) {
1711
29.2k
                            extensions.replaceKey(
1712
29.2k
                                "/ADBE",
1713
29.2k
                                QPDFObjectHandle::parse(
1714
29.2k
                                    "<< /BaseVersion /" + final_pdf_version + " /ExtensionLevel " +
1715
29.2k
                                    std::to_string(final_extension_level) + " >>"));
1716
29.2k
                        } else {
1717
1.66k
                            extensions.removeKey("/ADBE");
1718
1.66k
                        }
1719
30.8k
                    }
1720
31.6k
                }
1721
32.6k
            }
1722
93.1k
        }
1723
1724
        // Stream dictionaries.
1725
1726
1.37M
        if (flags & f_stream) {
1727
            // Suppress /Length since we will write it manually
1728
1729
            // Make a shallow copy of this object so we can modify it safely without affecting the
1730
            // original. This code has logic to skip certain keys in agreement with
1731
            // prepareFileForWrite and with skip_stream_parameters so that replacing them doesn't
1732
            // leave unreferenced objects in the output. We can use unsafeShallowCopy here because
1733
            // all we are doing is removing or replacing top-level keys.
1734
285k
            object = object.unsafeShallowCopy();
1735
1736
285k
            object.removeKey("/Length");
1737
1738
            // If /DecodeParms is an empty list, remove it.
1739
285k
            if (object.getKey("/DecodeParms").empty()) {
1740
273k
                object.removeKey("/DecodeParms");
1741
273k
            }
1742
1743
285k
            if (flags & f_filtered) {
1744
                // We will supply our own filter and decode parameters.
1745
145k
                object.removeKey("/Filter");
1746
145k
                object.removeKey("/DecodeParms");
1747
145k
            } else {
1748
                // Make sure, no matter what else we have, that we don't have /Crypt in the output
1749
                // filters.
1750
139k
                QPDFObjectHandle filter = object.getKey("/Filter");
1751
139k
                QPDFObjectHandle decode_parms = object.getKey("/DecodeParms");
1752
139k
                if (filter.isOrHasName("/Crypt")) {
1753
3.24k
                    if (filter.isName()) {
1754
359
                        object.removeKey("/Filter");
1755
359
                        object.removeKey("/DecodeParms");
1756
2.88k
                    } else {
1757
2.88k
                        int idx = 0;
1758
115k
                        for (auto const& item: filter.as_array()) {
1759
115k
                            if (item.isNameAndEquals("/Crypt")) {
1760
                                // If filter is an array, then the code in QPDF_Stream has already
1761
                                // verified that DecodeParms and Filters are arrays of the same
1762
                                // length, but if they weren't for some reason, eraseItem does type
1763
                                // and bounds checking. Fuzzing tells us that this can actually
1764
                                // happen.
1765
2.88k
                                filter.eraseItem(idx);
1766
2.88k
                                decode_parms.eraseItem(idx);
1767
2.88k
                                break;
1768
2.88k
                            }
1769
112k
                            ++idx;
1770
112k
                        }
1771
2.88k
                    }
1772
3.24k
                }
1773
139k
            }
1774
285k
        }
1775
1776
1.37M
        write("<<");
1777
1778
4.94M
        for (auto const& [key, value]: object.as_dictionary()) {
1779
4.94M
            if (!value.null()) {
1780
4.04M
                write(indent_large).write_name(key).write(" ");
1781
4.04M
                if (key == "/Contents" && object.isDictionaryOfType("/Sig") &&
1782
362
                    object.hasKey("/ByteRange")) {
1783
343
                    QTC::TC("qpdf", "QPDFWriter no encryption sig contents");
1784
343
                    unparseChild(value, level + 1, child_flags | f_hex_string | f_no_encryption);
1785
4.04M
                } else {
1786
4.04M
                    unparseChild(value, level + 1, child_flags);
1787
4.04M
                }
1788
4.04M
            }
1789
4.94M
        }
1790
1791
1.37M
        if (flags & f_stream) {
1792
283k
            write(indent_large).write("/Length ");
1793
1794
283k
            if (cfg.direct_stream_lengths()) {
1795
239k
                write(stream_length);
1796
239k
            } else {
1797
44.3k
                write(cur_stream_length_id).write(" 0 R");
1798
44.3k
            }
1799
283k
            if (compress && (flags & f_filtered)) {
1800
123k
                write(indent_large).write("/Filter /FlateDecode");
1801
123k
            }
1802
283k
        }
1803
1804
1.37M
        write(indent).write(">>");
1805
33.4M
    } else if (tc == ::ot_stream) {
1806
        // Write stream data to a buffer.
1807
285k
        if (!cfg.direct_stream_lengths()) {
1808
44.5k
            cur_stream_length_id = obj[old_og].renumber + 1;
1809
44.5k
        }
1810
1811
285k
        flags |= f_stream;
1812
285k
        std::string stream_data;
1813
285k
        auto [filter, compress_stream, is_root_metadata] = will_filter_stream(object, &stream_data);
1814
285k
        if (filter) {
1815
145k
            flags |= f_filtered;
1816
145k
        }
1817
285k
        QPDFObjectHandle stream_dict = object.getDict();
1818
1819
285k
        cur_stream_length = stream_data.size();
1820
285k
        if (is_root_metadata && encryption && !encryption->getEncryptMetadata()) {
1821
            // Don't encrypt stream data for the metadata stream
1822
0
            cur_data_key.clear();
1823
0
        }
1824
285k
        adjustAESStreamLength(cur_stream_length);
1825
285k
        unparseObject(stream_dict, 0, flags, cur_stream_length, compress_stream);
1826
285k
        char last_char = stream_data.empty() ? '\0' : stream_data.back();
1827
285k
        write("\nstream\n").write_encrypted(stream_data);
1828
285k
        added_newline = cfg.newline_before_endstream() || (cfg.qdf() && last_char != '\n');
1829
285k
        write(added_newline ? "\nendstream" : "endstream");
1830
33.1M
    } else if (tc == ::ot_string) {
1831
544k
        std::string val;
1832
544k
        if (encryption && !(flags & f_in_ostream) && !(flags & f_no_encryption) &&
1833
145k
            !cur_data_key.empty()) {
1834
100k
            val = object.getStringValue();
1835
100k
            if (cfg.encrypt_use_aes()) {
1836
68.2k
                Pl_Buffer bufpl("encrypted string");
1837
68.2k
                Pl_AES_PDF pl("aes encrypt string", &bufpl, true, cur_data_key);
1838
68.2k
                pl.writeString(val);
1839
68.2k
                pl.finish();
1840
68.2k
                val = QPDF_String(bufpl.getString()).unparse(true);
1841
68.2k
            } else {
1842
32.5k
                auto tmp_ph = QUtil::make_unique_cstr(val);
1843
32.5k
                char* tmp = tmp_ph.get();
1844
32.5k
                size_t vlen = val.length();
1845
32.5k
                RC4 rc4(
1846
32.5k
                    QUtil::unsigned_char_pointer(cur_data_key),
1847
32.5k
                    QIntC::to_int(cur_data_key.length()));
1848
32.5k
                auto data = QUtil::unsigned_char_pointer(tmp);
1849
32.5k
                rc4.process(data, vlen, data);
1850
32.5k
                val = QPDF_String(std::string(tmp, vlen)).unparse();
1851
32.5k
            }
1852
444k
        } else if (flags & f_hex_string) {
1853
402
            val = QPDF_String(object.getStringValue()).unparse(true);
1854
443k
        } else {
1855
443k
            val = object.unparseResolved();
1856
443k
        }
1857
544k
        write(val);
1858
32.5M
    } else {
1859
32.5M
        write(object.unparseResolved());
1860
32.5M
    }
1861
35.4M
}
1862
1863
void
1864
impl::Writer::writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj)
1865
88.6k
{
1866
88.6k
    qpdf_assert_debug(first_obj > 0);
1867
88.6k
    bool is_first = true;
1868
88.6k
    auto id = std::to_string(first_obj) + ' ';
1869
1.00M
    for (auto& offset: offsets) {
1870
1.00M
        if (is_first) {
1871
88.6k
            is_first = false;
1872
913k
        } else {
1873
913k
            write_qdf("\n").write_no_qdf(" ");
1874
913k
        }
1875
1.00M
        write(id);
1876
1.00M
        util::increment(id, 1);
1877
1.00M
        write(offset);
1878
1.00M
    }
1879
88.6k
    write("\n");
1880
88.6k
}
1881
1882
void
1883
impl::Writer::writeObjectStream(QPDFObjectHandle object)
1884
44.3k
{
1885
    // Note: object might be null if this is a place-holder for an object stream that we are
1886
    // generating from scratch.
1887
1888
44.3k
    QPDFObjGen old_og = object.getObjGen();
1889
44.3k
    qpdf_assert_debug(old_og.getGen() == 0);
1890
44.3k
    int old_id = old_og.getObj();
1891
44.3k
    int new_stream_id = obj[old_og].renumber;
1892
1893
44.3k
    std::vector<qpdf_offset_t> offsets;
1894
44.3k
    qpdf_offset_t first = 0;
1895
1896
    // Generate stream itself.  We have to do this in two passes so we can calculate offsets in the
1897
    // first pass.
1898
44.3k
    std::string stream_buffer_pass1;
1899
44.3k
    std::string stream_buffer_pass2;
1900
44.3k
    int first_obj = -1;
1901
44.3k
    const bool compressed = cfg.compress_streams() && !cfg.qdf();
1902
44.3k
    {
1903
        // Pass 1
1904
44.3k
        auto pp_ostream_pass1 = pipeline_stack.activate(stream_buffer_pass1);
1905
1906
44.3k
        int count = -1;
1907
501k
        for (auto const& og: object_stream_to_objects[old_id]) {
1908
501k
            ++count;
1909
501k
            int new_o = obj[og].renumber;
1910
501k
            if (first_obj == -1) {
1911
44.3k
                first_obj = new_o;
1912
44.3k
            }
1913
501k
            if (cfg.qdf()) {
1914
46.6k
                write("%% Object stream: object ").write(new_o).write(", index ").write(count);
1915
46.6k
                if (!cfg.no_original_object_ids()) {
1916
46.6k
                    write("; original object ID: ").write(og.getObj());
1917
                    // For compatibility, only write the generation if non-zero.  While object
1918
                    // streams only allow objects with generation 0, if we are generating object
1919
                    // streams, the old object could have a non-zero generation.
1920
46.6k
                    if (og.getGen() != 0) {
1921
0
                        write(" ").write(og.getGen());
1922
0
                    }
1923
46.6k
                }
1924
46.6k
                write("\n");
1925
46.6k
            }
1926
1927
501k
            offsets.push_back(pipeline->getCount());
1928
            // To avoid double-counting objects being written in object streams for progress
1929
            // reporting, decrement in pass 1.
1930
501k
            indicateProgress(true, false);
1931
1932
501k
            QPDFObjectHandle obj_to_write = qpdf.getObject(og);
1933
501k
            if (obj_to_write.isStream()) {
1934
                // This condition occurred in a fuzz input. Ideally we should block it at parse
1935
                // time, but it's not clear to me how to construct a case for this.
1936
4
                obj_to_write.warn("stream found inside object stream; treating as null");
1937
4
                obj_to_write = QPDFObjectHandle::newNull();
1938
4
            }
1939
501k
            writeObject(obj_to_write, count);
1940
1941
501k
            new_obj[new_o].xref = QPDFXRefEntry(new_stream_id, count);
1942
501k
        }
1943
44.3k
    }
1944
44.3k
    {
1945
        // Adjust offsets to skip over comment before first object
1946
44.3k
        first = offsets.at(0);
1947
501k
        for (auto& iter: offsets) {
1948
501k
            iter -= first;
1949
501k
        }
1950
1951
        // Take one pass at writing pairs of numbers so we can get their size information
1952
44.3k
        {
1953
44.3k
            auto pp_discard = pipeline_stack.activate(true);
1954
44.3k
            writeObjectStreamOffsets(offsets, first_obj);
1955
44.3k
            first += pipeline->getCount();
1956
44.3k
        }
1957
1958
        // Set up a stream to write the stream data into a buffer.
1959
44.3k
        auto pp_ostream = pipeline_stack.activate(stream_buffer_pass2);
1960
1961
44.3k
        writeObjectStreamOffsets(offsets, first_obj);
1962
44.3k
        write(stream_buffer_pass1);
1963
44.3k
        stream_buffer_pass1.clear();
1964
44.3k
        stream_buffer_pass1.shrink_to_fit();
1965
44.3k
        if (compressed) {
1966
38.6k
            stream_buffer_pass2 = pl::pipe<Pl_Flate>(stream_buffer_pass2, Pl_Flate::a_deflate);
1967
38.6k
        }
1968
44.3k
    }
1969
1970
    // Write the object
1971
44.3k
    openObject(new_stream_id);
1972
44.3k
    setDataKey(new_stream_id);
1973
44.3k
    write("<<").write_qdf("\n ").write(" /Type /ObjStm").write_qdf("\n ");
1974
44.3k
    size_t length = stream_buffer_pass2.size();
1975
44.3k
    adjustAESStreamLength(length);
1976
44.3k
    write(" /Length ").write(length).write_qdf("\n ");
1977
44.3k
    if (compressed) {
1978
38.6k
        write(" /Filter /FlateDecode");
1979
38.6k
    }
1980
44.3k
    write(" /N ").write(offsets.size()).write_qdf("\n ").write(" /First ").write(first);
1981
44.3k
    if (!object.null()) {
1982
        // If the original object has an /Extends key, preserve it.
1983
3.98k
        QPDFObjectHandle dict = object.getDict();
1984
3.98k
        QPDFObjectHandle extends = dict.getKey("/Extends");
1985
3.98k
        if (extends.isIndirect()) {
1986
1.30k
            write_qdf("\n ").write(" /Extends ");
1987
1.30k
            unparseChild(extends, 1, f_in_ostream);
1988
1.30k
        }
1989
3.98k
    }
1990
44.3k
    write_qdf("\n").write_no_qdf(" ").write(">>\nstream\n").write_encrypted(stream_buffer_pass2);
1991
44.3k
    write(cfg.newline_before_endstream() ? "\nendstream" : "endstream");
1992
44.3k
    if (encryption) {
1993
9.34k
        cur_data_key.clear();
1994
9.34k
    }
1995
44.3k
    closeObject(new_stream_id);
1996
44.3k
}
1997
1998
void
1999
impl::Writer::writeObject(QPDFObjectHandle object, int object_stream_index)
2000
1.53M
{
2001
1.53M
    QPDFObjGen old_og = object.getObjGen();
2002
2003
1.53M
    if (object_stream_index == -1 && old_og.getGen() == 0 &&
2004
1.01M
        object_stream_to_objects.contains(old_og.getObj())) {
2005
44.3k
        writeObjectStream(object);
2006
44.3k
        return;
2007
44.3k
    }
2008
2009
1.48M
    indicateProgress(false, false);
2010
1.48M
    auto new_id = obj[old_og].renumber;
2011
1.48M
    if (cfg.qdf()) {
2012
206k
        if (page_object_to_seq.contains(old_og)) {
2013
22.6k
            write("%% Page ").write(page_object_to_seq[old_og]).write("\n");
2014
22.6k
        }
2015
206k
        if (contents_to_page_seq.contains(old_og)) {
2016
16.7k
            write("%% Contents for page ").write(contents_to_page_seq[old_og]).write("\n");
2017
16.7k
        }
2018
206k
    }
2019
1.48M
    if (object_stream_index == -1) {
2020
985k
        if (cfg.qdf() && !cfg.no_original_object_ids()) {
2021
159k
            write("%% Original object ID: ").write(object.getObjGen().unparse(' ')).write("\n");
2022
159k
        }
2023
985k
        openObject(new_id);
2024
985k
        setDataKey(new_id);
2025
985k
        unparseObject(object, 0, 0);
2026
985k
        cur_data_key.clear();
2027
985k
        closeObject(new_id);
2028
985k
    } else {
2029
501k
        unparseObject(object, 0, f_in_ostream);
2030
501k
        write("\n");
2031
501k
    }
2032
2033
1.48M
    if (!cfg.direct_stream_lengths() && object.isStream()) {
2034
44.3k
        if (cfg.qdf()) {
2035
44.3k
            if (added_newline) {
2036
28.5k
                write("%QDF: ignore_newline\n");
2037
28.5k
            }
2038
44.3k
        }
2039
44.3k
        openObject(new_id + 1);
2040
44.3k
        write(cur_stream_length);
2041
44.3k
        closeObject(new_id + 1);
2042
44.3k
    }
2043
1.48M
}
2044
2045
std::string
2046
impl::Writer::getOriginalID1()
2047
126k
{
2048
126k
    if (String id0 = qpdf.getTrailer()["/ID"][0]) {
2049
9.86k
        return id0;
2050
9.86k
    }
2051
116k
    return "";
2052
126k
}
2053
2054
void
2055
impl::Writer::generateID(bool encrypted)
2056
126k
{
2057
    // Generate the ID lazily so that we can handle the user's preference to use static or
2058
    // deterministic ID generation.
2059
2060
126k
    if (!id2.empty()) {
2061
59.5k
        return;
2062
59.5k
    }
2063
2064
66.7k
    QPDFObjectHandle trailer = qpdf.getTrailer();
2065
2066
66.7k
    std::string result;
2067
2068
66.7k
    if (cfg.static_id()) {
2069
        // For test suite use only...
2070
34.3k
        static unsigned char tmp[] = {
2071
34.3k
            0x31,
2072
34.3k
            0x41,
2073
34.3k
            0x59,
2074
34.3k
            0x26,
2075
34.3k
            0x53,
2076
34.3k
            0x58,
2077
34.3k
            0x97,
2078
34.3k
            0x93,
2079
34.3k
            0x23,
2080
34.3k
            0x84,
2081
34.3k
            0x62,
2082
34.3k
            0x64,
2083
34.3k
            0x33,
2084
34.3k
            0x83,
2085
34.3k
            0x27,
2086
34.3k
            0x95,
2087
34.3k
            0x00};
2088
34.3k
        result = reinterpret_cast<char*>(tmp);
2089
34.3k
    } else {
2090
        // The PDF specification has guidelines for creating IDs, but it states clearly that the
2091
        // only thing that's really important is that it is very likely to be unique.  We can't
2092
        // really follow the guidelines in the spec exactly because we haven't written the file yet.
2093
        // This scheme should be fine though.  The deterministic ID case uses a digest of a
2094
        // sufficient portion of the file's contents such no two non-matching files would match in
2095
        // the subsets used for this computation.  Note that we explicitly omit the filename from
2096
        // the digest calculation for deterministic ID so that the same file converted with qpdf, in
2097
        // that case, would have the same ID regardless of the output file's name.
2098
2099
32.3k
        std::string seed;
2100
32.3k
        if (cfg.deterministic_id()) {
2101
32.3k
            if (encrypted) {
2102
157
                throw std::runtime_error(
2103
157
                    "QPDFWriter: unable to generated a deterministic ID because the file to be "
2104
157
                    "written is encrypted (even though the file may not require a password)");
2105
157
            }
2106
32.2k
            if (deterministic_id_data.empty()) {
2107
0
                throw std::logic_error(
2108
0
                    "INTERNAL ERROR: QPDFWriter::generateID has no data for deterministic ID");
2109
0
            }
2110
32.2k
            seed += deterministic_id_data;
2111
32.2k
        } else {
2112
0
            seed += std::to_string(QUtil::get_current_time());
2113
0
            seed += filename;
2114
0
            seed += " ";
2115
0
        }
2116
32.2k
        seed += " QPDF ";
2117
32.2k
        if (trailer.hasKey("/Info")) {
2118
21.9k
            for (auto const& item: trailer.getKey("/Info").as_dictionary()) {
2119
21.9k
                if (item.second.isString()) {
2120
5.88k
                    seed += " ";
2121
5.88k
                    seed += item.second.getStringValue();
2122
5.88k
                }
2123
21.9k
            }
2124
1.09k
        }
2125
2126
32.2k
        MD5 md5;
2127
32.2k
        md5.encodeString(seed.c_str());
2128
32.2k
        MD5::Digest digest;
2129
32.2k
        md5.digest(digest);
2130
32.2k
        result = std::string(reinterpret_cast<char*>(digest), sizeof(MD5::Digest));
2131
32.2k
    }
2132
2133
    // If /ID already exists, follow the spec: use the original first word and generate a new second
2134
    // word.  Otherwise, we'll use the generated ID for both.
2135
2136
66.5k
    id2 = result;
2137
    // Note: keep /ID from old file even if --static-id was given.
2138
66.5k
    id1 = getOriginalID1();
2139
66.5k
    if (id1.empty()) {
2140
61.2k
        id1 = id2;
2141
61.2k
    }
2142
66.5k
}
2143
2144
void
2145
impl::Writer::initializeSpecialStreams()
2146
18.3k
{
2147
    // Mark all page content streams in case we are filtering or normalizing.
2148
18.3k
    int num = 0;
2149
23.2k
    for (auto& page: pages) {
2150
23.2k
        page_object_to_seq[page.getObjGen()] = ++num;
2151
23.2k
        QPDFObjectHandle contents = page.getKey("/Contents");
2152
23.2k
        std::vector<QPDFObjGen> contents_objects;
2153
23.2k
        if (contents.isArray()) {
2154
1.10k
            int n = static_cast<int>(contents.size());
2155
341k
            for (int i = 0; i < n; ++i) {
2156
340k
                contents_objects.push_back(contents.getArrayItem(i).getObjGen());
2157
340k
            }
2158
22.1k
        } else if (contents.isStream()) {
2159
3.75k
            contents_objects.push_back(contents.getObjGen());
2160
3.75k
        }
2161
2162
344k
        for (auto const& c: contents_objects) {
2163
344k
            contents_to_page_seq[c] = num;
2164
344k
            normalized_streams.insert(c);
2165
344k
        }
2166
23.2k
    }
2167
18.3k
}
2168
2169
void
2170
impl::Writer::preserveObjectStreams()
2171
36.3k
{
2172
36.3k
    auto const& xref = objects.xref_table();
2173
    // Our object_to_object_stream map has to map ObjGen -> ObjGen since we may be generating object
2174
    // streams out of old objects that have generation numbers greater than zero. However in an
2175
    // existing PDF, all object stream objects and all objects in them must have generation 0
2176
    // because the PDF spec does not provide any way to do otherwise. This code filters out objects
2177
    // that are not allowed to be in object streams. In addition to removing objects that were
2178
    // erroneously included in object streams in the source PDF, it also prevents unreferenced
2179
    // objects from being included.
2180
36.3k
    auto end = xref.cend();
2181
36.3k
    obj.streams_empty = true;
2182
36.3k
    if (cfg.preserve_unreferenced()) {
2183
0
        for (auto iter = xref.cbegin(); iter != end; ++iter) {
2184
0
            if (iter->second.getType() == 2) {
2185
                // Pdf contains object streams.
2186
0
                obj.streams_empty = false;
2187
0
                obj[iter->first].object_stream = iter->second.getObjStreamNumber();
2188
0
            }
2189
0
        }
2190
36.3k
    } else {
2191
        // Start by scanning for first compressed object in case we don't have any object streams to
2192
        // process.
2193
362k
        for (auto iter = xref.cbegin(); iter != end; ++iter) {
2194
330k
            if (iter->second.getType() == 2) {
2195
                // Pdf contains object streams.
2196
4.40k
                obj.streams_empty = false;
2197
4.40k
                auto eligible = objects.compressible_set();
2198
                // The object pointed to by iter may be a previous generation, in which case it is
2199
                // removed by compressible_set. We need to restart the loop (while the object
2200
                // table may contain multiple generations of an object).
2201
1.26M
                for (iter = xref.cbegin(); iter != end; ++iter) {
2202
1.25M
                    if (iter->second.getType() == 2) {
2203
1.16M
                        auto id = static_cast<size_t>(iter->first.getObj());
2204
1.16M
                        if (id < eligible.size() && eligible[id]) {
2205
158k
                            obj[iter->first].object_stream = iter->second.getObjStreamNumber();
2206
1.00M
                        } else {
2207
1.00M
                            QTC::TC("qpdf", "QPDFWriter exclude from object stream");
2208
1.00M
                        }
2209
1.16M
                    }
2210
1.25M
                }
2211
4.40k
                return;
2212
4.40k
            }
2213
330k
        }
2214
36.3k
    }
2215
36.3k
}
2216
2217
void
2218
impl::Writer::generateObjectStreams()
2219
18.9k
{
2220
    // Basic strategy: make a list of objects that can go into an object stream.  Then figure out
2221
    // how many object streams are needed so that we can distribute objects approximately evenly
2222
    // without having any object stream exceed 100 members.  We don't have to worry about linearized
2223
    // files here -- if the file is linearized, we take care of excluding things that aren't allowed
2224
    // here later.
2225
2226
    // This code doesn't do anything with /Extends.
2227
2228
18.9k
    auto eligible = objects.compressible_vector();
2229
18.9k
    size_t n_object_streams = (eligible.size() + 99U) / 100U;
2230
2231
18.9k
    initializeTables(2U * n_object_streams);
2232
18.9k
    if (n_object_streams == 0) {
2233
55
        obj.streams_empty = true;
2234
55
        return;
2235
55
    }
2236
18.8k
    size_t n_per = eligible.size() / n_object_streams;
2237
18.8k
    if (n_per * n_object_streams < eligible.size()) {
2238
233
        ++n_per;
2239
233
    }
2240
18.8k
    unsigned int n = 0;
2241
18.8k
    int cur_ostream = qpdf.newIndirectNull().getObjectID();
2242
213k
    for (auto const& item: eligible) {
2243
213k
        if (n == n_per) {
2244
1.12k
            n = 0;
2245
            // Construct a new null object as the "original" object stream.  The rest of the code
2246
            // knows that this means we're creating the object stream from scratch.
2247
1.12k
            cur_ostream = qpdf.newIndirectNull().getObjectID();
2248
1.12k
        }
2249
213k
        auto& o = obj[item];
2250
213k
        o.object_stream = cur_ostream;
2251
213k
        o.gen = item.getGen();
2252
213k
        ++n;
2253
213k
    }
2254
18.8k
}
2255
2256
Dictionary
2257
impl::Writer::trimmed_trailer()
2258
185k
{
2259
    // Remove keys from the trailer that necessarily have to be replaced when writing the file.
2260
2261
185k
    Dictionary trailer = qpdf.getTrailer().unsafeShallowCopy();
2262
2263
    // Remove encryption keys
2264
185k
    trailer.erase("/ID");
2265
185k
    trailer.erase("/Encrypt");
2266
2267
    // Remove modification information
2268
185k
    trailer.erase("/Prev");
2269
2270
    // Remove all trailer keys that potentially come from a cross-reference stream
2271
185k
    trailer.erase("/Index");
2272
185k
    trailer.erase("/W");
2273
185k
    trailer.erase("/Length");
2274
185k
    trailer.erase("/Filter");
2275
185k
    trailer.erase("/DecodeParms");
2276
185k
    trailer.erase("/Type");
2277
185k
    trailer.erase("/XRefStm");
2278
2279
185k
    return trailer;
2280
185k
}
2281
2282
// Make document extension level information direct as required by the spec.
2283
void
2284
impl::Writer::prepareFileForWrite()
2285
71.1k
{
2286
71.1k
    qpdf.fixDanglingReferences();
2287
71.1k
    auto root = qpdf.getRoot();
2288
71.1k
    auto oh = root.getKey("/Extensions");
2289
71.1k
    if (oh.isDictionary()) {
2290
3.64k
        const bool extensions_indirect = oh.isIndirect();
2291
3.64k
        if (extensions_indirect) {
2292
1.37k
            QTC::TC("qpdf", "QPDFWriter make Extensions direct");
2293
1.37k
            oh = root.replaceKeyAndGetNew("/Extensions", oh.shallowCopy());
2294
1.37k
        }
2295
3.64k
        if (oh.hasKey("/ADBE")) {
2296
2.33k
            auto adbe = oh.getKey("/ADBE");
2297
2.33k
            if (adbe.isIndirect()) {
2298
1.58k
                QTC::TC("qpdf", "QPDFWriter make ADBE direct", extensions_indirect ? 0 : 1);
2299
1.58k
                adbe.makeDirect();
2300
1.58k
                oh.replaceKey("/ADBE", adbe);
2301
1.58k
            }
2302
2.33k
        }
2303
3.64k
    }
2304
71.1k
}
2305
2306
void
2307
impl::Writer::initializeTables(size_t extra)
2308
71.5k
{
2309
71.5k
    auto size = objects.table_size() + 100u + extra;
2310
71.5k
    obj.resize(size);
2311
71.5k
    new_obj.resize(size);
2312
71.5k
}
2313
2314
void
2315
impl::Writer::doWriteSetup()
2316
71.7k
{
2317
71.7k
    if (did_write_setup) {
2318
0
        return;
2319
0
    }
2320
71.7k
    did_write_setup = true;
2321
2322
    // Do preliminary setup
2323
2324
71.7k
    if (cfg.linearize()) {
2325
37.0k
        cfg.qdf(false);
2326
37.0k
    }
2327
2328
71.7k
    if (cfg.pclm()) {
2329
0
        encryption = nullptr;
2330
0
    }
2331
2332
71.7k
    if (encryption) {
2333
        // Encryption has been explicitly set
2334
34.3k
        cfg.preserve_encryption(false);
2335
37.4k
    } else if (cfg.normalize_content() || cfg.pclm()) {
2336
        // Encryption makes looking at contents pretty useless.  If the user explicitly encrypted
2337
        // though, we still obey that.
2338
18.3k
        cfg.preserve_encryption(false);
2339
18.3k
    }
2340
2341
71.7k
    if (cfg.preserve_encryption()) {
2342
19.0k
        copyEncryptionParameters(qpdf);
2343
19.0k
    }
2344
2345
71.7k
    if (!cfg.forced_pdf_version().empty()) {
2346
0
        int major = 0;
2347
0
        int minor = 0;
2348
0
        parseVersion(cfg.forced_pdf_version(), major, minor);
2349
0
        disableIncompatibleEncryption(major, minor, cfg.forced_extension_level());
2350
0
        if (compareVersions(major, minor, 1, 5) < 0) {
2351
0
            cfg.object_streams(qpdf_o_disable);
2352
0
        }
2353
0
    }
2354
2355
71.7k
    if (cfg.qdf() || cfg.normalize_content()) {
2356
18.3k
        initializeSpecialStreams();
2357
18.3k
    }
2358
2359
71.7k
    switch (cfg.object_streams()) {
2360
16.3k
    case qpdf_o_disable:
2361
16.3k
        initializeTables();
2362
16.3k
        obj.streams_empty = true;
2363
16.3k
        break;
2364
2365
36.3k
    case qpdf_o_preserve:
2366
36.3k
        initializeTables();
2367
36.3k
        preserveObjectStreams();
2368
36.3k
        break;
2369
2370
18.9k
    case qpdf_o_generate:
2371
18.9k
        generateObjectStreams();
2372
18.9k
        break;
2373
71.7k
    }
2374
2375
71.5k
    if (!obj.streams_empty) {
2376
23.2k
        if (cfg.linearize()) {
2377
            // Page dictionaries are not allowed to be compressed objects.
2378
34.7k
            for (auto& page: pages) {
2379
34.7k
                if (obj[page].object_stream > 0) {
2380
28.8k
                    obj[page].object_stream = 0;
2381
28.8k
                }
2382
34.7k
            }
2383
21.2k
        }
2384
2385
23.2k
        if (cfg.linearize() || encryption) {
2386
            // The document catalog is not allowed to be compressed in linearized files either.
2387
            // It also appears that Adobe Reader 8.0.0 has a bug that prevents it from being able to
2388
            // handle encrypted files with compressed document catalogs, so we disable them in that
2389
            // case as well.
2390
21.2k
            if (obj[root_og].object_stream > 0) {
2391
16.6k
                obj[root_og].object_stream = 0;
2392
16.6k
            }
2393
21.2k
        }
2394
2395
        // Generate reverse mapping from object stream to objects
2396
15.8M
        obj.forEach([this](auto id, auto const& item) -> void {
2397
15.8M
            if (item.object_stream > 0) {
2398
325k
                auto& vec = object_stream_to_objects[item.object_stream];
2399
325k
                vec.emplace_back(id, item.gen);
2400
325k
                if (max_ostream_index < vec.size()) {
2401
121k
                    ++max_ostream_index;
2402
121k
                }
2403
325k
            }
2404
15.8M
        });
2405
23.2k
        --max_ostream_index;
2406
2407
23.2k
        if (object_stream_to_objects.empty()) {
2408
3.81k
            obj.streams_empty = true;
2409
19.4k
        } else {
2410
19.4k
            setMinimumPDFVersion("1.5");
2411
19.4k
        }
2412
23.2k
    }
2413
2414
71.5k
    setMinimumPDFVersion(qpdf.getPDFVersion(), qpdf.getExtensionLevel());
2415
71.5k
    final_pdf_version = min_pdf_version;
2416
71.5k
    final_extension_level = min_extension_level;
2417
71.5k
    if (!cfg.forced_pdf_version().empty()) {
2418
0
        final_pdf_version = cfg.forced_pdf_version();
2419
0
        final_extension_level = cfg.forced_extension_level();
2420
0
    }
2421
71.5k
}
2422
2423
void
2424
QPDFWriter::write()
2425
71.7k
{
2426
71.7k
    m->write();
2427
71.7k
}
2428
2429
void
2430
impl::Writer::write()
2431
71.7k
{
2432
71.7k
    doWriteSetup();
2433
2434
    // Set up progress reporting. For linearized files, we write two passes. events_expected is an
2435
    // approximation, but it's good enough for progress reporting, which is mostly a guess anyway.
2436
71.7k
    events_expected = QIntC::to_int(qpdf.getObjectCount() * (cfg.linearize() ? 2 : 1));
2437
2438
71.7k
    prepareFileForWrite();
2439
2440
71.7k
    if (cfg.linearize()) {
2441
36.3k
        writeLinearized();
2442
36.3k
    } else {
2443
35.3k
        writeStandard();
2444
35.3k
    }
2445
2446
71.7k
    pipeline->finish();
2447
71.7k
    if (close_file) {
2448
0
        fclose(file);
2449
0
    }
2450
71.7k
    file = nullptr;
2451
71.7k
    if (buffer_pipeline) {
2452
0
        output_buffer = buffer_pipeline->getBuffer();
2453
0
        buffer_pipeline = nullptr;
2454
0
    }
2455
71.7k
    indicateProgress(false, true);
2456
71.7k
}
2457
2458
QPDFObjGen
2459
QPDFWriter::getRenumberedObjGen(QPDFObjGen og)
2460
0
{
2461
0
    return {m->obj[og].renumber, 0};
2462
0
}
2463
2464
std::map<QPDFObjGen, QPDFXRefEntry>
2465
QPDFWriter::getWrittenXRefTable()
2466
0
{
2467
0
    return m->getWrittenXRefTable();
2468
0
}
2469
2470
std::map<QPDFObjGen, QPDFXRefEntry>
2471
impl::Writer::getWrittenXRefTable()
2472
0
{
2473
0
    std::map<QPDFObjGen, QPDFXRefEntry> result;
2474
2475
0
    auto it = result.begin();
2476
0
    new_obj.forEach([&it, &result](auto id, auto const& item) -> void {
2477
0
        if (item.xref.getType() != 0) {
2478
0
            it = result.emplace_hint(it, QPDFObjGen(id, 0), item.xref);
2479
0
        }
2480
0
    });
2481
0
    return result;
2482
0
}
2483
2484
void
2485
impl::Writer::enqueuePart(std::vector<QPDFObjectHandle>& part)
2486
160k
{
2487
421k
    for (auto const& oh: part) {
2488
421k
        enqueue(oh);
2489
421k
    }
2490
160k
}
2491
2492
void
2493
impl::Writer::writeEncryptionDictionary()
2494
46.0k
{
2495
46.0k
    encryption_dict_objid = openObject(encryption_dict_objid);
2496
46.0k
    auto& enc = *encryption;
2497
46.0k
    auto const V = enc.getV();
2498
2499
46.0k
    write("<<");
2500
46.0k
    if (V >= 4) {
2501
30.0k
        write(" /CF << /StdCF << /AuthEvent /DocOpen /CFM ");
2502
30.0k
        write(cfg.encrypt_use_aes() ? (V < 5 ? "/AESV2" : "/AESV3") : "/V2");
2503
        // The PDF spec says the /Length key is optional, but the PDF previewer on some versions of
2504
        // MacOS won't open encrypted files without it.
2505
30.0k
        write(V < 5 ? " /Length 16 >> >>" : " /Length 32 >> >>");
2506
30.0k
        if (!encryption->getEncryptMetadata()) {
2507
0
            write(" /EncryptMetadata false");
2508
0
        }
2509
30.0k
    }
2510
46.0k
    write(" /Filter /Standard /Length ").write(enc.getLengthBytes() * 8);
2511
46.0k
    write(" /O ").write_string(enc.getO(), true);
2512
46.0k
    if (V >= 4) {
2513
30.0k
        write(" /OE ").write_string(enc.getOE(), true);
2514
30.0k
    }
2515
46.0k
    write(" /P ").write(enc.getP());
2516
46.0k
    if (V >= 5) {
2517
30.0k
        write(" /Perms ").write_string(enc.getPerms(), true);
2518
30.0k
    }
2519
46.0k
    write(" /R ").write(enc.getR());
2520
2521
46.0k
    if (V >= 4) {
2522
30.0k
        write(" /StmF /StdCF /StrF /StdCF");
2523
30.0k
    }
2524
46.0k
    write(" /U ").write_string(enc.getU(), true);
2525
46.0k
    if (V >= 4) {
2526
30.0k
        write(" /UE ").write_string(enc.getUE(), true);
2527
30.0k
    }
2528
46.0k
    write(" /V ").write(enc.getV()).write(" >>");
2529
46.0k
    closeObject(encryption_dict_objid);
2530
46.0k
}
2531
2532
std::string
2533
QPDFWriter::getFinalVersion()
2534
0
{
2535
0
    m->doWriteSetup();
2536
0
    return m->final_pdf_version;
2537
0
}
2538
2539
void
2540
impl::Writer::writeHeader()
2541
94.1k
{
2542
94.1k
    write("%PDF-").write(final_pdf_version);
2543
94.1k
    if (cfg.pclm()) {
2544
        // PCLm version
2545
0
        write("\n%PCLm 1.0\n");
2546
94.1k
    } else {
2547
        // This string of binary characters would not be valid UTF-8, so it really should be treated
2548
        // as binary.
2549
94.1k
        write("\n%\xbf\xf7\xa2\xfe\n");
2550
94.1k
    }
2551
94.1k
    write_qdf("%QDF-1.0\n\n");
2552
2553
    // Note: do not write extra header text here.  Linearized PDFs must include the entire
2554
    // linearization parameter dictionary within the first 1024 characters of the PDF file, so for
2555
    // linearized files, we have to write extra header text after the linearization parameter
2556
    // dictionary.
2557
94.1k
}
2558
2559
void
2560
impl::Writer::writeHintStream(int hint_id)
2561
29.0k
{
2562
29.0k
    std::string hint_buffer;
2563
29.0k
    int S = 0;
2564
29.0k
    int O = 0;
2565
29.0k
    bool compressed = cfg.compress_streams();
2566
29.0k
    lin.generateHintStream(new_obj, obj, hint_buffer, S, O, compressed);
2567
2568
29.0k
    openObject(hint_id);
2569
29.0k
    setDataKey(hint_id);
2570
2571
29.0k
    size_t hlen = hint_buffer.size();
2572
2573
29.0k
    write("<< ");
2574
29.0k
    if (compressed) {
2575
29.0k
        write("/Filter /FlateDecode ");
2576
29.0k
    }
2577
29.0k
    write("/S ").write(S);
2578
29.0k
    if (O) {
2579
682
        write(" /O ").write(O);
2580
682
    }
2581
29.0k
    adjustAESStreamLength(hlen);
2582
29.0k
    write(" /Length ").write(hlen);
2583
29.0k
    write(" >>\nstream\n").write_encrypted(hint_buffer);
2584
2585
29.0k
    if (encryption) {
2586
14.6k
        QTC::TC("qpdf", "QPDFWriter encrypted hint stream");
2587
14.6k
    }
2588
2589
29.0k
    write(hint_buffer.empty() || hint_buffer.back() != '\n' ? "\nendstream" : "endstream");
2590
29.0k
    closeObject(hint_id);
2591
29.0k
}
2592
2593
qpdf_offset_t
2594
impl::Writer::writeXRefTable(trailer_e which, int first, int last, int size)
2595
32.9k
{
2596
    // There are too many extra arguments to replace overloaded function with defaults in the header
2597
    // file...too much risk of leaving something off.
2598
32.9k
    return writeXRefTable(which, first, last, size, 0, false, 0, 0, 0, 0);
2599
32.9k
}
2600
2601
qpdf_offset_t
2602
impl::Writer::writeXRefTable(
2603
    trailer_e which,
2604
    int first,
2605
    int last,
2606
    int size,
2607
    qpdf_offset_t prev,
2608
    bool suppress_offsets,
2609
    int hint_id,
2610
    qpdf_offset_t hint_offset,
2611
    qpdf_offset_t hint_length,
2612
    int linearization_pass)
2613
93.2k
{
2614
93.2k
    write("xref\n").write(first).write(" ").write(last - first + 1);
2615
93.2k
    qpdf_offset_t space_before_zero = pipeline->getCount();
2616
93.2k
    write("\n");
2617
93.2k
    if (first == 0) {
2618
62.7k
        write("0000000000 65535 f \n");
2619
62.7k
        ++first;
2620
62.7k
    }
2621
912k
    for (int i = first; i <= last; ++i) {
2622
819k
        qpdf_offset_t offset = 0;
2623
819k
        if (!suppress_offsets) {
2624
652k
            offset = new_obj[i].xref.getOffset();
2625
652k
            if ((hint_id != 0) && (i != hint_id) && (offset >= hint_offset)) {
2626
88.5k
                offset += hint_length;
2627
88.5k
            }
2628
652k
        }
2629
819k
        write(QUtil::int_to_string(offset, 10)).write(" 00000 n \n");
2630
819k
    }
2631
93.2k
    writeTrailer(which, size, false, prev, linearization_pass);
2632
93.2k
    write("\n");
2633
93.2k
    return space_before_zero;
2634
93.2k
}
2635
2636
qpdf_offset_t
2637
impl::Writer::writeXRefStream(
2638
    int objid, int max_id, qpdf_offset_t max_offset, trailer_e which, int first, int last, int size)
2639
846
{
2640
    // There are too many extra arguments to replace overloaded function with defaults in the header
2641
    // file...too much risk of leaving something off.
2642
846
    return writeXRefStream(
2643
846
        objid, max_id, max_offset, which, first, last, size, 0, 0, 0, 0, false, 0);
2644
846
}
2645
2646
qpdf_offset_t
2647
impl::Writer::writeXRefStream(
2648
    int xref_id,
2649
    int max_id,
2650
    qpdf_offset_t max_offset,
2651
    trailer_e which,
2652
    int first,
2653
    int last,
2654
    int size,
2655
    qpdf_offset_t prev,
2656
    int hint_id,
2657
    qpdf_offset_t hint_offset,
2658
    qpdf_offset_t hint_length,
2659
    bool skip_compression,
2660
    int linearization_pass)
2661
58.3k
{
2662
58.3k
    qpdf_offset_t xref_offset = pipeline->getCount();
2663
58.3k
    qpdf_offset_t space_before_zero = xref_offset - 1;
2664
2665
    // field 1 contains offsets and object stream identifiers
2666
58.3k
    unsigned int f1_size = std::max(bytesNeeded(max_offset + hint_length), bytesNeeded(max_id));
2667
2668
    // field 2 contains object stream indices
2669
58.3k
    unsigned int f2_size = bytesNeeded(QIntC::to_longlong(max_ostream_index));
2670
2671
58.3k
    unsigned int esize = 1 + f1_size + f2_size;
2672
2673
    // Must store in xref table in advance of writing the actual data rather than waiting for
2674
    // openObject to do it.
2675
58.3k
    new_obj[xref_id].xref = QPDFXRefEntry(pipeline->getCount());
2676
2677
58.3k
    std::string xref_data;
2678
58.3k
    const bool compressed = cfg.compress_streams() && !cfg.qdf();
2679
58.3k
    {
2680
58.3k
        auto pp_xref = pipeline_stack.activate(xref_data);
2681
2682
1.06M
        for (int i = first; i <= last; ++i) {
2683
1.00M
            QPDFXRefEntry& e = new_obj[i].xref;
2684
1.00M
            switch (e.getType()) {
2685
235k
            case 0:
2686
235k
                writeBinary(0, 1);
2687
235k
                writeBinary(0, f1_size);
2688
235k
                writeBinary(0, f2_size);
2689
235k
                break;
2690
2691
352k
            case 1:
2692
352k
                {
2693
352k
                    qpdf_offset_t offset = e.getOffset();
2694
352k
                    if ((hint_id != 0) && (i != hint_id) && (offset >= hint_offset)) {
2695
92.2k
                        offset += hint_length;
2696
92.2k
                    }
2697
352k
                    writeBinary(1, 1);
2698
352k
                    writeBinary(QIntC::to_ulonglong(offset), f1_size);
2699
352k
                    writeBinary(0, f2_size);
2700
352k
                }
2701
352k
                break;
2702
2703
416k
            case 2:
2704
416k
                writeBinary(2, 1);
2705
416k
                writeBinary(QIntC::to_ulonglong(e.getObjStreamNumber()), f1_size);
2706
416k
                writeBinary(QIntC::to_ulonglong(e.getObjStreamIndex()), f2_size);
2707
416k
                break;
2708
2709
0
            default:
2710
0
                throw std::logic_error("invalid type writing xref stream");
2711
0
                break;
2712
1.00M
            }
2713
1.00M
        }
2714
58.3k
    }
2715
2716
58.3k
    if (compressed) {
2717
57.5k
        xref_data = pl::pipe<Pl_PNGFilter>(xref_data, Pl_PNGFilter::a_encode, esize);
2718
57.5k
        if (!skip_compression) {
2719
            // Write the stream dictionary for compression but don't actually compress.  This
2720
            // helps us with computation of padding for pass 1 of linearization.
2721
28.2k
            xref_data = pl::pipe<Pl_Flate>(xref_data, Pl_Flate::a_deflate);
2722
28.2k
        }
2723
57.5k
    }
2724
2725
58.3k
    openObject(xref_id);
2726
58.3k
    write("<<").write_qdf("\n ").write(" /Type /XRef").write_qdf("\n ");
2727
58.3k
    write(" /Length ").write(xref_data.size());
2728
58.3k
    if (compressed) {
2729
57.5k
        write_qdf("\n ").write(" /Filter /FlateDecode").write_qdf("\n ");
2730
57.5k
        write(" /DecodeParms << /Columns ").write(esize).write(" /Predictor 12 >>");
2731
57.5k
    }
2732
58.3k
    write_qdf("\n ").write(" /W [ 1 ").write(f1_size).write(" ").write(f2_size).write(" ]");
2733
58.3k
    if (!(first == 0 && last == (size - 1))) {
2734
29.3k
        write(" /Index [ ").write(first).write(" ").write(last - first + 1).write(" ]");
2735
29.3k
    }
2736
58.3k
    writeTrailer(which, size, true, prev, linearization_pass);
2737
58.3k
    write("\nstream\n").write(xref_data).write("\nendstream");
2738
58.3k
    closeObject(xref_id);
2739
58.3k
    return space_before_zero;
2740
58.3k
}
2741
2742
size_t
2743
impl::Writer::calculateXrefStreamPadding(qpdf_offset_t xref_bytes)
2744
29.3k
{
2745
    // This routine is called right after a linearization first pass xref stream has been written
2746
    // without compression.  Calculate the amount of padding that would be required in the worst
2747
    // case, assuming the number of uncompressed bytes remains the same. The worst case for zlib is
2748
    // that the output is larger than the input by 6 bytes plus 5 bytes per 16K, and then we'll add
2749
    // 10 extra bytes for number length increases.
2750
2751
29.3k
    return QIntC::to_size(16 + (5 * ((xref_bytes + 16383) / 16384)));
2752
29.3k
}
2753
2754
void
2755
impl::Writer::writeLinearized()
2756
36.3k
{
2757
    // Optimize file and enqueue objects in order
2758
2759
36.3k
    std::map<int, int> stream_cache;
2760
2761
178k
    auto skip_stream_parameters = [this, &stream_cache](QPDFObjectHandle& stream) {
2762
178k
        if (auto& result = stream_cache[stream.getObjectID()]) {
2763
80.6k
            return result;
2764
98.1k
        } else {
2765
98.1k
            return result = will_filter_stream(stream) ? 2 : 1;
2766
98.1k
        }
2767
178k
    };
2768
2769
36.3k
    lin.optimize(obj, skip_stream_parameters);
2770
2771
36.3k
    std::vector<QPDFObjectHandle> part4;
2772
36.3k
    std::vector<QPDFObjectHandle> part6;
2773
36.3k
    std::vector<QPDFObjectHandle> part7;
2774
36.3k
    std::vector<QPDFObjectHandle> part8;
2775
36.3k
    std::vector<QPDFObjectHandle> part9;
2776
36.3k
    lin.parts(obj, part4, part6, part7, part8, part9);
2777
2778
    // Object number sequence:
2779
    //
2780
    //  second half
2781
    //    second half uncompressed objects
2782
    //    second half xref stream, if any
2783
    //    second half compressed objects
2784
    //  first half
2785
    //    linearization dictionary
2786
    //    first half xref stream, if any
2787
    //    part 4 uncompresesd objects
2788
    //    encryption dictionary, if any
2789
    //    hint stream
2790
    //    part 6 uncompressed objects
2791
    //    first half compressed objects
2792
    //
2793
2794
    // Second half objects
2795
36.3k
    int second_half_uncompressed = QIntC::to_int(part7.size() + part8.size() + part9.size());
2796
36.3k
    int second_half_first_obj = 1;
2797
36.3k
    int after_second_half = 1 + second_half_uncompressed;
2798
36.3k
    next_objid = after_second_half;
2799
36.3k
    int second_half_xref = 0;
2800
36.3k
    bool need_xref_stream = !obj.streams_empty;
2801
36.3k
    if (need_xref_stream) {
2802
15.6k
        second_half_xref = next_objid++;
2803
15.6k
    }
2804
    // Assign numbers to all compressed objects in the second half.
2805
36.3k
    std::vector<QPDFObjectHandle>* vecs2[] = {&part7, &part8, &part9};
2806
133k
    for (int i = 0; i < 3; ++i) {
2807
166k
        for (auto const& oh: *vecs2[i]) {
2808
166k
            assignCompressedObjectNumbers(oh.getObjGen());
2809
166k
        }
2810
97.5k
    }
2811
36.3k
    int second_half_end = next_objid - 1;
2812
36.3k
    int second_trailer_size = next_objid;
2813
2814
    // First half objects
2815
36.3k
    int first_half_start = next_objid;
2816
36.3k
    int lindict_id = next_objid++;
2817
36.3k
    int first_half_xref = 0;
2818
36.3k
    if (need_xref_stream) {
2819
15.6k
        first_half_xref = next_objid++;
2820
15.6k
    }
2821
36.3k
    int part4_first_obj = next_objid;
2822
36.3k
    next_objid += QIntC::to_int(part4.size());
2823
36.3k
    int after_part4 = next_objid;
2824
36.3k
    if (encryption) {
2825
16.5k
        encryption_dict_objid = next_objid++;
2826
16.5k
    }
2827
36.3k
    int hint_id = next_objid++;
2828
36.3k
    int part6_first_obj = next_objid;
2829
36.3k
    next_objid += QIntC::to_int(part6.size());
2830
36.3k
    int after_part6 = next_objid;
2831
    // Assign numbers to all compressed objects in the first half
2832
36.3k
    std::vector<QPDFObjectHandle>* vecs1[] = {&part4, &part6};
2833
101k
    for (int i = 0; i < 2; ++i) {
2834
256k
        for (auto const& oh: *vecs1[i]) {
2835
256k
            assignCompressedObjectNumbers(oh.getObjGen());
2836
256k
        }
2837
65.0k
    }
2838
36.3k
    int first_half_end = next_objid - 1;
2839
36.3k
    int first_trailer_size = next_objid;
2840
2841
36.3k
    int part4_end_marker = part4.back().getObjectID();
2842
36.3k
    int part6_end_marker = part6.back().getObjectID();
2843
36.3k
    qpdf_offset_t space_before_zero = 0;
2844
36.3k
    qpdf_offset_t file_size = 0;
2845
36.3k
    qpdf_offset_t part6_end_offset = 0;
2846
36.3k
    qpdf_offset_t first_half_max_obj_offset = 0;
2847
36.3k
    qpdf_offset_t second_xref_offset = 0;
2848
36.3k
    qpdf_offset_t first_xref_end = 0;
2849
36.3k
    qpdf_offset_t second_xref_end = 0;
2850
2851
36.3k
    next_objid = part4_first_obj;
2852
36.3k
    enqueuePart(part4);
2853
36.3k
    if (next_objid != after_part4) {
2854
        // This can happen with very botched files as in the fuzzer test. There are likely some
2855
        // faulty assumptions in calculateLinearizationData
2856
30
        throw std::runtime_error("error encountered after writing part 4 of linearized data");
2857
30
    }
2858
36.3k
    next_objid = part6_first_obj;
2859
36.3k
    enqueuePart(part6);
2860
36.3k
    util::no_ci_rt_error_if(
2861
36.3k
        next_objid != after_part6, "error encountered after writing part 6 of linearized data" //
2862
36.3k
    );
2863
36.3k
    next_objid = second_half_first_obj;
2864
36.3k
    enqueuePart(part7);
2865
36.3k
    enqueuePart(part8);
2866
36.3k
    enqueuePart(part9);
2867
36.3k
    util::no_ci_rt_error_if(
2868
36.3k
        next_objid != after_second_half,
2869
36.3k
        "error encountered after writing part 9 of linearized data" //
2870
36.3k
    );
2871
2872
36.3k
    qpdf_offset_t hint_length = 0;
2873
36.3k
    std::string hint_buffer;
2874
2875
    // Write file in two passes.  Part numbers refer to PDF spec 1.4.
2876
2877
36.3k
    FILE* lin_pass1_file = nullptr;
2878
36.3k
    auto pp_pass1 = pipeline_stack.popper();
2879
36.3k
    auto pp_md5 = pipeline_stack.popper();
2880
59.7k
    for (int pass: {1, 2}) {
2881
59.7k
        if (pass == 1) {
2882
30.6k
            if (!cfg.linearize_pass1().empty()) {
2883
0
                lin_pass1_file = QUtil::safe_fopen(cfg.linearize_pass1().data(), "wb");
2884
0
                pipeline_stack.activate(
2885
0
                    pp_pass1,
2886
0
                    std::make_unique<Pl_StdioFile>("linearization pass1", lin_pass1_file));
2887
30.6k
            } else {
2888
30.6k
                pipeline_stack.activate(pp_pass1, true);
2889
30.6k
            }
2890
30.6k
            if (cfg.deterministic_id()) {
2891
15.1k
                pipeline_stack.activate_md5(pp_md5);
2892
15.1k
            }
2893
30.6k
        }
2894
2895
        // Part 1: header
2896
2897
59.7k
        writeHeader();
2898
2899
        // Part 2: linearization parameter dictionary.  Save enough space to write real dictionary.
2900
        // 200 characters is enough space if all numerical values in the parameter dictionary that
2901
        // contain offsets are 20 digits long plus a few extra characters for safety.  The entire
2902
        // linearization parameter dictionary must appear within the first 1024 characters of the
2903
        // file.
2904
2905
59.7k
        qpdf_offset_t pos = pipeline->getCount();
2906
59.7k
        openObject(lindict_id);
2907
59.7k
        write("<<");
2908
59.7k
        if (pass == 2) {
2909
29.0k
            write(" /Linearized 1 /L ").write(file_size + hint_length);
2910
            // Implementation note 121 states that a space is mandatory after this open bracket.
2911
29.0k
            write(" /H [ ").write(new_obj[hint_id].xref.getOffset()).write(" ");
2912
29.0k
            write(hint_length);
2913
29.0k
            write(" ] /O ").write(obj[pages.all().at(0)].renumber);
2914
29.0k
            write(" /E ").write(part6_end_offset + hint_length);
2915
29.0k
            write(" /N ").write(pages.size());
2916
29.0k
            write(" /T ").write(space_before_zero + hint_length);
2917
29.0k
        }
2918
59.7k
        write(" >>");
2919
59.7k
        closeObject(lindict_id);
2920
59.7k
        static int const pad = 200;
2921
59.7k
        write(QIntC::to_size(pos - pipeline->getCount() + pad), ' ').write("\n");
2922
2923
        // If the user supplied any additional header text, write it here after the linearization
2924
        // parameter dictionary.
2925
59.7k
        write(cfg.extra_header_text());
2926
2927
        // Part 3: first page cross reference table and trailer.
2928
2929
59.7k
        qpdf_offset_t first_xref_offset = pipeline->getCount();
2930
59.7k
        qpdf_offset_t hint_offset = 0;
2931
59.7k
        if (pass == 2) {
2932
29.0k
            hint_offset = new_obj[hint_id].xref.getOffset();
2933
29.0k
        }
2934
59.7k
        if (need_xref_stream) {
2935
            // Must pad here too.
2936
29.3k
            if (pass == 1) {
2937
                // Set first_half_max_obj_offset to a value large enough to force four bytes to be
2938
                // reserved for each file offset.  This would provide adequate space for the xref
2939
                // stream as long as the last object in page 1 starts with in the first 4 GB of the
2940
                // file, which is extremely likely.  In the second pass, we will know the actual
2941
                // value for this, but it's okay if it's smaller.
2942
15.1k
                first_half_max_obj_offset = 1 << 25;
2943
15.1k
            }
2944
29.3k
            pos = pipeline->getCount();
2945
29.3k
            writeXRefStream(
2946
29.3k
                first_half_xref,
2947
29.3k
                first_half_end,
2948
29.3k
                first_half_max_obj_offset,
2949
29.3k
                t_lin_first,
2950
29.3k
                first_half_start,
2951
29.3k
                first_half_end,
2952
29.3k
                first_trailer_size,
2953
29.3k
                hint_length + second_xref_offset,
2954
29.3k
                hint_id,
2955
29.3k
                hint_offset,
2956
29.3k
                hint_length,
2957
29.3k
                (pass == 1),
2958
29.3k
                pass);
2959
29.3k
            qpdf_offset_t endpos = pipeline->getCount();
2960
29.3k
            if (pass == 1) {
2961
                // Pad so we have enough room for the real xref stream.
2962
15.1k
                write(calculateXrefStreamPadding(endpos - pos), ' ');
2963
15.1k
                first_xref_end = pipeline->getCount();
2964
15.1k
            } else {
2965
                // Pad so that the next object starts at the same place as in pass 1.
2966
14.1k
                write(QIntC::to_size(first_xref_end - endpos), ' ');
2967
2968
14.1k
                if (pipeline->getCount() != first_xref_end) {
2969
0
                    throw std::logic_error(
2970
0
                        "insufficient padding for first pass xref stream; first_xref_end=" +
2971
0
                        std::to_string(first_xref_end) + "; endpos=" + std::to_string(endpos));
2972
0
                }
2973
14.1k
            }
2974
29.3k
            write("\n");
2975
30.4k
        } else {
2976
30.4k
            writeXRefTable(
2977
30.4k
                t_lin_first,
2978
30.4k
                first_half_start,
2979
30.4k
                first_half_end,
2980
30.4k
                first_trailer_size,
2981
30.4k
                hint_length + second_xref_offset,
2982
30.4k
                (pass == 1),
2983
30.4k
                hint_id,
2984
30.4k
                hint_offset,
2985
30.4k
                hint_length,
2986
30.4k
                pass);
2987
30.4k
            write("startxref\n0\n%%EOF\n");
2988
30.4k
        }
2989
2990
        // Parts 4 through 9
2991
2992
699k
        for (auto const& cur_object: object_queue) {
2993
699k
            if (cur_object.getObjectID() == part6_end_marker) {
2994
59.3k
                first_half_max_obj_offset = pipeline->getCount();
2995
59.3k
            }
2996
699k
            writeObject(cur_object);
2997
699k
            if (cur_object.getObjectID() == part4_end_marker) {
2998
59.6k
                if (encryption) {
2999
30.0k
                    writeEncryptionDictionary();
3000
30.0k
                }
3001
59.6k
                if (pass == 1) {
3002
30.5k
                    new_obj[hint_id].xref = QPDFXRefEntry(pipeline->getCount());
3003
30.5k
                } else {
3004
                    // Part 5: hint stream
3005
29.0k
                    write(hint_buffer);
3006
29.0k
                }
3007
59.6k
            }
3008
699k
            if (cur_object.getObjectID() == part6_end_marker) {
3009
58.9k
                part6_end_offset = pipeline->getCount();
3010
58.9k
            }
3011
699k
        }
3012
3013
        // Part 10: overflow hint stream -- not used
3014
3015
        // Part 11: main cross reference table and trailer
3016
3017
59.7k
        second_xref_offset = pipeline->getCount();
3018
59.7k
        if (need_xref_stream) {
3019
28.2k
            pos = pipeline->getCount();
3020
28.2k
            space_before_zero = writeXRefStream(
3021
28.2k
                second_half_xref,
3022
28.2k
                second_half_end,
3023
28.2k
                second_xref_offset,
3024
28.2k
                t_lin_second,
3025
28.2k
                0,
3026
28.2k
                second_half_end,
3027
28.2k
                second_trailer_size,
3028
28.2k
                0,
3029
28.2k
                0,
3030
28.2k
                0,
3031
28.2k
                0,
3032
28.2k
                (pass == 1),
3033
28.2k
                pass);
3034
28.2k
            qpdf_offset_t endpos = pipeline->getCount();
3035
3036
28.2k
            if (pass == 1) {
3037
                // Pad so we have enough room for the real xref stream.  See comments for previous
3038
                // xref stream on how we calculate the padding.
3039
14.1k
                write(calculateXrefStreamPadding(endpos - pos), ' ').write("\n");
3040
14.1k
                second_xref_end = pipeline->getCount();
3041
14.1k
            } else {
3042
                // Make the file size the same.
3043
14.0k
                auto padding =
3044
14.0k
                    QIntC::to_size(second_xref_end + hint_length - 1 - pipeline->getCount());
3045
14.0k
                write(padding, ' ').write("\n");
3046
3047
                // If this assertion fails, maybe we didn't have enough padding above.
3048
14.0k
                if (pipeline->getCount() != second_xref_end + hint_length) {
3049
0
                    throw std::logic_error(
3050
0
                        "count mismatch after xref stream; possible insufficient padding?");
3051
0
                }
3052
14.0k
            }
3053
31.5k
        } else {
3054
31.5k
            space_before_zero = writeXRefTable(
3055
31.5k
                t_lin_second, 0, second_half_end, second_trailer_size, 0, false, 0, 0, 0, pass);
3056
31.5k
        }
3057
59.7k
        write("startxref\n").write(first_xref_offset).write("\n%%EOF\n");
3058
3059
59.7k
        if (pass == 1) {
3060
29.0k
            if (cfg.deterministic_id()) {
3061
14.3k
                QTC::TC("qpdf", "QPDFWriter linearized deterministic ID", need_xref_stream ? 0 : 1);
3062
14.3k
                computeDeterministicIDData();
3063
14.3k
                pp_md5.pop();
3064
14.3k
            }
3065
3066
            // Close first pass pipeline
3067
29.0k
            file_size = pipeline->getCount();
3068
29.0k
            pp_pass1.pop();
3069
3070
            // Save hint offset since it will be set to zero by calling openObject.
3071
29.0k
            qpdf_offset_t hint_offset1 = new_obj[hint_id].xref.getOffset();
3072
3073
            // Write hint stream to a buffer
3074
29.0k
            {
3075
29.0k
                auto pp_hint = pipeline_stack.activate(hint_buffer);
3076
29.0k
                writeHintStream(hint_id);
3077
29.0k
            }
3078
29.0k
            hint_length = QIntC::to_offset(hint_buffer.size());
3079
3080
            // Restore hint offset
3081
29.0k
            new_obj[hint_id].xref = QPDFXRefEntry(hint_offset1);
3082
29.0k
            if (lin_pass1_file) {
3083
                // Write some debugging information
3084
0
                fprintf(
3085
0
                    lin_pass1_file, "%% hint_offset=%s\n", std::to_string(hint_offset1).c_str());
3086
0
                fprintf(lin_pass1_file, "%% hint_length=%s\n", std::to_string(hint_length).c_str());
3087
0
                fprintf(
3088
0
                    lin_pass1_file,
3089
0
                    "%% second_xref_offset=%s\n",
3090
0
                    std::to_string(second_xref_offset).c_str());
3091
0
                fprintf(
3092
0
                    lin_pass1_file,
3093
0
                    "%% second_xref_end=%s\n",
3094
0
                    std::to_string(second_xref_end).c_str());
3095
0
                fclose(lin_pass1_file);
3096
0
                lin_pass1_file = nullptr;
3097
0
            }
3098
29.0k
        }
3099
59.7k
    }
3100
36.3k
}
3101
3102
void
3103
impl::Writer::enqueueObjectsStandard()
3104
34.3k
{
3105
34.3k
    if (cfg.preserve_unreferenced()) {
3106
0
        for (auto const& oh: qpdf.getAllObjects()) {
3107
0
            enqueue(oh);
3108
0
        }
3109
0
    }
3110
3111
    // Put root first on queue.
3112
34.3k
    auto trailer = trimmed_trailer();
3113
34.3k
    enqueue(trailer["/Root"]);
3114
3115
    // Next place any other objects referenced from the trailer dictionary into the queue, handling
3116
    // direct objects recursively. Root is already there, so enqueuing it a second time is a no-op.
3117
65.9k
    for (auto& item: trailer) {
3118
65.9k
        if (!item.second.null()) {
3119
53.8k
            enqueue(item.second);
3120
53.8k
        }
3121
65.9k
    }
3122
34.3k
}
3123
3124
void
3125
impl::Writer::enqueueObjectsPCLm()
3126
0
{
3127
    // Image transform stream content for page strip images. Each of this new stream has to come
3128
    // after every page image strip written in the pclm file.
3129
0
    std::string image_transform_content = "q /image Do Q\n";
3130
3131
    // enqueue all pages first
3132
0
    for (auto& page: pages) {
3133
0
        enqueue(page);
3134
0
        enqueue(page["/Contents"]);
3135
3136
        // enqueue all the strips for each page
3137
0
        for (auto& image: Dictionary(page["/Resources"]["/XObject"])) {
3138
0
            if (!image.second.null()) {
3139
0
                enqueue(image.second);
3140
0
                enqueue(qpdf.newStream(image_transform_content));
3141
0
            }
3142
0
        }
3143
0
    }
3144
3145
0
    enqueue(trimmed_trailer()["/Root"]);
3146
0
}
3147
3148
void
3149
impl::Writer::indicateProgress(bool decrement, bool finished)
3150
2.05M
{
3151
2.05M
    if (decrement) {
3152
501k
        --events_seen;
3153
501k
        return;
3154
501k
    }
3155
3156
1.54M
    ++events_seen;
3157
3158
1.54M
    if (!progress_reporter.get()) {
3159
1.54M
        return;
3160
1.54M
    }
3161
3162
0
    if (finished || events_seen >= next_progress_report) {
3163
0
        int percentage =
3164
0
            (finished ? 100
3165
0
                 : next_progress_report == 0
3166
0
                 ? 0
3167
0
                 : std::min(99, 1 + ((100 * events_seen) / events_expected)));
3168
0
        progress_reporter->reportProgress(percentage);
3169
0
    }
3170
0
    int increment = std::max(1, (events_expected / 100));
3171
0
    while (events_seen >= next_progress_report) {
3172
0
        next_progress_report += increment;
3173
0
    }
3174
0
}
3175
3176
void
3177
QPDFWriter::registerProgressReporter(std::shared_ptr<ProgressReporter> pr)
3178
0
{
3179
0
    m->progress_reporter = pr;
3180
0
}
3181
3182
void
3183
impl::Writer::writeStandard()
3184
34.3k
{
3185
34.3k
    auto pp_md5 = pipeline_stack.popper();
3186
34.3k
    if (cfg.deterministic_id()) {
3187
18.1k
        pipeline_stack.activate_md5(pp_md5);
3188
18.1k
    }
3189
3190
    // Start writing
3191
3192
34.3k
    writeHeader();
3193
34.3k
    write(cfg.extra_header_text());
3194
3195
34.3k
    if (cfg.pclm()) {
3196
0
        enqueueObjectsPCLm();
3197
34.3k
    } else {
3198
34.3k
        enqueueObjectsStandard();
3199
34.3k
    }
3200
3201
    // Now start walking queue, outputting each object.
3202
364k
    while (object_queue_front < object_queue.size()) {
3203
330k
        QPDFObjectHandle cur_object = object_queue.at(object_queue_front);
3204
330k
        ++object_queue_front;
3205
330k
        writeObject(cur_object);
3206
330k
    }
3207
3208
    // Write out the encryption dictionary, if any
3209
34.3k
    if (encryption) {
3210
15.9k
        writeEncryptionDictionary();
3211
15.9k
    }
3212
3213
    // Now write out xref.  next_objid is now the number of objects.
3214
34.3k
    qpdf_offset_t xref_offset = pipeline->getCount();
3215
34.3k
    if (object_stream_to_objects.empty()) {
3216
        // Write regular cross-reference table
3217
32.9k
        writeXRefTable(t_normal, 0, next_objid - 1, next_objid);
3218
32.9k
    } else {
3219
        // Write cross-reference stream.
3220
1.49k
        int xref_id = next_objid++;
3221
1.49k
        writeXRefStream(xref_id, xref_id, xref_offset, t_normal, 0, next_objid - 1, next_objid);
3222
1.49k
    }
3223
34.3k
    write("startxref\n").write(xref_offset).write("\n%%EOF\n");
3224
3225
34.3k
    if (cfg.deterministic_id()) {
3226
17.8k
        QTC::TC(
3227
17.8k
            "qpdf",
3228
17.8k
            "QPDFWriter standard deterministic ID",
3229
17.8k
            object_stream_to_objects.empty() ? 0 : 1);
3230
17.8k
    }
3231
34.3k
}