Coverage Report

Created: 2026-02-26 06:40

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
679k
                stack(&stack)
104
679k
            {
105
679k
            }
106
107
            Pl_stack* stack{nullptr};
108
            unsigned long stack_id{0};
109
        };
110
111
      public:
112
        Pl_stack(pl::Count*& top) :
113
67.7k
            top(top)
114
67.7k
        {
115
67.7k
        }
116
117
        Popper
118
        popper()
119
89.5k
        {
120
89.5k
            return {*this};
121
89.5k
        }
122
123
        void
124
        initialize(Pipeline* p)
125
67.7k
        {
126
67.7k
            auto c = std::make_unique<pl::Count>(++last_id, p);
127
67.7k
            top = c.get();
128
67.7k
            stack.emplace_back(std::move(c));
129
67.7k
        }
130
131
        Popper
132
        activate(std::string& str)
133
551k
        {
134
551k
            Popper pp{*this};
135
551k
            activate(pp, str);
136
551k
            return pp;
137
551k
        }
138
139
        void
140
        activate(Popper& pp, std::string& str)
141
551k
        {
142
551k
            activate(pp, false, &str, nullptr);
143
551k
        }
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
38.6k
        {
158
38.6k
            Popper pp{*this};
159
38.6k
            activate(pp, discard, str, std::move(next));
160
38.6k
            return pp;
161
38.6k
        }
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
619k
        {
170
619k
            std::unique_ptr<pl::Count> c;
171
619k
            if (next) {
172
0
                c = std::make_unique<pl::Count>(++last_id, count_buffer, std::move(next));
173
619k
            } else if (discard) {
174
67.4k
                c = std::make_unique<pl::Count>(++last_id, nullptr);
175
551k
            } else if (!str) {
176
0
                c = std::make_unique<pl::Count>(++last_id, top);
177
551k
            } else {
178
551k
                c = std::make_unique<pl::Count>(++last_id, *str);
179
551k
            }
180
619k
            pp.stack_id = last_id;
181
619k
            top = c.get();
182
619k
            stack.emplace_back(std::move(c));
183
619k
        }
184
        void
185
        activate_md5(Popper& pp)
186
31.8k
        {
187
31.8k
            qpdf_assert_debug(!md5_pipeline);
188
31.8k
            qpdf_assert_debug(md5_id == 0);
189
31.8k
            qpdf_assert_debug(top->getCount() == 0);
190
31.8k
            md5_pipeline = std::make_unique<Pl_MD5>("qpdf md5", top);
191
31.8k
            md5_pipeline->persistAcrossFinish(true);
192
            // Special case code in pop clears m->md5_pipeline upon deletion.
193
31.8k
            auto c = std::make_unique<pl::Count>(++last_id, md5_pipeline.get());
194
31.8k
            pp.stack_id = last_id;
195
31.8k
            md5_id = last_id;
196
31.8k
            top = c.get();
197
31.8k
            stack.emplace_back(std::move(c));
198
31.8k
        }
199
200
        // Return the hex digest and disable the MD5 pipeline.
201
        std::string
202
        hex_digest()
203
30.6k
        {
204
30.6k
            qpdf_assert_debug(md5_pipeline);
205
30.6k
            auto digest = md5_pipeline->getHexDigest();
206
30.6k
            md5_pipeline->enable(false);
207
30.6k
            return digest;
208
30.6k
        }
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
679k
        {
220
679k
            if (!stack_id) {
221
28.8k
                return;
222
28.8k
            }
223
651k
            qpdf_assert_debug(stack.size() >= 2);
224
651k
            top->finish();
225
651k
            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
651k
            qpdf_assert_debug(top->id() == stack_id);
231
651k
            if (stack_id == md5_id) {
232
31.8k
                md5_pipeline = nullptr;
233
31.8k
                md5_id = 0;
234
31.8k
            }
235
651k
            stack.pop_back();
236
651k
            top = stack.back().get();
237
651k
        }
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
679k
{
250
679k
    if (stack) {
251
639k
        stack->pop(stack_id);
252
639k
    }
253
679k
}
254
255
void
256
Pl_stack::Popper::pop()
257
40.8k
{
258
40.8k
    if (stack) {
259
40.8k
        stack->pop(stack_id);
260
40.8k
    }
261
40.8k
    stack_id = 0;
262
40.8k
    stack = nullptr;
263
40.8k
}
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
67.7k
        {
287
67.7k
            if (file && close_file) {
288
0
                fclose(file);
289
0
            }
290
67.7k
            delete output_buffer;
291
67.7k
        }
292
        Writer(QPDF& qpdf, QPDFWriter& w) :
293
69.4k
            Common(qpdf.doc()),
294
69.4k
            lin(qpdf.doc().linearization()),
295
69.4k
            cfg(true),
296
69.4k
            root_og(qpdf.getRoot().indirect() ? qpdf.getRoot().id_gen() : QPDFObjGen(-1, 0)),
297
69.4k
            pipeline_stack(pipeline)
298
69.4k
        {
299
69.4k
        }
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
69.4k
        impl::Writer(qpdf, w)
481
69.4k
    {
482
69.4k
    }
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
69.4k
    m(std::make_shared<Members>(*this, pdf))
492
69.4k
{
493
69.4k
}
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
67.7k
{
559
67.7k
    m->filename = "custom pipeline";
560
67.7k
    m->pipeline_stack.initialize(p);
561
67.7k
}
562
563
void
564
QPDFWriter::setObjectStreamMode(qpdf_object_stream_e mode)
565
33.3k
{
566
33.3k
    m->cfg.object_streams(mode);
567
33.3k
}
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
17.6k
{
605
17.6k
    if (pclm_) {
606
0
        usage("compress_streams cannot be set when pclm is set");
607
0
        return *this;
608
0
    }
609
17.6k
    compress_streams_set_ = true;
610
17.6k
    compress_streams_ = val;
611
17.6k
    return *this;
612
17.6k
}
613
614
void
615
QPDFWriter::setDecodeLevel(qpdf_stream_decode_level_e val)
616
67.7k
{
617
67.7k
    m->cfg.decode_level(val);
618
67.7k
}
619
620
Config&
621
Config::decode_level(qpdf_stream_decode_level_e val)
622
67.7k
{
623
67.7k
    if (pclm_) {
624
0
        usage("stream_decode_level cannot be set when pclm is set");
625
0
        return *this;
626
0
    }
627
67.7k
    decode_level_set_ = true;
628
67.7k
    decode_level_ = val;
629
67.7k
    return *this;
630
67.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
17.6k
{
647
17.6k
    m->cfg.qdf(val);
648
17.6k
}
649
650
Config&
651
Config::qdf(bool val)
652
52.9k
{
653
52.9k
    if (pclm_ || linearize_) {
654
35.2k
        usage("qdf cannot be set when linearize or pclm are set");
655
35.2k
    }
656
52.9k
    if (preserve_encryption_) {
657
52.9k
        usage("preserve_encryption cannot be set when qdf is set");
658
52.9k
    }
659
52.9k
    qdf_ = val;
660
52.9k
    if (val) {
661
17.6k
        if (!normalize_content_set_) {
662
17.6k
            normalize_content(true);
663
17.6k
        }
664
17.6k
        if (!compress_streams_set_) {
665
17.6k
            compress_streams(false);
666
17.6k
        }
667
17.6k
        if (!decode_level_set_) {
668
0
            decode_level(qpdf_dl_generalized);
669
0
        }
670
17.6k
        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
17.6k
        direct_stream_lengths_ = false;
675
17.6k
    }
676
52.9k
    return *this;
677
52.9k
}
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
117k
{
700
117k
    bool set_version = false;
701
117k
    bool set_extension_level = false;
702
117k
    if (min_pdf_version.empty()) {
703
67.1k
        set_version = true;
704
67.1k
        set_extension_level = true;
705
67.1k
    } else {
706
49.9k
        int old_major = 0;
707
49.9k
        int old_minor = 0;
708
49.9k
        int min_major = 0;
709
49.9k
        int min_minor = 0;
710
49.9k
        parseVersion(version, old_major, old_minor);
711
49.9k
        parseVersion(min_pdf_version, min_major, min_minor);
712
49.9k
        int compare = compareVersions(old_major, old_minor, min_major, min_minor);
713
49.9k
        if (compare > 0) {
714
2.94k
            QTC::TC("qpdf", "QPDFWriter increasing minimum version", extension_level == 0 ? 0 : 1);
715
2.94k
            set_version = true;
716
2.94k
            set_extension_level = true;
717
47.0k
        } else if (compare == 0) {
718
2.06k
            if (extension_level > min_extension_level) {
719
21
                set_extension_level = true;
720
21
            }
721
2.06k
        }
722
49.9k
    }
723
724
117k
    if (set_version) {
725
70.1k
        min_pdf_version = version;
726
70.1k
    }
727
117k
    if (set_extension_level) {
728
70.1k
        min_extension_level = extension_level;
729
70.1k
    }
730
117k
}
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
31.4k
{
768
31.4k
    m->cfg.static_id(val);
769
31.4k
}
770
771
void
772
QPDFWriter::setDeterministicID(bool val)
773
36.2k
{
774
36.2k
    m->cfg.deterministic_id(val);
775
36.2k
}
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
35.3k
{
800
35.3k
    m->cfg.linearize(val);
801
35.3k
}
802
803
Config&
804
Config::linearize(bool val)
805
35.3k
{
806
35.3k
    if (pclm_ || qdf_) {
807
0
        usage("linearize cannot be set when qdf or pclm are set");
808
0
        return *this;
809
0
    }
810
35.3k
    linearize_ = val;
811
35.3k
    return *this;
812
35.3k
}
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
14.7k
{
882
14.7k
    m->encryption = std::make_unique<Encryption>(2, 3, 16, true);
883
14.7k
    m->interpretR3EncryptionParameters(
884
14.7k
        allow_accessibility,
885
14.7k
        allow_extract,
886
14.7k
        allow_assemble,
887
14.7k
        allow_annotate_and_form,
888
14.7k
        allow_form_filling,
889
14.7k
        allow_modify_other,
890
14.7k
        print,
891
14.7k
        qpdf_r3m_all);
892
14.7k
    m->setEncryptionParameters(user_password, owner_password);
893
14.7k
}
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
16.7k
{
963
16.7k
    m->encryption = std::make_unique<Encryption>(5, 6, 32, encrypt_metadata);
964
16.7k
    m->interpretR3EncryptionParameters(
965
16.7k
        allow_accessibility,
966
16.7k
        allow_extract,
967
16.7k
        allow_assemble,
968
16.7k
        allow_annotate_and_form,
969
16.7k
        allow_form_filling,
970
16.7k
        allow_modify_other,
971
16.7k
        print,
972
16.7k
        qpdf_r3m_all);
973
16.7k
    m->cfg.encrypt_use_aes(true);
974
16.7k
    m->setEncryptionParameters(user_password, owner_password);
975
16.7k
}
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
31.4k
{
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
31.4k
    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
31.4k
    if (!allow_extract) {
1023
0
        encryption->setP(5, false);
1024
0
    }
1025
1026
31.4k
    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
31.4k
    case qpdf_r3p_full:
1034
31.4k
        break;
1035
        // no default so gcc warns for missing cases
1036
31.4k
    }
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
31.4k
    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
31.4k
    case qpdf_r3m_all:
1057
31.4k
        break;
1058
        // no default so gcc warns for missing cases
1059
31.4k
    }
1060
    // END NOT EXERCISED IN TEST SUITE
1061
1062
31.4k
    if (!allow_assemble) {
1063
0
        encryption->setP(11, false);
1064
0
    }
1065
31.4k
    if (!allow_annotate_and_form) {
1066
0
        encryption->setP(6, false);
1067
0
    }
1068
31.4k
    if (!allow_form_filling) {
1069
0
        encryption->setP(9, false);
1070
0
    }
1071
31.4k
    if (!allow_modify_other) {
1072
0
        encryption->setP(4, false);
1073
0
    }
1074
31.4k
}
1075
1076
void
1077
impl::Writer::setEncryptionParameters(char const* user_password, char const* owner_password)
1078
31.4k
{
1079
31.4k
    generateID(true);
1080
31.4k
    encryption->setId1(id1);
1081
31.4k
    encryption_key = encryption->compute_parameters(user_password, owner_password);
1082
31.4k
    setEncryptionMinimumVersion();
1083
31.4k
}
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
18.6k
{
1094
18.6k
    cfg.preserve_encryption(false);
1095
18.6k
    QPDFObjectHandle trailer = qpdf.getTrailer();
1096
18.6k
    if (trailer.hasKey("/Encrypt")) {
1097
128
        generateID(true);
1098
128
        id1 = trailer.getKey("/ID").getArrayItem(0).getStringValue();
1099
128
        QPDFObjectHandle encrypt = trailer.getKey("/Encrypt");
1100
128
        int V = encrypt.getKey("/V").getIntValueAsInt();
1101
128
        int key_len = 5;
1102
128
        if (V > 1) {
1103
0
            key_len = encrypt.getKey("/Length").getIntValueAsInt() / 8;
1104
0
        }
1105
128
        const bool encrypt_metadata =
1106
128
            encrypt.hasKey("/EncryptMetadata") && encrypt.getKey("/EncryptMetadata").isBool()
1107
128
            ? encrypt.getKey("/EncryptMetadata").getBoolValue()
1108
128
            : true;
1109
128
        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
128
        QTC::TC("qpdf", "QPDFWriter copy encrypt metadata", encrypt_metadata ? 0 : 1);
1117
128
        QTC::TC("qpdf", "QPDFWriter copy use_aes", cfg.encrypt_use_aes() ? 0 : 1);
1118
1119
128
        encryption = std::make_unique<Encryption>(
1120
128
            V,
1121
128
            encrypt.getKey("/R").getIntValueAsInt(),
1122
128
            key_len,
1123
128
            static_cast<int>(encrypt.getKey("/P").getIntValue()),
1124
128
            encrypt.getKey("/O").getStringValue(),
1125
128
            encrypt.getKey("/U").getStringValue(),
1126
128
            V < 5 ? "" : encrypt.getKey("/OE").getStringValue(),
1127
128
            V < 5 ? "" : encrypt.getKey("/UE").getStringValue(),
1128
128
            V < 5 ? "" : encrypt.getKey("/Perms").getStringValue(),
1129
128
            id1, // id1 == the other file's id1
1130
128
            encrypt_metadata);
1131
128
        encryption_key = V >= 5 ? qpdf.getEncryptionKey()
1132
128
                                : encryption->compute_encryption_key(qpdf.getPaddedUserPassword());
1133
128
        setEncryptionMinimumVersion();
1134
128
    }
1135
18.6k
}
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
99.8k
{
1177
99.8k
    major = QUtil::string_to_int(version.c_str());
1178
99.8k
    minor = 0;
1179
99.8k
    size_t p = version.find('.');
1180
99.8k
    if ((p != std::string::npos) && (version.length() > p)) {
1181
99.7k
        minor = QUtil::string_to_int(version.substr(p + 1).c_str());
1182
99.7k
    }
1183
99.8k
    std::string tmp = std::to_string(major) + "." + std::to_string(minor);
1184
99.8k
    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
506
    }
1189
99.8k
}
1190
1191
int
1192
impl::Writer::compareVersions(int major1, int minor1, int major2, int minor2) const
1193
49.8k
{
1194
49.8k
    if (major1 < major2) {
1195
519
        return -1;
1196
519
    }
1197
49.3k
    if (major1 > major2) {
1198
726
        return 1;
1199
726
    }
1200
48.6k
    if (minor1 < minor2) {
1201
44.3k
        return -1;
1202
44.3k
    }
1203
4.27k
    return minor1 > minor2 ? 1 : 0;
1204
48.6k
}
1205
1206
void
1207
impl::Writer::setEncryptionMinimumVersion()
1208
31.4k
{
1209
31.4k
    auto const R = encryption->getR();
1210
31.4k
    if (R >= 6) {
1211
16.6k
        setMinimumPDFVersion("1.7", 8);
1212
16.6k
    } else if (R == 5) {
1213
0
        setMinimumPDFVersion("1.7", 3);
1214
14.7k
    } else if (R == 4) {
1215
0
        setMinimumPDFVersion(cfg.encrypt_use_aes() ? "1.6" : "1.5");
1216
14.7k
    } else if (R == 3) {
1217
14.7k
        setMinimumPDFVersion("1.4");
1218
14.7k
    } else {
1219
0
        setMinimumPDFVersion("1.3");
1220
0
    }
1221
31.4k
}
1222
1223
void
1224
impl::Writer::setDataKey(int objid)
1225
878k
{
1226
878k
    if (encryption) {
1227
506k
        cur_data_key = QPDF::compute_data_key(
1228
506k
            encryption_key,
1229
506k
            objid,
1230
506k
            0,
1231
506k
            cfg.encrypt_use_aes(),
1232
506k
            encryption->getV(),
1233
506k
            encryption->getR());
1234
506k
    }
1235
878k
}
1236
1237
unsigned int
1238
impl::Writer::bytesNeeded(long long n)
1239
166k
{
1240
166k
    unsigned int bytes = 0;
1241
381k
    while (n) {
1242
214k
        ++bytes;
1243
214k
        n >>= 8;
1244
214k
    }
1245
166k
    return bytes;
1246
166k
}
1247
1248
void
1249
impl::Writer::writeBinary(unsigned long long val, unsigned int bytes)
1250
2.56M
{
1251
2.56M
    if (bytes > sizeof(unsigned long long)) {
1252
0
        throw std::logic_error("QPDFWriter::writeBinary called with too many bytes");
1253
0
    }
1254
2.56M
    unsigned char data[sizeof(unsigned long long)];
1255
6.25M
    for (unsigned int i = 0; i < bytes; ++i) {
1256
3.68M
        data[bytes - i - 1] = static_cast<unsigned char>(val & 0xff);
1257
3.68M
        val >>= 8;
1258
3.68M
    }
1259
2.56M
    pipeline->write(data, bytes);
1260
2.56M
}
1261
1262
impl::Writer&
1263
impl::Writer::write(std::string_view str)
1264
56.5M
{
1265
56.5M
    pipeline->write(str);
1266
56.5M
    return *this;
1267
56.5M
}
1268
1269
impl::Writer&
1270
impl::Writer::write(std::integral auto val)
1271
5.20M
{
1272
5.20M
    pipeline->write(std::to_string(val));
1273
5.20M
    return *this;
1274
5.20M
}
_ZN4qpdf4impl6Writer5writeITkNSt3__18integralEiEERS1_T_
Line
Count
Source
1271
3.51M
{
1272
3.51M
    pipeline->write(std::to_string(val));
1273
3.51M
    return *this;
1274
3.51M
}
_ZN4qpdf4impl6Writer5writeITkNSt3__18integralExEERS1_T_
Line
Count
Source
1271
1.10M
{
1272
1.10M
    pipeline->write(std::to_string(val));
1273
1.10M
    return *this;
1274
1.10M
}
_ZN4qpdf4impl6Writer5writeITkNSt3__18integralEmEERS1_T_
Line
Count
Source
1271
415k
{
1272
415k
    pipeline->write(std::to_string(val));
1273
415k
    return *this;
1274
415k
}
_ZN4qpdf4impl6Writer5writeITkNSt3__18integralEjEERS1_T_
Line
Count
Source
1271
166k
{
1272
166k
    pipeline->write(std::to_string(val));
1273
166k
    return *this;
1274
166k
}
1275
1276
impl::Writer&
1277
impl::Writer::write(size_t count, char c)
1278
125k
{
1279
125k
    pipeline->write(count, c);
1280
125k
    return *this;
1281
125k
}
1282
1283
impl::Writer&
1284
impl::Writer::write_name(std::string const& str)
1285
3.76M
{
1286
3.76M
    pipeline->write(Name::normalize(str));
1287
3.76M
    return *this;
1288
3.76M
}
1289
1290
impl::Writer&
1291
impl::Writer::write_string(std::string const& str, bool force_binary)
1292
337k
{
1293
337k
    pipeline->write(QPDF_String(str).unparse(force_binary));
1294
337k
    return *this;
1295
337k
}
1296
1297
template <typename... Args>
1298
impl::Writer&
1299
impl::Writer::write_qdf(Args&&... args)
1300
3.11M
{
1301
3.11M
    if (cfg.qdf()) {
1302
380k
        pipeline->write(std::forward<Args>(args)...);
1303
380k
    }
1304
3.11M
    return *this;
1305
3.11M
}
qpdf::impl::Writer& qpdf::impl::Writer::write_qdf<char const (&) [2]>(char const (&) [2])
Line
Count
Source
1300
2.30M
{
1301
2.30M
    if (cfg.qdf()) {
1302
302k
        pipeline->write(std::forward<Args>(args)...);
1303
302k
    }
1304
2.30M
    return *this;
1305
2.30M
}
qpdf::impl::Writer& qpdf::impl::Writer::write_qdf<char const (&) [3]>(char const (&) [3])
Line
Count
Source
1300
584k
{
1301
584k
    if (cfg.qdf()) {
1302
43.6k
        pipeline->write(std::forward<Args>(args)...);
1303
43.6k
    }
1304
584k
    return *this;
1305
584k
}
qpdf::impl::Writer& qpdf::impl::Writer::write_qdf<char const (&) [4]>(char const (&) [4])
Line
Count
Source
1300
140k
{
1301
140k
    if (cfg.qdf()) {
1302
16.6k
        pipeline->write(std::forward<Args>(args)...);
1303
16.6k
    }
1304
140k
    return *this;
1305
140k
}
qpdf::impl::Writer& qpdf::impl::Writer::write_qdf<char const (&) [11]>(char const (&) [11])
Line
Count
Source
1300
87.9k
{
1301
87.9k
    if (cfg.qdf()) {
1302
17.3k
        pipeline->write(std::forward<Args>(args)...);
1303
17.3k
    }
1304
87.9k
    return *this;
1305
87.9k
}
1306
1307
template <typename... Args>
1308
impl::Writer&
1309
impl::Writer::write_no_qdf(Args&&... args)
1310
1.08M
{
1311
1.08M
    if (!cfg.qdf()) {
1312
986k
        pipeline->write(std::forward<Args>(args)...);
1313
986k
    }
1314
1.08M
    return *this;
1315
1.08M
}
qpdf::impl::Writer& qpdf::impl::Writer::write_no_qdf<char const (&) [2]>(char const (&) [2])
Line
Count
Source
1310
947k
{
1311
947k
    if (!cfg.qdf()) {
1312
862k
        pipeline->write(std::forward<Args>(args)...);
1313
862k
    }
1314
947k
    return *this;
1315
947k
}
qpdf::impl::Writer& qpdf::impl::Writer::write_no_qdf<char const (&) [4]>(char const (&) [4])
Line
Count
Source
1310
140k
{
1311
140k
    if (!cfg.qdf()) {
1312
124k
        pipeline->write(std::forward<Args>(args)...);
1313
124k
    }
1314
140k
    return *this;
1315
140k
}
1316
1317
void
1318
impl::Writer::adjustAESStreamLength(size_t& length)
1319
295k
{
1320
295k
    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
100k
        length += 32 - (length & 0xf);
1324
100k
    }
1325
295k
}
1326
1327
impl::Writer&
1328
impl::Writer::write_encrypted(std::string_view str)
1329
294k
{
1330
294k
    if (!(encryption && !cur_data_key.empty())) {
1331
156k
        write(str);
1332
156k
    } else if (cfg.encrypt_use_aes()) {
1333
99.5k
        write(pl::pipe<Pl_AES_PDF>(str, true, cur_data_key));
1334
99.5k
    } else {
1335
38.5k
        write(pl::pipe<Pl_RC4>(str, cur_data_key));
1336
38.5k
    }
1337
1338
294k
    return *this;
1339
294k
}
1340
1341
void
1342
impl::Writer::computeDeterministicIDData()
1343
30.6k
{
1344
30.6k
    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
30.6k
    qpdf_assert_debug(deterministic_id_data.empty());
1350
30.6k
    deterministic_id_data = pipeline_stack.hex_digest();
1351
30.6k
}
1352
1353
int
1354
impl::Writer::openObject(int objid)
1355
1.07M
{
1356
1.07M
    if (objid == 0) {
1357
14.1k
        objid = next_objid++;
1358
14.1k
    }
1359
1.07M
    new_obj[objid].xref = QPDFXRefEntry(pipeline->getCount());
1360
1.07M
    write(objid).write(" 0 obj\n");
1361
1.07M
    return objid;
1362
1.07M
}
1363
1364
void
1365
impl::Writer::closeObject(int objid)
1366
1.07M
{
1367
    // Write a newline before endobj as it makes the file easier to repair.
1368
1.07M
    write("\nendobj\n").write_qdf("\n");
1369
1.07M
    auto& no = new_obj[objid];
1370
1.07M
    no.length = pipeline->getCount() - no.xref.getOffset();
1371
1.07M
}
1372
1373
void
1374
impl::Writer::assignCompressedObjectNumbers(QPDFObjGen og)
1375
359k
{
1376
359k
    int objid = og.getObj();
1377
359k
    if (og.getGen() != 0 || !object_stream_to_objects.contains(objid)) {
1378
        // This is not an object stream.
1379
335k
        return;
1380
335k
    }
1381
1382
    // Reserve numbers for the objects that belong to this object stream.
1383
248k
    for (auto const& iter: object_stream_to_objects[objid]) {
1384
248k
        obj[iter].renumber = next_objid++;
1385
248k
    }
1386
23.4k
}
1387
1388
void
1389
impl::Writer::enqueue(QPDFObjectHandle const& object)
1390
82.0M
{
1391
82.0M
    if (object.indirect()) {
1392
2.16M
        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
2.16M
            object.qpdf() == &qpdf,
1399
2.16M
            "QPDFObjectHandle from different QPDF found while writing.  "
1400
2.16M
            "Use QPDF::copyForeignObject to add objects from another file." //
1401
2.16M
        );
1402
1403
2.16M
        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.47k
            return;
1410
1.47k
        }
1411
1412
2.16M
        QPDFObjGen og = object.getObjGen();
1413
2.16M
        auto& o = obj[og];
1414
1415
2.16M
        if (o.renumber == 0) {
1416
618k
            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
5.12k
                o.renumber = -1;
1421
5.12k
                enqueue(qpdf.getObject(o.object_stream, 0));
1422
613k
            } else {
1423
613k
                object_queue.emplace_back(object);
1424
613k
                o.renumber = next_objid++;
1425
1426
613k
                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
23.2k
                    if (!cfg.linearize()) {
1430
3.53k
                        assignCompressedObjectNumbers(og);
1431
3.53k
                    }
1432
590k
                } else if (!cfg.direct_stream_lengths() && object.isStream()) {
1433
                    // reserve next object ID for length
1434
41.0k
                    ++next_objid;
1435
41.0k
                }
1436
613k
            }
1437
618k
        }
1438
2.16M
        return;
1439
2.16M
    }
1440
1441
79.9M
    if (cfg.linearize()) {
1442
511
        return;
1443
511
    }
1444
1445
79.9M
    if (Array array = object) {
1446
65.7M
        for (auto& item: array) {
1447
65.7M
            enqueue(item);
1448
65.7M
        }
1449
1.88M
        return;
1450
1.88M
    }
1451
1452
78.0M
    for (auto const& item: Dictionary(object)) {
1453
4.95M
        if (!item.second.null()) {
1454
4.37M
            enqueue(item.second);
1455
4.37M
        }
1456
4.95M
    }
1457
78.0M
}
1458
1459
void
1460
impl::Writer::unparseChild(QPDFObjectHandle const& child, size_t level, int flags)
1461
18.6M
{
1462
18.6M
    if (!cfg.linearize()) {
1463
11.5M
        enqueue(child);
1464
11.5M
    }
1465
18.6M
    if (child.indirect()) {
1466
1.71M
        write(obj[child].renumber).write(" 0 R");
1467
16.9M
    } else {
1468
16.9M
        unparseObject(child, level, flags);
1469
16.9M
    }
1470
18.6M
}
1471
1472
void
1473
impl::Writer::writeTrailer(
1474
    trailer_e which, int size, bool xref_stream, qpdf_offset_t prev, int linearization_pass)
1475
141k
{
1476
141k
    auto trailer = trimmed_trailer();
1477
141k
    if (xref_stream) {
1478
55.5k
        cur_data_key.clear();
1479
86.0k
    } else {
1480
86.0k
        write("trailer <<");
1481
86.0k
    }
1482
141k
    write_qdf("\n");
1483
141k
    if (which == t_lin_second) {
1484
54.3k
        write(" /Size ").write(size);
1485
87.2k
    } else {
1486
187k
        for (auto const& [key, value]: trailer) {
1487
187k
            if (value.null()) {
1488
35.4k
                continue;
1489
35.4k
            }
1490
152k
            write_qdf("  ").write_no_qdf(" ").write_name(key).write(" ");
1491
152k
            if (key == "/Size") {
1492
17.2k
                write(size);
1493
17.2k
                if (which == t_lin_first) {
1494
10.2k
                    write(" /Prev ");
1495
10.2k
                    qpdf_offset_t pos = pipeline->getCount();
1496
10.2k
                    write(prev).write(QIntC::to_size(pos - pipeline->getCount() + 21), ' ');
1497
10.2k
                }
1498
134k
            } else {
1499
134k
                unparseChild(value, 1, 0);
1500
134k
            }
1501
152k
            write_qdf("\n");
1502
152k
        }
1503
87.2k
    }
1504
1505
    // Write ID
1506
141k
    write_qdf(" ").write(" /ID [");
1507
141k
    if (linearization_pass == 1) {
1508
56.0k
        std::string original_id1 = getOriginalID1();
1509
56.0k
        if (original_id1.empty()) {
1510
51.4k
            write("<00000000000000000000000000000000>");
1511
51.4k
        } 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.65k
            size_t len = QPDF_String(original_id1).unparse(true).length() - 2;
1517
4.65k
            write("<").write(len, '0').write(">");
1518
4.65k
        }
1519
56.0k
        write("<00000000000000000000000000000000>");
1520
85.5k
    } else {
1521
85.5k
        if (linearization_pass == 0 && cfg.deterministic_id()) {
1522
17.0k
            computeDeterministicIDData();
1523
17.0k
        }
1524
85.5k
        generateID(encryption.get());
1525
85.5k
        write_string(id1, true).write_string(id2, true);
1526
85.5k
    }
1527
141k
    write("]");
1528
1529
141k
    if (which != t_lin_second) {
1530
        // Write reference to encryption dictionary
1531
86.6k
        if (encryption) {
1532
42.1k
            write(" /Encrypt ").write(encryption_dict_objid).write(" 0 R");
1533
42.1k
        }
1534
86.6k
    }
1535
1536
141k
    write_qdf("\n>>").write_no_qdf(" >>");
1537
141k
}
1538
1539
bool
1540
impl::Writer::will_filter_stream(QPDFObjectHandle stream)
1541
83.6k
{
1542
83.6k
    std::string s;
1543
83.6k
    [[maybe_unused]] auto [filter, ignore1, ignore2] = will_filter_stream(stream, &s);
1544
83.6k
    return filter;
1545
83.6k
}
1546
1547
std::tuple<const bool, const bool, const bool>
1548
impl::Writer::will_filter_stream(QPDFObjectHandle stream, std::string* stream_data)
1549
313k
{
1550
313k
    const bool is_root_metadata = stream.isRootMetadata();
1551
313k
    bool filter = false;
1552
313k
    auto decode_level = cfg.decode_level();
1553
313k
    int encode_flags = 0;
1554
313k
    Dictionary stream_dict = stream.getDict();
1555
1556
313k
    if (stream.getFilterOnWrite()) {
1557
260k
        filter = stream.isDataModified() || cfg.compress_streams() || decode_level != qpdf_dl_none;
1558
260k
        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
219k
            Name Filter = stream_dict["/Filter"];
1564
219k
            if (Filter && !cfg.recompress_flate() && !stream.isDataModified() &&
1565
85.8k
                (Filter == "/FlateDecode" || Filter == "/Fl")) {
1566
44.3k
                filter = false;
1567
44.3k
            }
1568
219k
        }
1569
260k
        if (is_root_metadata && (!encryption || !encryption->getEncryptMetadata())) {
1570
566
            filter = true;
1571
566
            decode_level = qpdf_dl_all;
1572
260k
        } else if (cfg.normalize_content() && normalized_streams.contains(stream)) {
1573
5.14k
            encode_flags = qpdf_ef_normalize;
1574
5.14k
            filter = true;
1575
254k
        } else if (filter && cfg.compress_streams()) {
1576
175k
            encode_flags = qpdf_ef_compress;
1577
175k
        }
1578
260k
    }
1579
1580
    // Disable compression for empty streams to improve compatibility
1581
313k
    if (Integer(stream_dict["/Length"]) == 0) {
1582
14.5k
        filter = true;
1583
14.5k
        encode_flags = 0;
1584
14.5k
    }
1585
1586
391k
    for (bool first_attempt: {true, false}) {
1587
391k
        auto pp_stream_data =
1588
391k
            stream_data ? pipeline_stack.activate(*stream_data) : pipeline_stack.activate(true);
1589
1590
391k
        try {
1591
391k
            if (stream.pipeStreamData(
1592
391k
                    pipeline,
1593
391k
                    filter ? encode_flags : 0,
1594
391k
                    filter ? decode_level : qpdf_dl_none,
1595
391k
                    false,
1596
391k
                    first_attempt)) {
1597
148k
                return {true, encode_flags & qpdf_ef_compress, is_root_metadata};
1598
148k
            }
1599
243k
            if (!filter) {
1600
165k
                break;
1601
165k
            }
1602
243k
        } catch (std::runtime_error& e) {
1603
586
            if (!(filter && first_attempt)) {
1604
123
                throw std::runtime_error(
1605
123
                    "error while getting stream data for " + stream.unparse() + ": " + e.what());
1606
123
            }
1607
463
            stream.warn("error while getting stream data: "s + e.what());
1608
463
            stream.warn("qpdf will attempt to write the damaged stream unchanged");
1609
463
        }
1610
        // Try again
1611
77.8k
        filter = false;
1612
77.8k
        stream.setFilterOnWrite(false);
1613
77.8k
        if (stream_data) {
1614
77.8k
            stream_data->clear();
1615
77.8k
        }
1616
77.8k
    }
1617
165k
    return {false, false, is_root_metadata};
1618
313k
}
1619
1620
void
1621
impl::Writer::unparseObject(
1622
    QPDFObjectHandle object, size_t level, int flags, size_t stream_length, bool compress)
1623
18.4M
{
1624
18.4M
    QPDFObjGen old_og = object.getObjGen();
1625
18.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
18.4M
    std::string indent_large = " ";
1629
18.4M
    if (cfg.qdf()) {
1630
10.5M
        indent_large.append(2 * (level + 1), ' ');
1631
10.5M
        indent_large[0] = '\n';
1632
10.5M
    }
1633
18.4M
    std::string_view indent{indent_large.data(), cfg.qdf() ? indent_large.size() - 2 : 1};
1634
1635
18.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
595k
        write("[");
1640
14.9M
        for (auto const& item: object.as_array()) {
1641
14.9M
            write(indent_large);
1642
14.9M
            unparseChild(item, level + 1, child_flags);
1643
14.9M
        }
1644
595k
        write(indent).write("]");
1645
17.8M
    } else if (tc == ::ot_dictionary) {
1646
        // Handle special cases for specific dictionaries.
1647
1648
1.20M
        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
84.9k
            auto extensions = object.getKey("/Extensions");
1668
84.9k
            const bool has_extensions = extensions.isDictionary();
1669
84.9k
            const bool need_extensions_adbe = final_extension_level > 0;
1670
1671
84.9k
            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
29.9k
                object = object.unsafeShallowCopy();
1678
29.9k
                if (!has_extensions) {
1679
25.9k
                    extensions = QPDFObjectHandle();
1680
25.9k
                }
1681
1682
29.9k
                const bool have_extensions_adbe = extensions && extensions.hasKey("/ADBE");
1683
29.9k
                const bool have_extensions_other =
1684
29.9k
                    extensions && extensions.getKeys().size() > (have_extensions_adbe ? 1u : 0u);
1685
1686
29.9k
                if (need_extensions_adbe) {
1687
27.3k
                    if (!(have_extensions_other || have_extensions_adbe)) {
1688
                        // We need Extensions and don't have it.  Create it here.
1689
25.9k
                        QTC::TC("qpdf", "QPDFWriter create Extensions", cfg.qdf() ? 0 : 1);
1690
25.9k
                        extensions = object.replaceKeyAndGetNew(
1691
25.9k
                            "/Extensions", QPDFObjectHandle::newDictionary());
1692
25.9k
                    }
1693
27.3k
                } else if (!have_extensions_other) {
1694
                    // We have Extensions dictionary and don't want one.
1695
1.29k
                    if (have_extensions_adbe) {
1696
1.13k
                        QTC::TC("qpdf", "QPDFWriter remove existing Extensions");
1697
1.13k
                        object.removeKey("/Extensions");
1698
1.13k
                        extensions = QPDFObjectHandle(); // uninitialized
1699
1.13k
                    }
1700
1.29k
                }
1701
1702
29.9k
                if (extensions) {
1703
28.8k
                    QTC::TC("qpdf", "QPDFWriter preserve Extensions");
1704
28.8k
                    QPDFObjectHandle adbe = extensions.getKey("/ADBE");
1705
28.8k
                    if (adbe.isDictionary() &&
1706
1.34k
                        adbe.getKey("/BaseVersion").isNameAndEquals("/" + final_pdf_version) &&
1707
822
                        adbe.getKey("/ExtensionLevel").isInteger() &&
1708
813
                        (adbe.getKey("/ExtensionLevel").getIntValue() == final_extension_level)) {
1709
28.1k
                    } else {
1710
28.1k
                        if (need_extensions_adbe) {
1711
26.6k
                            extensions.replaceKey(
1712
26.6k
                                "/ADBE",
1713
26.6k
                                QPDFObjectHandle::parse(
1714
26.6k
                                    "<< /BaseVersion /" + final_pdf_version + " /ExtensionLevel " +
1715
26.6k
                                    std::to_string(final_extension_level) + " >>"));
1716
26.6k
                        } else {
1717
1.48k
                            extensions.removeKey("/ADBE");
1718
1.48k
                        }
1719
28.1k
                    }
1720
28.8k
                }
1721
29.9k
            }
1722
84.9k
        }
1723
1724
        // Stream dictionaries.
1725
1726
1.20M
        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
229k
            object = object.unsafeShallowCopy();
1735
1736
229k
            object.removeKey("/Length");
1737
1738
            // If /DecodeParms is an empty list, remove it.
1739
229k
            if (object.getKey("/DecodeParms").empty()) {
1740
217k
                object.removeKey("/DecodeParms");
1741
217k
            }
1742
1743
229k
            if (flags & f_filtered) {
1744
                // We will supply our own filter and decode parameters.
1745
108k
                object.removeKey("/Filter");
1746
108k
                object.removeKey("/DecodeParms");
1747
121k
            } else {
1748
                // Make sure, no matter what else we have, that we don't have /Crypt in the output
1749
                // filters.
1750
121k
                QPDFObjectHandle filter = object.getKey("/Filter");
1751
121k
                QPDFObjectHandle decode_parms = object.getKey("/DecodeParms");
1752
121k
                if (filter.isOrHasName("/Crypt")) {
1753
2.50k
                    if (filter.isName()) {
1754
291
                        object.removeKey("/Filter");
1755
291
                        object.removeKey("/DecodeParms");
1756
2.21k
                    } else {
1757
2.21k
                        int idx = 0;
1758
85.3k
                        for (auto const& item: filter.as_array()) {
1759
85.3k
                            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.21k
                                filter.eraseItem(idx);
1766
2.21k
                                decode_parms.eraseItem(idx);
1767
2.21k
                                break;
1768
2.21k
                            }
1769
83.1k
                            ++idx;
1770
83.1k
                        }
1771
2.21k
                    }
1772
2.50k
                }
1773
121k
            }
1774
229k
        }
1775
1776
1.20M
        write("<<");
1777
1778
4.27M
        for (auto const& [key, value]: object.as_dictionary()) {
1779
4.27M
            if (!value.null()) {
1780
3.60M
                write(indent_large).write_name(key).write(" ");
1781
3.60M
                if (key == "/Contents" && object.isDictionaryOfType("/Sig") &&
1782
321
                    object.hasKey("/ByteRange")) {
1783
293
                    QTC::TC("qpdf", "QPDFWriter no encryption sig contents");
1784
293
                    unparseChild(value, level + 1, child_flags | f_hex_string | f_no_encryption);
1785
3.60M
                } else {
1786
3.60M
                    unparseChild(value, level + 1, child_flags);
1787
3.60M
                }
1788
3.60M
            }
1789
4.27M
        }
1790
1791
1.20M
        if (flags & f_stream) {
1792
228k
            write(indent_large).write("/Length ");
1793
1794
228k
            if (cfg.direct_stream_lengths()) {
1795
188k
                write(stream_length);
1796
188k
            } else {
1797
40.5k
                write(cur_stream_length_id).write(" 0 R");
1798
40.5k
            }
1799
228k
            if (compress && (flags & f_filtered)) {
1800
86.7k
                write(indent_large).write("/Filter /FlateDecode");
1801
86.7k
            }
1802
228k
        }
1803
1804
1.20M
        write(indent).write(">>");
1805
16.6M
    } else if (tc == ::ot_stream) {
1806
        // Write stream data to a buffer.
1807
230k
        if (!cfg.direct_stream_lengths()) {
1808
40.7k
            cur_stream_length_id = obj[old_og].renumber + 1;
1809
40.7k
        }
1810
1811
230k
        flags |= f_stream;
1812
230k
        std::string stream_data;
1813
230k
        auto [filter, compress_stream, is_root_metadata] = will_filter_stream(object, &stream_data);
1814
230k
        if (filter) {
1815
108k
            flags |= f_filtered;
1816
108k
        }
1817
230k
        QPDFObjectHandle stream_dict = object.getDict();
1818
1819
230k
        cur_stream_length = stream_data.size();
1820
230k
        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
230k
        adjustAESStreamLength(cur_stream_length);
1825
230k
        unparseObject(stream_dict, 0, flags, cur_stream_length, compress_stream);
1826
230k
        char last_char = stream_data.empty() ? '\0' : stream_data.back();
1827
230k
        write("\nstream\n").write_encrypted(stream_data);
1828
230k
        added_newline = cfg.newline_before_endstream() || (cfg.qdf() && last_char != '\n');
1829
230k
        write(added_newline ? "\nendstream" : "endstream");
1830
16.3M
    } else if (tc == ::ot_string) {
1831
266k
        std::string val;
1832
266k
        if (encryption && !(flags & f_in_ostream) && !(flags & f_no_encryption) &&
1833
111k
            !cur_data_key.empty()) {
1834
81.7k
            val = object.getStringValue();
1835
81.7k
            if (cfg.encrypt_use_aes()) {
1836
61.2k
                Pl_Buffer bufpl("encrypted string");
1837
61.2k
                Pl_AES_PDF pl("aes encrypt string", &bufpl, true, cur_data_key);
1838
61.2k
                pl.writeString(val);
1839
61.2k
                pl.finish();
1840
61.2k
                val = QPDF_String(bufpl.getString()).unparse(true);
1841
61.2k
            } else {
1842
20.4k
                auto tmp_ph = QUtil::make_unique_cstr(val);
1843
20.4k
                char* tmp = tmp_ph.get();
1844
20.4k
                size_t vlen = val.length();
1845
20.4k
                RC4 rc4(
1846
20.4k
                    QUtil::unsigned_char_pointer(cur_data_key),
1847
20.4k
                    QIntC::to_int(cur_data_key.length()));
1848
20.4k
                auto data = QUtil::unsigned_char_pointer(tmp);
1849
20.4k
                rc4.process(data, vlen, data);
1850
20.4k
                val = QPDF_String(std::string(tmp, vlen)).unparse();
1851
20.4k
            }
1852
184k
        } else if (flags & f_hex_string) {
1853
326
            val = QPDF_String(object.getStringValue()).unparse(true);
1854
184k
        } else {
1855
184k
            val = object.unparseResolved();
1856
184k
        }
1857
266k
        write(val);
1858
16.1M
    } else {
1859
16.1M
        write(object.unparseResolved());
1860
16.1M
    }
1861
18.4M
}
1862
1863
void
1864
impl::Writer::writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj)
1865
77.2k
{
1866
77.2k
    qpdf_assert_debug(first_obj > 0);
1867
77.2k
    bool is_first = true;
1868
77.2k
    auto id = std::to_string(first_obj) + ' ';
1869
833k
    for (auto& offset: offsets) {
1870
833k
        if (is_first) {
1871
77.2k
            is_first = false;
1872
756k
        } else {
1873
756k
            write_qdf("\n").write_no_qdf(" ");
1874
756k
        }
1875
833k
        write(id);
1876
833k
        util::increment(id, 1);
1877
833k
        write(offset);
1878
833k
    }
1879
77.2k
    write("\n");
1880
77.2k
}
1881
1882
void
1883
impl::Writer::writeObjectStream(QPDFObjectHandle object)
1884
38.6k
{
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
38.6k
    QPDFObjGen old_og = object.getObjGen();
1889
38.6k
    qpdf_assert_debug(old_og.getGen() == 0);
1890
38.6k
    int old_id = old_og.getObj();
1891
38.6k
    int new_stream_id = obj[old_og].renumber;
1892
1893
38.6k
    std::vector<qpdf_offset_t> offsets;
1894
38.6k
    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
38.6k
    std::string stream_buffer_pass1;
1899
38.6k
    std::string stream_buffer_pass2;
1900
38.6k
    int first_obj = -1;
1901
38.6k
    const bool compressed = cfg.compress_streams() && !cfg.qdf();
1902
38.6k
    {
1903
        // Pass 1
1904
38.6k
        auto pp_ostream_pass1 = pipeline_stack.activate(stream_buffer_pass1);
1905
1906
38.6k
        int count = -1;
1907
417k
        for (auto const& og: object_stream_to_objects[old_id]) {
1908
417k
            ++count;
1909
417k
            int new_o = obj[og].renumber;
1910
417k
            if (first_obj == -1) {
1911
38.6k
                first_obj = new_o;
1912
38.6k
            }
1913
417k
            if (cfg.qdf()) {
1914
30.4k
                write("%% Object stream: object ").write(new_o).write(", index ").write(count);
1915
30.4k
                if (!cfg.no_original_object_ids()) {
1916
30.4k
                    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
30.4k
                    if (og.getGen() != 0) {
1921
0
                        write(" ").write(og.getGen());
1922
0
                    }
1923
30.4k
                }
1924
30.4k
                write("\n");
1925
30.4k
            }
1926
1927
417k
            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
417k
            indicateProgress(true, false);
1931
1932
417k
            QPDFObjectHandle obj_to_write = qpdf.getObject(og);
1933
417k
            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
2
                obj_to_write.warn("stream found inside object stream; treating as null");
1937
2
                obj_to_write = QPDFObjectHandle::newNull();
1938
2
            }
1939
417k
            writeObject(obj_to_write, count);
1940
1941
417k
            new_obj[new_o].xref = QPDFXRefEntry(new_stream_id, count);
1942
417k
        }
1943
38.6k
    }
1944
38.6k
    {
1945
        // Adjust offsets to skip over comment before first object
1946
38.6k
        first = offsets.at(0);
1947
416k
        for (auto& iter: offsets) {
1948
416k
            iter -= first;
1949
416k
        }
1950
1951
        // Take one pass at writing pairs of numbers so we can get their size information
1952
38.6k
        {
1953
38.6k
            auto pp_discard = pipeline_stack.activate(true);
1954
38.6k
            writeObjectStreamOffsets(offsets, first_obj);
1955
38.6k
            first += pipeline->getCount();
1956
38.6k
        }
1957
1958
        // Set up a stream to write the stream data into a buffer.
1959
38.6k
        auto pp_ostream = pipeline_stack.activate(stream_buffer_pass2);
1960
1961
38.6k
        writeObjectStreamOffsets(offsets, first_obj);
1962
38.6k
        write(stream_buffer_pass1);
1963
38.6k
        stream_buffer_pass1.clear();
1964
38.6k
        stream_buffer_pass1.shrink_to_fit();
1965
38.6k
        if (compressed) {
1966
35.1k
            stream_buffer_pass2 = pl::pipe<Pl_Flate>(stream_buffer_pass2, Pl_Flate::a_deflate);
1967
35.1k
        }
1968
38.6k
    }
1969
1970
    // Write the object
1971
38.6k
    openObject(new_stream_id);
1972
38.6k
    setDataKey(new_stream_id);
1973
38.6k
    write("<<").write_qdf("\n ").write(" /Type /ObjStm").write_qdf("\n ");
1974
38.6k
    size_t length = stream_buffer_pass2.size();
1975
38.6k
    adjustAESStreamLength(length);
1976
38.6k
    write(" /Length ").write(length).write_qdf("\n ");
1977
38.6k
    if (compressed) {
1978
35.1k
        write(" /Filter /FlateDecode");
1979
35.1k
    }
1980
38.6k
    write(" /N ").write(offsets.size()).write_qdf("\n ").write(" /First ").write(first);
1981
38.6k
    if (!object.null()) {
1982
        // If the original object has an /Extends key, preserve it.
1983
3.37k
        QPDFObjectHandle dict = object.getDict();
1984
3.37k
        QPDFObjectHandle extends = dict.getKey("/Extends");
1985
3.37k
        if (extends.isIndirect()) {
1986
883
            write_qdf("\n ").write(" /Extends ");
1987
883
            unparseChild(extends, 1, f_in_ostream);
1988
883
        }
1989
3.37k
    }
1990
38.6k
    write_qdf("\n").write_no_qdf(" ").write(">>\nstream\n").write_encrypted(stream_buffer_pass2);
1991
38.6k
    write(cfg.newline_before_endstream() ? "\nendstream" : "endstream");
1992
38.6k
    if (encryption) {
1993
7.32k
        cur_data_key.clear();
1994
7.32k
    }
1995
38.6k
    closeObject(new_stream_id);
1996
38.6k
}
1997
1998
void
1999
impl::Writer::writeObject(QPDFObjectHandle object, int object_stream_index)
2000
1.26M
{
2001
1.26M
    QPDFObjGen old_og = object.getObjGen();
2002
2003
1.26M
    if (object_stream_index == -1 && old_og.getGen() == 0 &&
2004
841k
        object_stream_to_objects.contains(old_og.getObj())) {
2005
38.6k
        writeObjectStream(object);
2006
38.6k
        return;
2007
38.6k
    }
2008
2009
1.22M
    indicateProgress(false, false);
2010
1.22M
    auto new_id = obj[old_og].renumber;
2011
1.22M
    if (cfg.qdf()) {
2012
170k
        if (page_object_to_seq.contains(old_og)) {
2013
19.7k
            write("%% Page ").write(page_object_to_seq[old_og]).write("\n");
2014
19.7k
        }
2015
170k
        if (contents_to_page_seq.contains(old_og)) {
2016
12.2k
            write("%% Contents for page ").write(contents_to_page_seq[old_og]).write("\n");
2017
12.2k
        }
2018
170k
    }
2019
1.22M
    if (object_stream_index == -1) {
2020
812k
        if (cfg.qdf() && !cfg.no_original_object_ids()) {
2021
139k
            write("%% Original object ID: ").write(object.getObjGen().unparse(' ')).write("\n");
2022
139k
        }
2023
812k
        openObject(new_id);
2024
812k
        setDataKey(new_id);
2025
812k
        unparseObject(object, 0, 0);
2026
812k
        cur_data_key.clear();
2027
812k
        closeObject(new_id);
2028
812k
    } else {
2029
417k
        unparseObject(object, 0, f_in_ostream);
2030
417k
        write("\n");
2031
417k
    }
2032
2033
1.22M
    if (!cfg.direct_stream_lengths() && object.isStream()) {
2034
40.5k
        if (cfg.qdf()) {
2035
40.5k
            if (added_newline) {
2036
24.8k
                write("%QDF: ignore_newline\n");
2037
24.8k
            }
2038
40.5k
        }
2039
40.5k
        openObject(new_id + 1);
2040
40.5k
        write(cur_stream_length);
2041
40.5k
        closeObject(new_id + 1);
2042
40.5k
    }
2043
1.22M
}
2044
2045
std::string
2046
impl::Writer::getOriginalID1()
2047
118k
{
2048
118k
    QPDFObjectHandle trailer = qpdf.getTrailer();
2049
118k
    if (trailer.hasKey("/ID")) {
2050
10.5k
        return trailer.getKey("/ID").getArrayItem(0).getStringValue();
2051
107k
    } else {
2052
107k
        return "";
2053
107k
    }
2054
118k
}
2055
2056
void
2057
impl::Writer::generateID(bool encrypted)
2058
117k
{
2059
    // Generate the ID lazily so that we can handle the user's preference to use static or
2060
    // deterministic ID generation.
2061
2062
117k
    if (!id2.empty()) {
2063
54.9k
        return;
2064
54.9k
    }
2065
2066
62.2k
    QPDFObjectHandle trailer = qpdf.getTrailer();
2067
2068
62.2k
    std::string result;
2069
2070
62.2k
    if (cfg.static_id()) {
2071
        // For test suite use only...
2072
31.4k
        static unsigned char tmp[] = {
2073
31.4k
            0x31,
2074
31.4k
            0x41,
2075
31.4k
            0x59,
2076
31.4k
            0x26,
2077
31.4k
            0x53,
2078
31.4k
            0x58,
2079
31.4k
            0x97,
2080
31.4k
            0x93,
2081
31.4k
            0x23,
2082
31.4k
            0x84,
2083
31.4k
            0x62,
2084
31.4k
            0x64,
2085
31.4k
            0x33,
2086
31.4k
            0x83,
2087
31.4k
            0x27,
2088
31.4k
            0x95,
2089
31.4k
            0x00};
2090
31.4k
        result = reinterpret_cast<char*>(tmp);
2091
31.4k
    } else {
2092
        // The PDF specification has guidelines for creating IDs, but it states clearly that the
2093
        // only thing that's really important is that it is very likely to be unique.  We can't
2094
        // really follow the guidelines in the spec exactly because we haven't written the file yet.
2095
        // This scheme should be fine though.  The deterministic ID case uses a digest of a
2096
        // sufficient portion of the file's contents such no two non-matching files would match in
2097
        // the subsets used for this computation.  Note that we explicitly omit the filename from
2098
        // the digest calculation for deterministic ID so that the same file converted with qpdf, in
2099
        // that case, would have the same ID regardless of the output file's name.
2100
2101
30.7k
        std::string seed;
2102
30.7k
        if (cfg.deterministic_id()) {
2103
30.7k
            if (encrypted) {
2104
128
                throw std::runtime_error(
2105
128
                    "QPDFWriter: unable to generated a deterministic ID because the file to be "
2106
128
                    "written is encrypted (even though the file may not require a password)");
2107
128
            }
2108
30.6k
            if (deterministic_id_data.empty()) {
2109
0
                throw std::logic_error(
2110
0
                    "INTERNAL ERROR: QPDFWriter::generateID has no data for deterministic ID");
2111
0
            }
2112
30.6k
            seed += deterministic_id_data;
2113
30.6k
        } else {
2114
0
            seed += std::to_string(QUtil::get_current_time());
2115
0
            seed += filename;
2116
0
            seed += " ";
2117
0
        }
2118
30.6k
        seed += " QPDF ";
2119
30.6k
        if (trailer.hasKey("/Info")) {
2120
16.2k
            for (auto const& item: trailer.getKey("/Info").as_dictionary()) {
2121
16.2k
                if (item.second.isString()) {
2122
4.98k
                    seed += " ";
2123
4.98k
                    seed += item.second.getStringValue();
2124
4.98k
                }
2125
16.2k
            }
2126
1.02k
        }
2127
2128
30.6k
        MD5 md5;
2129
30.6k
        md5.encodeString(seed.c_str());
2130
30.6k
        MD5::Digest digest;
2131
30.6k
        md5.digest(digest);
2132
30.6k
        result = std::string(reinterpret_cast<char*>(digest), sizeof(MD5::Digest));
2133
30.6k
    }
2134
2135
    // If /ID already exists, follow the spec: use the original first word and generate a new second
2136
    // word.  Otherwise, we'll use the generated ID for both.
2137
2138
62.1k
    id2 = result;
2139
    // Note: keep /ID from old file even if --static-id was given.
2140
62.1k
    id1 = getOriginalID1();
2141
62.1k
    if (id1.empty()) {
2142
56.7k
        id1 = id2;
2143
56.7k
    }
2144
62.1k
}
2145
2146
void
2147
impl::Writer::initializeSpecialStreams()
2148
17.6k
{
2149
    // Mark all page content streams in case we are filtering or normalizing.
2150
17.6k
    int num = 0;
2151
20.5k
    for (auto& page: pages) {
2152
20.5k
        page_object_to_seq[page.getObjGen()] = ++num;
2153
20.5k
        QPDFObjectHandle contents = page.getKey("/Contents");
2154
20.5k
        std::vector<QPDFObjGen> contents_objects;
2155
20.5k
        if (contents.isArray()) {
2156
1.04k
            int n = static_cast<int>(contents.size());
2157
69.3k
            for (int i = 0; i < n; ++i) {
2158
68.2k
                contents_objects.push_back(contents.getArrayItem(i).getObjGen());
2159
68.2k
            }
2160
19.5k
        } else if (contents.isStream()) {
2161
3.35k
            contents_objects.push_back(contents.getObjGen());
2162
3.35k
        }
2163
2164
71.6k
        for (auto const& c: contents_objects) {
2165
71.6k
            contents_to_page_seq[c] = num;
2166
71.6k
            normalized_streams.insert(c);
2167
71.6k
        }
2168
20.5k
    }
2169
17.6k
}
2170
2171
void
2172
impl::Writer::preserveObjectStreams()
2173
34.3k
{
2174
34.3k
    auto const& xref = objects.xref_table();
2175
    // Our object_to_object_stream map has to map ObjGen -> ObjGen since we may be generating object
2176
    // streams out of old objects that have generation numbers greater than zero. However in an
2177
    // existing PDF, all object stream objects and all objects in them must have generation 0
2178
    // because the PDF spec does not provide any way to do otherwise. This code filters out objects
2179
    // that are not allowed to be in object streams. In addition to removing objects that were
2180
    // erroneously included in object streams in the source PDF, it also prevents unreferenced
2181
    // objects from being included.
2182
34.3k
    auto end = xref.cend();
2183
34.3k
    obj.streams_empty = true;
2184
34.3k
    if (cfg.preserve_unreferenced()) {
2185
0
        for (auto iter = xref.cbegin(); iter != end; ++iter) {
2186
0
            if (iter->second.getType() == 2) {
2187
                // Pdf contains object streams.
2188
0
                obj.streams_empty = false;
2189
0
                obj[iter->first].object_stream = iter->second.getObjStreamNumber();
2190
0
            }
2191
0
        }
2192
34.3k
    } else {
2193
        // Start by scanning for first compressed object in case we don't have any object streams to
2194
        // process.
2195
323k
        for (auto iter = xref.cbegin(); iter != end; ++iter) {
2196
293k
            if (iter->second.getType() == 2) {
2197
                // Pdf contains object streams.
2198
4.05k
                obj.streams_empty = false;
2199
4.05k
                auto eligible = objects.compressible_set();
2200
                // The object pointed to by iter may be a previous generation, in which case it is
2201
                // removed by compressible_set. We need to restart the loop (while the object
2202
                // table may contain multiple generations of an object).
2203
785k
                for (iter = xref.cbegin(); iter != end; ++iter) {
2204
781k
                    if (iter->second.getType() == 2) {
2205
713k
                        auto id = static_cast<size_t>(iter->first.getObj());
2206
713k
                        if (id < eligible.size() && eligible[id]) {
2207
111k
                            obj[iter->first].object_stream = iter->second.getObjStreamNumber();
2208
601k
                        } else {
2209
601k
                            QTC::TC("qpdf", "QPDFWriter exclude from object stream");
2210
601k
                        }
2211
713k
                    }
2212
781k
                }
2213
4.05k
                return;
2214
4.05k
            }
2215
293k
        }
2216
34.3k
    }
2217
34.3k
}
2218
2219
void
2220
impl::Writer::generateObjectStreams()
2221
18.4k
{
2222
    // Basic strategy: make a list of objects that can go into an object stream.  Then figure out
2223
    // how many object streams are needed so that we can distribute objects approximately evenly
2224
    // without having any object stream exceed 100 members.  We don't have to worry about linearized
2225
    // files here -- if the file is linearized, we take care of excluding things that aren't allowed
2226
    // here later.
2227
2228
    // This code doesn't do anything with /Extends.
2229
2230
18.4k
    auto eligible = objects.compressible_vector();
2231
18.4k
    size_t n_object_streams = (eligible.size() + 99U) / 100U;
2232
2233
18.4k
    initializeTables(2U * n_object_streams);
2234
18.4k
    if (n_object_streams == 0) {
2235
88
        obj.streams_empty = true;
2236
88
        return;
2237
88
    }
2238
18.3k
    size_t n_per = eligible.size() / n_object_streams;
2239
18.3k
    if (n_per * n_object_streams < eligible.size()) {
2240
234
        ++n_per;
2241
234
    }
2242
18.3k
    unsigned int n = 0;
2243
18.3k
    int cur_ostream = qpdf.newIndirectNull().getObjectID();
2244
195k
    for (auto const& item: eligible) {
2245
195k
        if (n == n_per) {
2246
1.00k
            n = 0;
2247
            // Construct a new null object as the "original" object stream.  The rest of the code
2248
            // knows that this means we're creating the object stream from scratch.
2249
1.00k
            cur_ostream = qpdf.newIndirectNull().getObjectID();
2250
1.00k
        }
2251
195k
        auto& o = obj[item];
2252
195k
        o.object_stream = cur_ostream;
2253
195k
        o.gen = item.getGen();
2254
195k
        ++n;
2255
195k
    }
2256
18.3k
}
2257
2258
Dictionary
2259
impl::Writer::trimmed_trailer()
2260
173k
{
2261
    // Remove keys from the trailer that necessarily have to be replaced when writing the file.
2262
2263
173k
    Dictionary trailer = qpdf.getTrailer().unsafeShallowCopy();
2264
2265
    // Remove encryption keys
2266
173k
    trailer.erase("/ID");
2267
173k
    trailer.erase("/Encrypt");
2268
2269
    // Remove modification information
2270
173k
    trailer.erase("/Prev");
2271
2272
    // Remove all trailer keys that potentially come from a cross-reference stream
2273
173k
    trailer.erase("/Index");
2274
173k
    trailer.erase("/W");
2275
173k
    trailer.erase("/Length");
2276
173k
    trailer.erase("/Filter");
2277
173k
    trailer.erase("/DecodeParms");
2278
173k
    trailer.erase("/Type");
2279
173k
    trailer.erase("/XRefStm");
2280
2281
173k
    return trailer;
2282
173k
}
2283
2284
// Make document extension level information direct as required by the spec.
2285
void
2286
impl::Writer::prepareFileForWrite()
2287
66.7k
{
2288
66.7k
    qpdf.fixDanglingReferences();
2289
66.7k
    auto root = qpdf.getRoot();
2290
66.7k
    auto oh = root.getKey("/Extensions");
2291
66.7k
    if (oh.isDictionary()) {
2292
3.26k
        const bool extensions_indirect = oh.isIndirect();
2293
3.26k
        if (extensions_indirect) {
2294
1.21k
            QTC::TC("qpdf", "QPDFWriter make Extensions direct");
2295
1.21k
            oh = root.replaceKeyAndGetNew("/Extensions", oh.shallowCopy());
2296
1.21k
        }
2297
3.26k
        if (oh.hasKey("/ADBE")) {
2298
2.10k
            auto adbe = oh.getKey("/ADBE");
2299
2.10k
            if (adbe.isIndirect()) {
2300
1.42k
                QTC::TC("qpdf", "QPDFWriter make ADBE direct", extensions_indirect ? 0 : 1);
2301
1.42k
                adbe.makeDirect();
2302
1.42k
                oh.replaceKey("/ADBE", adbe);
2303
1.42k
            }
2304
2.10k
        }
2305
3.26k
    }
2306
66.7k
}
2307
2308
void
2309
impl::Writer::initializeTables(size_t extra)
2310
67.4k
{
2311
67.4k
    auto size = objects.table_size() + 100u + extra;
2312
67.4k
    obj.resize(size);
2313
67.4k
    new_obj.resize(size);
2314
67.4k
}
2315
2316
void
2317
impl::Writer::doWriteSetup()
2318
67.7k
{
2319
67.7k
    if (did_write_setup) {
2320
0
        return;
2321
0
    }
2322
67.7k
    did_write_setup = true;
2323
2324
    // Do preliminary setup
2325
2326
67.7k
    if (cfg.linearize()) {
2327
35.2k
        cfg.qdf(false);
2328
35.2k
    }
2329
2330
67.7k
    if (cfg.pclm()) {
2331
0
        encryption = nullptr;
2332
0
    }
2333
2334
67.7k
    if (encryption) {
2335
        // Encryption has been explicitly set
2336
31.4k
        cfg.preserve_encryption(false);
2337
36.2k
    } else if (cfg.normalize_content() || cfg.pclm()) {
2338
        // Encryption makes looking at contents pretty useless.  If the user explicitly encrypted
2339
        // though, we still obey that.
2340
17.6k
        cfg.preserve_encryption(false);
2341
17.6k
    }
2342
2343
67.7k
    if (cfg.preserve_encryption()) {
2344
18.6k
        copyEncryptionParameters(qpdf);
2345
18.6k
    }
2346
2347
67.7k
    if (!cfg.forced_pdf_version().empty()) {
2348
0
        int major = 0;
2349
0
        int minor = 0;
2350
0
        parseVersion(cfg.forced_pdf_version(), major, minor);
2351
0
        disableIncompatibleEncryption(major, minor, cfg.forced_extension_level());
2352
0
        if (compareVersions(major, minor, 1, 5) < 0) {
2353
0
            cfg.object_streams(qpdf_o_disable);
2354
0
        }
2355
0
    }
2356
2357
67.7k
    if (cfg.qdf() || cfg.normalize_content()) {
2358
17.6k
        initializeSpecialStreams();
2359
17.6k
    }
2360
2361
67.7k
    switch (cfg.object_streams()) {
2362
14.7k
    case qpdf_o_disable:
2363
14.7k
        initializeTables();
2364
14.7k
        obj.streams_empty = true;
2365
14.7k
        break;
2366
2367
34.3k
    case qpdf_o_preserve:
2368
34.3k
        initializeTables();
2369
34.3k
        preserveObjectStreams();
2370
34.3k
        break;
2371
2372
18.4k
    case qpdf_o_generate:
2373
18.4k
        generateObjectStreams();
2374
18.4k
        break;
2375
67.7k
    }
2376
2377
67.3k
    if (!obj.streams_empty) {
2378
22.3k
        if (cfg.linearize()) {
2379
            // Page dictionaries are not allowed to be compressed objects.
2380
31.0k
            for (auto& page: pages) {
2381
31.0k
                if (obj[page].object_stream > 0) {
2382
25.6k
                    obj[page].object_stream = 0;
2383
25.6k
                }
2384
31.0k
            }
2385
20.4k
        }
2386
2387
22.3k
        if (cfg.linearize() || encryption) {
2388
            // The document catalog is not allowed to be compressed in linearized files either.
2389
            // It also appears that Adobe Reader 8.0.0 has a bug that prevents it from being able to
2390
            // handle encrypted files with compressed document catalogs, so we disable them in that
2391
            // case as well.
2392
20.3k
            if (obj[root_og].object_stream > 0) {
2393
15.9k
                obj[root_og].object_stream = 0;
2394
15.9k
            }
2395
20.3k
        }
2396
2397
        // Generate reverse mapping from object stream to objects
2398
13.1M
        obj.forEach([this](auto id, auto const& item) -> void {
2399
13.1M
            if (item.object_stream > 0) {
2400
264k
                auto& vec = object_stream_to_objects[item.object_stream];
2401
264k
                vec.emplace_back(id, item.gen);
2402
264k
                if (max_ostream_index < vec.size()) {
2403
110k
                    ++max_ostream_index;
2404
110k
                }
2405
264k
            }
2406
13.1M
        });
2407
22.3k
        --max_ostream_index;
2408
2409
22.3k
        if (object_stream_to_objects.empty()) {
2410
3.49k
            obj.streams_empty = true;
2411
18.8k
        } else {
2412
18.8k
            setMinimumPDFVersion("1.5");
2413
18.8k
        }
2414
22.3k
    }
2415
2416
67.3k
    setMinimumPDFVersion(qpdf.getPDFVersion(), qpdf.getExtensionLevel());
2417
67.3k
    final_pdf_version = min_pdf_version;
2418
67.3k
    final_extension_level = min_extension_level;
2419
67.3k
    if (!cfg.forced_pdf_version().empty()) {
2420
0
        final_pdf_version = cfg.forced_pdf_version();
2421
0
        final_extension_level = cfg.forced_extension_level();
2422
0
    }
2423
67.3k
}
2424
2425
void
2426
QPDFWriter::write()
2427
67.7k
{
2428
67.7k
    m->write();
2429
67.7k
}
2430
2431
void
2432
impl::Writer::write()
2433
67.7k
{
2434
67.7k
    doWriteSetup();
2435
2436
    // Set up progress reporting. For linearized files, we write two passes. events_expected is an
2437
    // approximation, but it's good enough for progress reporting, which is mostly a guess anyway.
2438
67.7k
    events_expected = QIntC::to_int(qpdf.getObjectCount() * (cfg.linearize() ? 2 : 1));
2439
2440
67.7k
    prepareFileForWrite();
2441
2442
67.7k
    if (cfg.linearize()) {
2443
34.5k
        writeLinearized();
2444
34.5k
    } else {
2445
33.2k
        writeStandard();
2446
33.2k
    }
2447
2448
67.7k
    pipeline->finish();
2449
67.7k
    if (close_file) {
2450
0
        fclose(file);
2451
0
    }
2452
67.7k
    file = nullptr;
2453
67.7k
    if (buffer_pipeline) {
2454
0
        output_buffer = buffer_pipeline->getBuffer();
2455
0
        buffer_pipeline = nullptr;
2456
0
    }
2457
67.7k
    indicateProgress(false, true);
2458
67.7k
}
2459
2460
QPDFObjGen
2461
QPDFWriter::getRenumberedObjGen(QPDFObjGen og)
2462
0
{
2463
0
    return {m->obj[og].renumber, 0};
2464
0
}
2465
2466
std::map<QPDFObjGen, QPDFXRefEntry>
2467
QPDFWriter::getWrittenXRefTable()
2468
0
{
2469
0
    return m->getWrittenXRefTable();
2470
0
}
2471
2472
std::map<QPDFObjGen, QPDFXRefEntry>
2473
impl::Writer::getWrittenXRefTable()
2474
0
{
2475
0
    std::map<QPDFObjGen, QPDFXRefEntry> result;
2476
2477
0
    auto it = result.begin();
2478
0
    new_obj.forEach([&it, &result](auto id, auto const& item) -> void {
2479
0
        if (item.xref.getType() != 0) {
2480
0
            it = result.emplace_hint(it, QPDFObjGen(id, 0), item.xref);
2481
0
        }
2482
0
    });
2483
0
    return result;
2484
0
}
2485
2486
void
2487
impl::Writer::enqueuePart(std::vector<QPDFObjectHandle>& part)
2488
152k
{
2489
354k
    for (auto const& oh: part) {
2490
354k
        enqueue(oh);
2491
354k
    }
2492
152k
}
2493
2494
void
2495
impl::Writer::writeEncryptionDictionary()
2496
42.0k
{
2497
42.0k
    encryption_dict_objid = openObject(encryption_dict_objid);
2498
42.0k
    auto& enc = *encryption;
2499
42.0k
    auto const V = enc.getV();
2500
2501
42.0k
    write("<<");
2502
42.0k
    if (V >= 4) {
2503
27.8k
        write(" /CF << /StdCF << /AuthEvent /DocOpen /CFM ");
2504
27.8k
        write(cfg.encrypt_use_aes() ? (V < 5 ? "/AESV2" : "/AESV3") : "/V2");
2505
        // The PDF spec says the /Length key is optional, but the PDF previewer on some versions of
2506
        // MacOS won't open encrypted files without it.
2507
27.8k
        write(V < 5 ? " /Length 16 >> >>" : " /Length 32 >> >>");
2508
27.8k
        if (!encryption->getEncryptMetadata()) {
2509
0
            write(" /EncryptMetadata false");
2510
0
        }
2511
27.8k
    }
2512
42.0k
    write(" /Filter /Standard /Length ").write(enc.getLengthBytes() * 8);
2513
42.0k
    write(" /O ").write_string(enc.getO(), true);
2514
42.0k
    if (V >= 4) {
2515
27.8k
        write(" /OE ").write_string(enc.getOE(), true);
2516
27.8k
    }
2517
42.0k
    write(" /P ").write(enc.getP());
2518
42.0k
    if (V >= 5) {
2519
27.8k
        write(" /Perms ").write_string(enc.getPerms(), true);
2520
27.8k
    }
2521
42.0k
    write(" /R ").write(enc.getR());
2522
2523
42.0k
    if (V >= 4) {
2524
27.8k
        write(" /StmF /StdCF /StrF /StdCF");
2525
27.8k
    }
2526
42.0k
    write(" /U ").write_string(enc.getU(), true);
2527
42.0k
    if (V >= 4) {
2528
27.8k
        write(" /UE ").write_string(enc.getUE(), true);
2529
27.8k
    }
2530
42.0k
    write(" /V ").write(enc.getV()).write(" >>");
2531
42.0k
    closeObject(encryption_dict_objid);
2532
42.0k
}
2533
2534
std::string
2535
QPDFWriter::getFinalVersion()
2536
0
{
2537
0
    m->doWriteSetup();
2538
0
    return m->final_pdf_version;
2539
0
}
2540
2541
void
2542
impl::Writer::writeHeader()
2543
87.9k
{
2544
87.9k
    write("%PDF-").write(final_pdf_version);
2545
87.9k
    if (cfg.pclm()) {
2546
        // PCLm version
2547
0
        write("\n%PCLm 1.0\n");
2548
87.9k
    } else {
2549
        // This string of binary characters would not be valid UTF-8, so it really should be treated
2550
        // as binary.
2551
87.9k
        write("\n%\xbf\xf7\xa2\xfe\n");
2552
87.9k
    }
2553
87.9k
    write_qdf("%QDF-1.0\n\n");
2554
2555
    // Note: do not write extra header text here.  Linearized PDFs must include the entire
2556
    // linearization parameter dictionary within the first 1024 characters of the PDF file, so for
2557
    // linearized files, we have to write extra header text after the linearization parameter
2558
    // dictionary.
2559
87.9k
}
2560
2561
void
2562
impl::Writer::writeHintStream(int hint_id)
2563
27.2k
{
2564
27.2k
    std::string hint_buffer;
2565
27.2k
    int S = 0;
2566
27.2k
    int O = 0;
2567
27.2k
    bool compressed = cfg.compress_streams();
2568
27.2k
    lin.generateHintStream(new_obj, obj, hint_buffer, S, O, compressed);
2569
2570
27.2k
    openObject(hint_id);
2571
27.2k
    setDataKey(hint_id);
2572
2573
27.2k
    size_t hlen = hint_buffer.size();
2574
2575
27.2k
    write("<< ");
2576
27.2k
    if (compressed) {
2577
27.2k
        write("/Filter /FlateDecode ");
2578
27.2k
    }
2579
27.2k
    write("/S ").write(S);
2580
27.2k
    if (O) {
2581
636
        write(" /O ").write(O);
2582
636
    }
2583
27.2k
    adjustAESStreamLength(hlen);
2584
27.2k
    write(" /Length ").write(hlen);
2585
27.2k
    write(" >>\nstream\n").write_encrypted(hint_buffer);
2586
2587
27.2k
    if (encryption) {
2588
13.5k
        QTC::TC("qpdf", "QPDFWriter encrypted hint stream");
2589
13.5k
    }
2590
2591
27.2k
    write(hint_buffer.empty() || hint_buffer.back() != '\n' ? "\nendstream" : "endstream");
2592
27.2k
    closeObject(hint_id);
2593
27.2k
}
2594
2595
qpdf_offset_t
2596
impl::Writer::writeXRefTable(trailer_e which, int first, int last, int size)
2597
30.5k
{
2598
    // There are too many extra arguments to replace overloaded function with defaults in the header
2599
    // file...too much risk of leaving something off.
2600
30.5k
    return writeXRefTable(which, first, last, size, 0, false, 0, 0, 0, 0);
2601
30.5k
}
2602
2603
qpdf_offset_t
2604
impl::Writer::writeXRefTable(
2605
    trailer_e which,
2606
    int first,
2607
    int last,
2608
    int size,
2609
    qpdf_offset_t prev,
2610
    bool suppress_offsets,
2611
    int hint_id,
2612
    qpdf_offset_t hint_offset,
2613
    qpdf_offset_t hint_length,
2614
    int linearization_pass)
2615
86.0k
{
2616
86.0k
    write("xref\n").write(first).write(" ").write(last - first + 1);
2617
86.0k
    qpdf_offset_t space_before_zero = pipeline->getCount();
2618
86.0k
    write("\n");
2619
86.0k
    if (first == 0) {
2620
57.9k
        write("0000000000 65535 f \n");
2621
57.9k
        ++first;
2622
57.9k
    }
2623
770k
    for (int i = first; i <= last; ++i) {
2624
684k
        qpdf_offset_t offset = 0;
2625
684k
        if (!suppress_offsets) {
2626
539k
            offset = new_obj[i].xref.getOffset();
2627
539k
            if ((hint_id != 0) && (i != hint_id) && (offset >= hint_offset)) {
2628
74.9k
                offset += hint_length;
2629
74.9k
            }
2630
539k
        }
2631
684k
        write(QUtil::int_to_string(offset, 10)).write(" 00000 n \n");
2632
684k
    }
2633
86.0k
    writeTrailer(which, size, false, prev, linearization_pass);
2634
86.0k
    write("\n");
2635
86.0k
    return space_before_zero;
2636
86.0k
}
2637
2638
qpdf_offset_t
2639
impl::Writer::writeXRefStream(
2640
    int objid, int max_id, qpdf_offset_t max_offset, trailer_e which, int first, int last, int size)
2641
681
{
2642
    // There are too many extra arguments to replace overloaded function with defaults in the header
2643
    // file...too much risk of leaving something off.
2644
681
    return writeXRefStream(
2645
681
        objid, max_id, max_offset, which, first, last, size, 0, 0, 0, 0, false, 0);
2646
681
}
2647
2648
qpdf_offset_t
2649
impl::Writer::writeXRefStream(
2650
    int xref_id,
2651
    int max_id,
2652
    qpdf_offset_t max_offset,
2653
    trailer_e which,
2654
    int first,
2655
    int last,
2656
    int size,
2657
    qpdf_offset_t prev,
2658
    int hint_id,
2659
    qpdf_offset_t hint_offset,
2660
    qpdf_offset_t hint_length,
2661
    bool skip_compression,
2662
    int linearization_pass)
2663
55.5k
{
2664
55.5k
    qpdf_offset_t xref_offset = pipeline->getCount();
2665
55.5k
    qpdf_offset_t space_before_zero = xref_offset - 1;
2666
2667
    // field 1 contains offsets and object stream identifiers
2668
55.5k
    unsigned int f1_size = std::max(bytesNeeded(max_offset + hint_length), bytesNeeded(max_id));
2669
2670
    // field 2 contains object stream indices
2671
55.5k
    unsigned int f2_size = bytesNeeded(QIntC::to_longlong(max_ostream_index));
2672
2673
55.5k
    unsigned int esize = 1 + f1_size + f2_size;
2674
2675
    // Must store in xref table in advance of writing the actual data rather than waiting for
2676
    // openObject to do it.
2677
55.5k
    new_obj[xref_id].xref = QPDFXRefEntry(pipeline->getCount());
2678
2679
55.5k
    std::string xref_data;
2680
55.5k
    const bool compressed = cfg.compress_streams() && !cfg.qdf();
2681
55.5k
    {
2682
55.5k
        auto pp_xref = pipeline_stack.activate(xref_data);
2683
2684
911k
        for (int i = first; i <= last; ++i) {
2685
855k
            QPDFXRefEntry& e = new_obj[i].xref;
2686
855k
            switch (e.getType()) {
2687
199k
            case 0:
2688
199k
                writeBinary(0, 1);
2689
199k
                writeBinary(0, f1_size);
2690
199k
                writeBinary(0, f2_size);
2691
199k
                break;
2692
2693
305k
            case 1:
2694
305k
                {
2695
305k
                    qpdf_offset_t offset = e.getOffset();
2696
305k
                    if ((hint_id != 0) && (i != hint_id) && (offset >= hint_offset)) {
2697
84.6k
                        offset += hint_length;
2698
84.6k
                    }
2699
305k
                    writeBinary(1, 1);
2700
305k
                    writeBinary(QIntC::to_ulonglong(offset), f1_size);
2701
305k
                    writeBinary(0, f2_size);
2702
305k
                }
2703
305k
                break;
2704
2705
350k
            case 2:
2706
350k
                writeBinary(2, 1);
2707
350k
                writeBinary(QIntC::to_ulonglong(e.getObjStreamNumber()), f1_size);
2708
350k
                writeBinary(QIntC::to_ulonglong(e.getObjStreamIndex()), f2_size);
2709
350k
                break;
2710
2711
0
            default:
2712
0
                throw std::logic_error("invalid type writing xref stream");
2713
0
                break;
2714
855k
            }
2715
855k
        }
2716
55.5k
    }
2717
2718
55.5k
    if (compressed) {
2719
54.9k
        xref_data = pl::pipe<Pl_PNGFilter>(xref_data, Pl_PNGFilter::a_encode, esize);
2720
54.9k
        if (!skip_compression) {
2721
            // Write the stream dictionary for compression but don't actually compress.  This
2722
            // helps us with computation of padding for pass 1 of linearization.
2723
26.8k
            xref_data = pl::pipe<Pl_Flate>(xref_data, Pl_Flate::a_deflate);
2724
26.8k
        }
2725
54.9k
    }
2726
2727
55.5k
    openObject(xref_id);
2728
55.5k
    write("<<").write_qdf("\n ").write(" /Type /XRef").write_qdf("\n ");
2729
55.5k
    write(" /Length ").write(xref_data.size());
2730
55.5k
    if (compressed) {
2731
54.9k
        write_qdf("\n ").write(" /Filter /FlateDecode").write_qdf("\n ");
2732
54.9k
        write(" /DecodeParms << /Columns ").write(esize).write(" /Predictor 12 >>");
2733
54.9k
    }
2734
55.5k
    write_qdf("\n ").write(" /W [ 1 ").write(f1_size).write(" ").write(f2_size).write(" ]");
2735
55.5k
    if (!(first == 0 && last == (size - 1))) {
2736
28.0k
        write(" /Index [ ").write(first).write(" ").write(last - first + 1).write(" ]");
2737
28.0k
    }
2738
55.5k
    writeTrailer(which, size, true, prev, linearization_pass);
2739
55.5k
    write("\nstream\n").write(xref_data).write("\nendstream");
2740
55.5k
    closeObject(xref_id);
2741
55.5k
    return space_before_zero;
2742
55.5k
}
2743
2744
size_t
2745
impl::Writer::calculateXrefStreamPadding(qpdf_offset_t xref_bytes)
2746
27.7k
{
2747
    // This routine is called right after a linearization first pass xref stream has been written
2748
    // without compression.  Calculate the amount of padding that would be required in the worst
2749
    // case, assuming the number of uncompressed bytes remains the same. The worst case for zlib is
2750
    // that the output is larger than the input by 6 bytes plus 5 bytes per 16K, and then we'll add
2751
    // 10 extra bytes for number length increases.
2752
2753
27.7k
    return QIntC::to_size(16 + (5 * ((xref_bytes + 16383) / 16384)));
2754
27.7k
}
2755
2756
void
2757
impl::Writer::writeLinearized()
2758
34.5k
{
2759
    // Optimize file and enqueue objects in order
2760
2761
34.5k
    std::map<int, int> stream_cache;
2762
2763
164k
    auto skip_stream_parameters = [this, &stream_cache](QPDFObjectHandle& stream) {
2764
164k
        if (auto& result = stream_cache[stream.getObjectID()]) {
2765
80.9k
            return result;
2766
83.6k
        } else {
2767
83.6k
            return result = will_filter_stream(stream) ? 2 : 1;
2768
83.6k
        }
2769
164k
    };
2770
2771
34.5k
    lin.optimize(obj, skip_stream_parameters);
2772
2773
34.5k
    std::vector<QPDFObjectHandle> part4;
2774
34.5k
    std::vector<QPDFObjectHandle> part6;
2775
34.5k
    std::vector<QPDFObjectHandle> part7;
2776
34.5k
    std::vector<QPDFObjectHandle> part8;
2777
34.5k
    std::vector<QPDFObjectHandle> part9;
2778
34.5k
    lin.parts(obj, part4, part6, part7, part8, part9);
2779
2780
    // Object number sequence:
2781
    //
2782
    //  second half
2783
    //    second half uncompressed objects
2784
    //    second half xref stream, if any
2785
    //    second half compressed objects
2786
    //  first half
2787
    //    linearization dictionary
2788
    //    first half xref stream, if any
2789
    //    part 4 uncompresesd objects
2790
    //    encryption dictionary, if any
2791
    //    hint stream
2792
    //    part 6 uncompressed objects
2793
    //    first half compressed objects
2794
    //
2795
2796
    // Second half objects
2797
34.5k
    int second_half_uncompressed = QIntC::to_int(part7.size() + part8.size() + part9.size());
2798
34.5k
    int second_half_first_obj = 1;
2799
34.5k
    int after_second_half = 1 + second_half_uncompressed;
2800
34.5k
    next_objid = after_second_half;
2801
34.5k
    int second_half_xref = 0;
2802
34.5k
    bool need_xref_stream = !obj.streams_empty;
2803
34.5k
    if (need_xref_stream) {
2804
15.0k
        second_half_xref = next_objid++;
2805
15.0k
    }
2806
    // Assign numbers to all compressed objects in the second half.
2807
34.5k
    std::vector<QPDFObjectHandle>* vecs2[] = {&part7, &part8, &part9};
2808
127k
    for (int i = 0; i < 3; ++i) {
2809
141k
        for (auto const& oh: *vecs2[i]) {
2810
141k
            assignCompressedObjectNumbers(oh.getObjGen());
2811
141k
        }
2812
92.6k
    }
2813
34.5k
    int second_half_end = next_objid - 1;
2814
34.5k
    int second_trailer_size = next_objid;
2815
2816
    // First half objects
2817
34.5k
    int first_half_start = next_objid;
2818
34.5k
    int lindict_id = next_objid++;
2819
34.5k
    int first_half_xref = 0;
2820
34.5k
    if (need_xref_stream) {
2821
15.0k
        first_half_xref = next_objid++;
2822
15.0k
    }
2823
34.5k
    int part4_first_obj = next_objid;
2824
34.5k
    next_objid += QIntC::to_int(part4.size());
2825
34.5k
    int after_part4 = next_objid;
2826
34.5k
    if (encryption) {
2827
15.5k
        encryption_dict_objid = next_objid++;
2828
15.5k
    }
2829
34.5k
    int hint_id = next_objid++;
2830
34.5k
    int part6_first_obj = next_objid;
2831
34.5k
    next_objid += QIntC::to_int(part6.size());
2832
34.5k
    int after_part6 = next_objid;
2833
    // Assign numbers to all compressed objects in the first half
2834
34.5k
    std::vector<QPDFObjectHandle>* vecs1[] = {&part4, &part6};
2835
96.2k
    for (int i = 0; i < 2; ++i) {
2836
214k
        for (auto const& oh: *vecs1[i]) {
2837
214k
            assignCompressedObjectNumbers(oh.getObjGen());
2838
214k
        }
2839
61.7k
    }
2840
34.5k
    int first_half_end = next_objid - 1;
2841
34.5k
    int first_trailer_size = next_objid;
2842
2843
34.5k
    int part4_end_marker = part4.back().getObjectID();
2844
34.5k
    int part6_end_marker = part6.back().getObjectID();
2845
34.5k
    qpdf_offset_t space_before_zero = 0;
2846
34.5k
    qpdf_offset_t file_size = 0;
2847
34.5k
    qpdf_offset_t part6_end_offset = 0;
2848
34.5k
    qpdf_offset_t first_half_max_obj_offset = 0;
2849
34.5k
    qpdf_offset_t second_xref_offset = 0;
2850
34.5k
    qpdf_offset_t first_xref_end = 0;
2851
34.5k
    qpdf_offset_t second_xref_end = 0;
2852
2853
34.5k
    next_objid = part4_first_obj;
2854
34.5k
    enqueuePart(part4);
2855
34.5k
    if (next_objid != after_part4) {
2856
        // This can happen with very botched files as in the fuzzer test. There are likely some
2857
        // faulty assumptions in calculateLinearizationData
2858
29
        throw std::runtime_error("error encountered after writing part 4 of linearized data");
2859
29
    }
2860
34.4k
    next_objid = part6_first_obj;
2861
34.4k
    enqueuePart(part6);
2862
34.4k
    util::no_ci_rt_error_if(
2863
34.4k
        next_objid != after_part6, "error encountered after writing part 6 of linearized data" //
2864
34.4k
    );
2865
34.4k
    next_objid = second_half_first_obj;
2866
34.4k
    enqueuePart(part7);
2867
34.4k
    enqueuePart(part8);
2868
34.4k
    enqueuePart(part9);
2869
34.4k
    util::no_ci_rt_error_if(
2870
34.4k
        next_objid != after_second_half,
2871
34.4k
        "error encountered after writing part 9 of linearized data" //
2872
34.4k
    );
2873
2874
34.4k
    qpdf_offset_t hint_length = 0;
2875
34.4k
    std::string hint_buffer;
2876
2877
    // Write file in two passes.  Part numbers refer to PDF spec 1.4.
2878
2879
34.4k
    FILE* lin_pass1_file = nullptr;
2880
34.4k
    auto pp_pass1 = pipeline_stack.popper();
2881
34.4k
    auto pp_md5 = pipeline_stack.popper();
2882
56.0k
    for (int pass: {1, 2}) {
2883
56.0k
        if (pass == 1) {
2884
28.8k
            if (!cfg.linearize_pass1().empty()) {
2885
0
                lin_pass1_file = QUtil::safe_fopen(cfg.linearize_pass1().data(), "wb");
2886
0
                pipeline_stack.activate(
2887
0
                    pp_pass1,
2888
0
                    std::make_unique<Pl_StdioFile>("linearization pass1", lin_pass1_file));
2889
28.8k
            } else {
2890
28.8k
                pipeline_stack.activate(pp_pass1, true);
2891
28.8k
            }
2892
28.8k
            if (cfg.deterministic_id()) {
2893
14.4k
                pipeline_stack.activate_md5(pp_md5);
2894
14.4k
            }
2895
28.8k
        }
2896
2897
        // Part 1: header
2898
2899
56.0k
        writeHeader();
2900
2901
        // Part 2: linearization parameter dictionary.  Save enough space to write real dictionary.
2902
        // 200 characters is enough space if all numerical values in the parameter dictionary that
2903
        // contain offsets are 20 digits long plus a few extra characters for safety.  The entire
2904
        // linearization parameter dictionary must appear within the first 1024 characters of the
2905
        // file.
2906
2907
56.0k
        qpdf_offset_t pos = pipeline->getCount();
2908
56.0k
        openObject(lindict_id);
2909
56.0k
        write("<<");
2910
56.0k
        if (pass == 2) {
2911
27.2k
            write(" /Linearized 1 /L ").write(file_size + hint_length);
2912
            // Implementation note 121 states that a space is mandatory after this open bracket.
2913
27.2k
            write(" /H [ ").write(new_obj[hint_id].xref.getOffset()).write(" ");
2914
27.2k
            write(hint_length);
2915
27.2k
            write(" ] /O ").write(obj[pages.all().at(0)].renumber);
2916
27.2k
            write(" /E ").write(part6_end_offset + hint_length);
2917
27.2k
            write(" /N ").write(pages.size());
2918
27.2k
            write(" /T ").write(space_before_zero + hint_length);
2919
27.2k
        }
2920
56.0k
        write(" >>");
2921
56.0k
        closeObject(lindict_id);
2922
56.0k
        static int const pad = 200;
2923
56.0k
        write(QIntC::to_size(pos - pipeline->getCount() + pad), ' ').write("\n");
2924
2925
        // If the user supplied any additional header text, write it here after the linearization
2926
        // parameter dictionary.
2927
56.0k
        write(cfg.extra_header_text());
2928
2929
        // Part 3: first page cross reference table and trailer.
2930
2931
56.0k
        qpdf_offset_t first_xref_offset = pipeline->getCount();
2932
56.0k
        qpdf_offset_t hint_offset = 0;
2933
56.0k
        if (pass == 2) {
2934
27.2k
            hint_offset = new_obj[hint_id].xref.getOffset();
2935
27.2k
        }
2936
56.0k
        if (need_xref_stream) {
2937
            // Must pad here too.
2938
28.0k
            if (pass == 1) {
2939
                // Set first_half_max_obj_offset to a value large enough to force four bytes to be
2940
                // reserved for each file offset.  This would provide adequate space for the xref
2941
                // stream as long as the last object in page 1 starts with in the first 4 GB of the
2942
                // file, which is extremely likely.  In the second pass, we will know the actual
2943
                // value for this, but it's okay if it's smaller.
2944
14.5k
                first_half_max_obj_offset = 1 << 25;
2945
14.5k
            }
2946
28.0k
            pos = pipeline->getCount();
2947
28.0k
            writeXRefStream(
2948
28.0k
                first_half_xref,
2949
28.0k
                first_half_end,
2950
28.0k
                first_half_max_obj_offset,
2951
28.0k
                t_lin_first,
2952
28.0k
                first_half_start,
2953
28.0k
                first_half_end,
2954
28.0k
                first_trailer_size,
2955
28.0k
                hint_length + second_xref_offset,
2956
28.0k
                hint_id,
2957
28.0k
                hint_offset,
2958
28.0k
                hint_length,
2959
28.0k
                (pass == 1),
2960
28.0k
                pass);
2961
28.0k
            qpdf_offset_t endpos = pipeline->getCount();
2962
28.0k
            if (pass == 1) {
2963
                // Pad so we have enough room for the real xref stream.
2964
14.2k
                write(calculateXrefStreamPadding(endpos - pos), ' ');
2965
14.2k
                first_xref_end = pipeline->getCount();
2966
14.2k
            } else {
2967
                // Pad so that the next object starts at the same place as in pass 1.
2968
13.7k
                write(QIntC::to_size(first_xref_end - endpos), ' ');
2969
2970
13.7k
                if (pipeline->getCount() != first_xref_end) {
2971
0
                    throw std::logic_error(
2972
0
                        "insufficient padding for first pass xref stream; first_xref_end=" +
2973
0
                        std::to_string(first_xref_end) + "; endpos=" + std::to_string(endpos));
2974
0
                }
2975
13.7k
            }
2976
28.0k
            write("\n");
2977
28.0k
        } else {
2978
28.0k
            writeXRefTable(
2979
28.0k
                t_lin_first,
2980
28.0k
                first_half_start,
2981
28.0k
                first_half_end,
2982
28.0k
                first_trailer_size,
2983
28.0k
                hint_length + second_xref_offset,
2984
28.0k
                (pass == 1),
2985
28.0k
                hint_id,
2986
28.0k
                hint_offset,
2987
28.0k
                hint_length,
2988
28.0k
                pass);
2989
28.0k
            write("startxref\n0\n%%EOF\n");
2990
28.0k
        }
2991
2992
        // Parts 4 through 9
2993
2994
589k
        for (auto const& cur_object: object_queue) {
2995
589k
            if (cur_object.getObjectID() == part6_end_marker) {
2996
55.4k
                first_half_max_obj_offset = pipeline->getCount();
2997
55.4k
            }
2998
589k
            writeObject(cur_object);
2999
589k
            if (cur_object.getObjectID() == part4_end_marker) {
3000
55.6k
                if (encryption) {
3001
27.8k
                    writeEncryptionDictionary();
3002
27.8k
                }
3003
55.6k
                if (pass == 1) {
3004
28.4k
                    new_obj[hint_id].xref = QPDFXRefEntry(pipeline->getCount());
3005
28.4k
                } else {
3006
                    // Part 5: hint stream
3007
27.2k
                    write(hint_buffer);
3008
27.2k
                }
3009
55.6k
            }
3010
589k
            if (cur_object.getObjectID() == part6_end_marker) {
3011
55.0k
                part6_end_offset = pipeline->getCount();
3012
55.0k
            }
3013
589k
        }
3014
3015
        // Part 10: overflow hint stream -- not used
3016
3017
        // Part 11: main cross reference table and trailer
3018
3019
56.0k
        second_xref_offset = pipeline->getCount();
3020
56.0k
        if (need_xref_stream) {
3021
26.8k
            pos = pipeline->getCount();
3022
26.8k
            space_before_zero = writeXRefStream(
3023
26.8k
                second_half_xref,
3024
26.8k
                second_half_end,
3025
26.8k
                second_xref_offset,
3026
26.8k
                t_lin_second,
3027
26.8k
                0,
3028
26.8k
                second_half_end,
3029
26.8k
                second_trailer_size,
3030
26.8k
                0,
3031
26.8k
                0,
3032
26.8k
                0,
3033
26.8k
                0,
3034
26.8k
                (pass == 1),
3035
26.8k
                pass);
3036
26.8k
            qpdf_offset_t endpos = pipeline->getCount();
3037
3038
26.8k
            if (pass == 1) {
3039
                // Pad so we have enough room for the real xref stream.  See comments for previous
3040
                // xref stream on how we calculate the padding.
3041
13.4k
                write(calculateXrefStreamPadding(endpos - pos), ' ').write("\n");
3042
13.4k
                second_xref_end = pipeline->getCount();
3043
13.4k
            } else {
3044
                // Make the file size the same.
3045
13.4k
                auto padding =
3046
13.4k
                    QIntC::to_size(second_xref_end + hint_length - 1 - pipeline->getCount());
3047
13.4k
                write(padding, ' ').write("\n");
3048
3049
                // If this assertion fails, maybe we didn't have enough padding above.
3050
13.4k
                if (pipeline->getCount() != second_xref_end + hint_length) {
3051
0
                    throw std::logic_error(
3052
0
                        "count mismatch after xref stream; possible insufficient padding?");
3053
0
                }
3054
13.4k
            }
3055
29.1k
        } else {
3056
29.1k
            space_before_zero = writeXRefTable(
3057
29.1k
                t_lin_second, 0, second_half_end, second_trailer_size, 0, false, 0, 0, 0, pass);
3058
29.1k
        }
3059
56.0k
        write("startxref\n").write(first_xref_offset).write("\n%%EOF\n");
3060
3061
56.0k
        if (pass == 1) {
3062
27.2k
            if (cfg.deterministic_id()) {
3063
13.6k
                QTC::TC("qpdf", "QPDFWriter linearized deterministic ID", need_xref_stream ? 0 : 1);
3064
13.6k
                computeDeterministicIDData();
3065
13.6k
                pp_md5.pop();
3066
13.6k
            }
3067
3068
            // Close first pass pipeline
3069
27.2k
            file_size = pipeline->getCount();
3070
27.2k
            pp_pass1.pop();
3071
3072
            // Save hint offset since it will be set to zero by calling openObject.
3073
27.2k
            qpdf_offset_t hint_offset1 = new_obj[hint_id].xref.getOffset();
3074
3075
            // Write hint stream to a buffer
3076
27.2k
            {
3077
27.2k
                auto pp_hint = pipeline_stack.activate(hint_buffer);
3078
27.2k
                writeHintStream(hint_id);
3079
27.2k
            }
3080
27.2k
            hint_length = QIntC::to_offset(hint_buffer.size());
3081
3082
            // Restore hint offset
3083
27.2k
            new_obj[hint_id].xref = QPDFXRefEntry(hint_offset1);
3084
27.2k
            if (lin_pass1_file) {
3085
                // Write some debugging information
3086
0
                fprintf(
3087
0
                    lin_pass1_file, "%% hint_offset=%s\n", std::to_string(hint_offset1).c_str());
3088
0
                fprintf(lin_pass1_file, "%% hint_length=%s\n", std::to_string(hint_length).c_str());
3089
0
                fprintf(
3090
0
                    lin_pass1_file,
3091
0
                    "%% second_xref_offset=%s\n",
3092
0
                    std::to_string(second_xref_offset).c_str());
3093
0
                fprintf(
3094
0
                    lin_pass1_file,
3095
0
                    "%% second_xref_end=%s\n",
3096
0
                    std::to_string(second_xref_end).c_str());
3097
0
                fclose(lin_pass1_file);
3098
0
                lin_pass1_file = nullptr;
3099
0
            }
3100
27.2k
        }
3101
56.0k
    }
3102
34.4k
}
3103
3104
void
3105
impl::Writer::enqueueObjectsStandard()
3106
31.8k
{
3107
31.8k
    if (cfg.preserve_unreferenced()) {
3108
0
        for (auto const& oh: qpdf.getAllObjects()) {
3109
0
            enqueue(oh);
3110
0
        }
3111
0
    }
3112
3113
    // Put root first on queue.
3114
31.8k
    auto trailer = trimmed_trailer();
3115
31.8k
    enqueue(trailer["/Root"]);
3116
3117
    // Next place any other objects referenced from the trailer dictionary into the queue, handling
3118
    // direct objects recursively. Root is already there, so enqueuing it a second time is a no-op.
3119
64.8k
    for (auto& item: trailer) {
3120
64.8k
        if (!item.second.null()) {
3121
55.1k
            enqueue(item.second);
3122
55.1k
        }
3123
64.8k
    }
3124
31.8k
}
3125
3126
void
3127
impl::Writer::enqueueObjectsPCLm()
3128
0
{
3129
    // Image transform stream content for page strip images. Each of this new stream has to come
3130
    // after every page image strip written in the pclm file.
3131
0
    std::string image_transform_content = "q /image Do Q\n";
3132
3133
    // enqueue all pages first
3134
0
    for (auto& page: pages) {
3135
0
        enqueue(page);
3136
0
        enqueue(page["/Contents"]);
3137
3138
        // enqueue all the strips for each page
3139
0
        for (auto& image: Dictionary(page["/Resources"]["/XObject"])) {
3140
0
            if (!image.second.null()) {
3141
0
                enqueue(image.second);
3142
0
                enqueue(qpdf.newStream(image_transform_content));
3143
0
            }
3144
0
        }
3145
0
    }
3146
3147
0
    enqueue(trimmed_trailer()["/Root"]);
3148
0
}
3149
3150
void
3151
impl::Writer::indicateProgress(bool decrement, bool finished)
3152
1.70M
{
3153
1.70M
    if (decrement) {
3154
417k
        --events_seen;
3155
417k
        return;
3156
417k
    }
3157
3158
1.28M
    ++events_seen;
3159
3160
1.28M
    if (!progress_reporter.get()) {
3161
1.28M
        return;
3162
1.28M
    }
3163
3164
0
    if (finished || events_seen >= next_progress_report) {
3165
0
        int percentage =
3166
0
            (finished ? 100
3167
0
                 : next_progress_report == 0
3168
0
                 ? 0
3169
0
                 : std::min(99, 1 + ((100 * events_seen) / events_expected)));
3170
0
        progress_reporter->reportProgress(percentage);
3171
0
    }
3172
0
    int increment = std::max(1, (events_expected / 100));
3173
0
    while (events_seen >= next_progress_report) {
3174
0
        next_progress_report += increment;
3175
0
    }
3176
0
}
3177
3178
void
3179
QPDFWriter::registerProgressReporter(std::shared_ptr<ProgressReporter> pr)
3180
0
{
3181
0
    m->progress_reporter = pr;
3182
0
}
3183
3184
void
3185
impl::Writer::writeStandard()
3186
31.8k
{
3187
31.8k
    auto pp_md5 = pipeline_stack.popper();
3188
31.8k
    if (cfg.deterministic_id()) {
3189
17.3k
        pipeline_stack.activate_md5(pp_md5);
3190
17.3k
    }
3191
3192
    // Start writing
3193
3194
31.8k
    writeHeader();
3195
31.8k
    write(cfg.extra_header_text());
3196
3197
31.8k
    if (cfg.pclm()) {
3198
0
        enqueueObjectsPCLm();
3199
31.8k
    } else {
3200
31.8k
        enqueueObjectsStandard();
3201
31.8k
    }
3202
3203
    // Now start walking queue, outputting each object.
3204
293k
    while (object_queue_front < object_queue.size()) {
3205
261k
        QPDFObjectHandle cur_object = object_queue.at(object_queue_front);
3206
261k
        ++object_queue_front;
3207
261k
        writeObject(cur_object);
3208
261k
    }
3209
3210
    // Write out the encryption dictionary, if any
3211
31.8k
    if (encryption) {
3212
14.1k
        writeEncryptionDictionary();
3213
14.1k
    }
3214
3215
    // Now write out xref.  next_objid is now the number of objects.
3216
31.8k
    qpdf_offset_t xref_offset = pipeline->getCount();
3217
31.8k
    if (object_stream_to_objects.empty()) {
3218
        // Write regular cross-reference table
3219
30.5k
        writeXRefTable(t_normal, 0, next_objid - 1, next_objid);
3220
30.5k
    } else {
3221
        // Write cross-reference stream.
3222
1.38k
        int xref_id = next_objid++;
3223
1.38k
        writeXRefStream(xref_id, xref_id, xref_offset, t_normal, 0, next_objid - 1, next_objid);
3224
1.38k
    }
3225
31.8k
    write("startxref\n").write(xref_offset).write("\n%%EOF\n");
3226
3227
31.8k
    if (cfg.deterministic_id()) {
3228
16.6k
        QTC::TC(
3229
16.6k
            "qpdf",
3230
16.6k
            "QPDFWriter standard deterministic ID",
3231
16.6k
            object_stream_to_objects.empty() ? 0 : 1);
3232
16.6k
    }
3233
31.8k
}