Coverage Report

Created: 2025-08-28 06:32

/src/qpdf/libqpdf/QPDF_Stream.cc
Line
Count
Source (jump to first uncovered line)
1
#include <qpdf/QPDFObjectHandle_private.hh>
2
3
#include <qpdf/ContentNormalizer.hh>
4
#include <qpdf/JSON_writer.hh>
5
#include <qpdf/Pipeline.hh>
6
#include <qpdf/Pipeline_private.hh>
7
#include <qpdf/Pl_Base64.hh>
8
#include <qpdf/Pl_Buffer.hh>
9
#include <qpdf/Pl_Count.hh>
10
#include <qpdf/Pl_Discard.hh>
11
#include <qpdf/Pl_Flate.hh>
12
#include <qpdf/Pl_QPDFTokenizer.hh>
13
#include <qpdf/QIntC.hh>
14
#include <qpdf/QPDFExc.hh>
15
#include <qpdf/QPDF_private.hh>
16
#include <qpdf/QTC.hh>
17
#include <qpdf/QUtil.hh>
18
#include <qpdf/SF_ASCII85Decode.hh>
19
#include <qpdf/SF_ASCIIHexDecode.hh>
20
#include <qpdf/SF_DCTDecode.hh>
21
#include <qpdf/SF_FlateLzwDecode.hh>
22
#include <qpdf/SF_RunLengthDecode.hh>
23
24
#include <stdexcept>
25
26
using namespace std::literals;
27
using namespace qpdf;
28
29
namespace
30
{
31
    class SF_Crypt final: public QPDFStreamFilter
32
    {
33
      public:
34
155
        SF_Crypt() = default;
35
        ~SF_Crypt() final = default;
36
37
        bool
38
        setDecodeParms(QPDFObjectHandle decode_parms) final
39
147
        {
40
            // we only validate here - processing happens in decryptStream
41
147
            if (auto dict = decode_parms.as_dictionary(optional)) {
42
410
                for (auto const& [key, value]: dict) {
43
410
                    if (key == "/Type" &&
44
410
                        (value.null() ||
45
53
                         (value.isName() && value.getName() == "/CryptFilterDecodeParms"))) {
46
25
                        continue;
47
25
                    }
48
385
                    if (key == "/Name") {
49
56
                        continue;
50
56
                    }
51
329
                    if (!value.null()) {
52
67
                        return false;
53
67
                    }
54
329
                }
55
70
                return true;
56
137
            }
57
10
            return false;
58
147
        }
59
60
        Pipeline*
61
        getDecodePipeline(Pipeline*) final
62
70
        {
63
            // Not used -- handled by pipeStreamData
64
70
            return nullptr;
65
70
        }
66
    };
67
68
    class StreamBlobProvider
69
    {
70
      public:
71
        StreamBlobProvider(Stream stream, qpdf_stream_decode_level_e decode_level) :
72
0
            stream(stream),
73
0
            decode_level(decode_level)
74
0
        {
75
0
        }
76
        void
77
        operator()(Pipeline* p)
78
0
        {
79
0
            stream.pipeStreamData(p, nullptr, 0, decode_level, false, false);
80
0
        }
81
82
      private:
83
        Stream stream;
84
        qpdf_stream_decode_level_e decode_level;
85
    };
86
87
    /// User defined streamfilter factories
88
    std::map<std::string, std::function<std::shared_ptr<QPDFStreamFilter>()>> filter_factories;
89
    bool filter_factories_registered = false;
90
} // namespace
91
92
std::string
93
QPDF_Stream::Members::expand_filter_name(std::string const& name) const
94
0
{
95
    // The PDF specification provides these filter abbreviations for use in inline images, but
96
    // according to table H.1 in the pre-ISO versions of the PDF specification, Adobe Reader also
97
    // accepts them for stream filters.
98
0
    if (name == "/AHx") {
99
0
        return "/ASCIIHexDecode";
100
0
    }
101
0
    if (name == "/A85") {
102
0
        return "/ASCII85Decode";
103
0
    }
104
0
    if (name == "/LZW") {
105
0
        return "/LZWDecode";
106
0
    }
107
0
    if (name == "/Fl") {
108
0
        return "/FlateDecode";
109
0
    }
110
0
    if (name == "/RL") {
111
0
        return "/RunLengthDecode";
112
0
    }
113
0
    if (name == "/CCF") {
114
0
        return "/CCITTFaxDecode";
115
0
    }
116
0
    if (name == "/DCT") {
117
0
        return "/DCTDecode";
118
0
    }
119
0
    return name;
120
0
};
121
122
std::function<std::shared_ptr<QPDFStreamFilter>()>
123
QPDF_Stream::Members::filter_factory(std::string const& name) const
124
17.5k
{
125
17.5k
    if (filter_factories_registered) [[unlikely]] {
126
        // We need to check user provided filters first as we allow users to replace qpdf provided
127
        // default filters. This will have a performance impact if the facility to register stream
128
        // filters is actually used. We can optimize this away if necessary.
129
0
        auto ff = filter_factories.find(expand_filter_name(name));
130
0
        if (ff != filter_factories.end()) {
131
0
            return ff->second;
132
0
        }
133
0
    }
134
17.5k
    if (name == "/FlateDecode") {
135
5.53k
        return SF_FlateLzwDecode::flate_factory;
136
5.53k
    }
137
11.9k
    if (name == "/Crypt") {
138
155
        return []() { return std::make_shared<SF_Crypt>(); };
139
155
    }
140
11.8k
    if (name == "/LZWDecode") {
141
1.62k
        return SF_FlateLzwDecode::lzw_factory;
142
1.62k
    }
143
10.1k
    if (name == "/RunLengthDecode") {
144
273
        return SF_RunLengthDecode::factory;
145
273
    }
146
9.92k
    if (name == "/DCTDecode") {
147
7
        return SF_DCTDecode::factory;
148
7
    }
149
9.91k
    if (name == "/ASCII85Decode") {
150
390
        return SF_ASCII85Decode::factory;
151
390
    }
152
9.52k
    if (name == "/ASCIIHexDecode") {
153
52
        return SF_ASCIIHexDecode::factory;
154
52
    }
155
    // The PDF specification provides these filter abbreviations for use in inline images, but
156
    // according to table H.1 in the pre-ISO versions of the PDF specification, Adobe Reader
157
    // also accepts them for stream filters.
158
159
9.47k
    if (name == "/Fl") {
160
1.47k
        return SF_FlateLzwDecode::flate_factory;
161
1.47k
    }
162
8.00k
    if (name == "/AHx") {
163
510
        return SF_ASCIIHexDecode::factory;
164
510
    }
165
7.49k
    if (name == "/A85") {
166
189
        return SF_ASCII85Decode::factory;
167
189
    }
168
7.30k
    if (name == "/LZW") {
169
2.05k
        return SF_FlateLzwDecode::lzw_factory;
170
2.05k
    }
171
5.25k
    if (name == "/RL") {
172
4.90k
        return SF_RunLengthDecode::factory;
173
4.90k
    }
174
342
    if (name == "/DCT") {
175
7
        return SF_DCTDecode::factory;
176
7
    }
177
335
    return nullptr;
178
342
}
179
180
Stream::Stream(
181
    QPDF& qpdf, QPDFObjGen og, QPDFObjectHandle stream_dict, qpdf_offset_t offset, size_t length) :
182
41.7k
    BaseHandle(QPDFObject::create<QPDF_Stream>(&qpdf, og, std::move(stream_dict), length))
183
41.7k
{
184
41.7k
    auto descr = std::make_shared<QPDFObject::Description>(
185
41.7k
        qpdf.getFilename() + ", stream object " + og.unparse(' '));
186
41.7k
    obj->setDescription(&qpdf, descr, offset);
187
41.7k
    setDictDescription();
188
41.7k
}
189
190
void
191
Stream::registerStreamFilter(
192
    std::string const& filter_name, std::function<std::shared_ptr<QPDFStreamFilter>()> factory)
193
0
{
194
0
    filter_factories[filter_name] = factory;
195
0
    filter_factories_registered = true;
196
0
}
197
198
JSON
199
Stream::getStreamJSON(
200
    int json_version,
201
    qpdf_json_stream_data_e json_data,
202
    qpdf_stream_decode_level_e decode_level,
203
    Pipeline* p,
204
    std::string const& data_filename)
205
0
{
206
0
    Pl_Buffer pb{"streamjson"};
207
0
    JSON::Writer jw{&pb, 0};
208
0
    decode_level =
209
0
        writeStreamJSON(json_version, jw, json_data, decode_level, p, data_filename, true);
210
0
    pb.finish();
211
0
    auto result = JSON::parse(pb.getString());
212
0
    if (json_data == qpdf_sj_inline) {
213
0
        result.addDictionaryMember("data", JSON::makeBlob(StreamBlobProvider(*this, decode_level)));
214
0
    }
215
0
    return result;
216
0
}
217
218
qpdf_stream_decode_level_e
219
Stream::writeStreamJSON(
220
    int json_version,
221
    JSON::Writer& jw,
222
    qpdf_json_stream_data_e json_data,
223
    qpdf_stream_decode_level_e decode_level,
224
    Pipeline* p,
225
    std::string const& data_filename,
226
    bool no_data_key)
227
0
{
228
0
    auto s = stream();
229
0
    switch (json_data) {
230
0
    case qpdf_sj_none:
231
0
    case qpdf_sj_inline:
232
0
        if (p != nullptr) {
233
0
            throw std::logic_error(
234
0
                "QPDF_Stream::writeStreamJSON: pipeline should only be supplied "
235
0
                "when json_data is file");
236
0
        }
237
0
        break;
238
0
    case qpdf_sj_file:
239
0
        if (p == nullptr) {
240
0
            throw std::logic_error(
241
0
                "QPDF_Stream::writeStreamJSON: pipeline must be supplied when json_data is file");
242
0
        }
243
0
        if (data_filename.empty()) {
244
0
            throw std::logic_error(
245
0
                "QPDF_Stream::writeStreamJSON: data_filename must be supplied "
246
0
                "when json_data is file");
247
0
        }
248
0
        break;
249
0
    }
250
251
0
    jw.writeStart('{');
252
253
0
    if (json_data == qpdf_sj_none) {
254
0
        jw.writeNext();
255
0
        jw << R"("dict": )";
256
0
        s->stream_dict.writeJSON(json_version, jw);
257
0
        jw.writeEnd('}');
258
0
        return decode_level;
259
0
    }
260
261
0
    Pl_Discard discard;
262
0
    Pl_Buffer buf_pl{"stream data"};
263
0
    Pipeline* data_pipeline = &buf_pl;
264
0
    if (no_data_key && json_data == qpdf_sj_inline) {
265
0
        data_pipeline = &discard;
266
0
    }
267
    // pipeStreamData produced valid data.
268
0
    bool buf_pl_ready = false;
269
0
    bool filtered = false;
270
0
    bool filter = (decode_level != qpdf_dl_none);
271
0
    for (int attempt = 1; attempt <= 2; ++attempt) {
272
0
        bool succeeded =
273
0
            pipeStreamData(data_pipeline, &filtered, 0, decode_level, false, (attempt == 1));
274
0
        if (!succeeded || (filter && !filtered)) {
275
            // Try again
276
0
            filter = false;
277
0
            decode_level = qpdf_dl_none;
278
0
            buf_pl.getString(); // reset buf_pl
279
0
        } else {
280
0
            buf_pl_ready = true;
281
0
            break;
282
0
        }
283
0
    }
284
0
    if (!buf_pl_ready) {
285
0
        throw std::logic_error("QPDF_Stream: failed to get stream data");
286
0
    }
287
    // We can use unsafeShallowCopy because we are only touching top-level keys.
288
0
    auto dict = s->stream_dict.unsafeShallowCopy();
289
0
    dict.removeKey("/Length");
290
0
    if (filter && filtered) {
291
0
        dict.removeKey("/Filter");
292
0
        dict.removeKey("/DecodeParms");
293
0
    }
294
0
    if (json_data == qpdf_sj_file) {
295
0
        jw.writeNext() << R"("datafile": ")" << JSON::Writer::encode_string(data_filename) << "\"";
296
0
        p->writeString(buf_pl.getString());
297
0
    } else if (json_data == qpdf_sj_inline) {
298
0
        if (!no_data_key) {
299
0
            jw.writeNext() << R"("data": ")";
300
0
            jw.writeBase64(buf_pl.getString()) << "\"";
301
0
        }
302
0
    } else {
303
0
        throw std::logic_error("QPDF_Stream::writeStreamJSON : unexpected value of json_data");
304
0
    }
305
306
0
    jw.writeNext() << R"("dict": )";
307
0
    dict.writeJSON(json_version, jw);
308
0
    jw.writeEnd('}');
309
310
0
    return decode_level;
311
0
}
312
313
void
314
qpdf::Stream::setDictDescription()
315
44.8k
{
316
44.8k
    auto s = stream();
317
44.8k
    if (!s->stream_dict.hasObjectDescription()) {
318
10.3k
        s->stream_dict.setObjectDescription(
319
10.3k
            obj->getQPDF(), obj->getDescription() + " -> stream dictionary");
320
10.3k
    }
321
44.8k
}
322
323
std::string
324
Stream::getStreamData(qpdf_stream_decode_level_e decode_level)
325
7.93k
{
326
7.93k
    std::string result;
327
7.93k
    pl::String buf(result);
328
7.93k
    bool filtered;
329
7.93k
    pipeStreamData(&buf, &filtered, 0, decode_level, false, false);
330
7.93k
    if (!filtered) {
331
2.02k
        throw QPDFExc(
332
2.02k
            qpdf_e_unsupported,
333
2.02k
            obj->getQPDF()->getFilename(),
334
2.02k
            "",
335
2.02k
            obj->getParsedOffset(),
336
2.02k
            "getStreamData called on unfilterable stream");
337
2.02k
    }
338
5.91k
    QTC::TC("qpdf", "QPDF_Stream getStreamData");
339
5.91k
    return result;
340
7.93k
}
341
342
std::string
343
Stream::getRawStreamData()
344
0
{
345
0
    std::string result;
346
0
    pl::String buf(result);
347
0
    if (!pipeStreamData(&buf, nullptr, 0, qpdf_dl_none, false, false)) {
348
0
        throw QPDFExc(
349
0
            qpdf_e_unsupported,
350
0
            obj->getQPDF()->getFilename(),
351
0
            "",
352
0
            obj->getParsedOffset(),
353
0
            "error getting raw stream data");
354
0
    }
355
0
    QTC::TC("qpdf", "QPDF_Stream getRawStreamData");
356
0
    return result;
357
0
}
358
359
bool
360
Stream::isRootMetadata() const
361
11.8k
{
362
11.8k
    if (!getDict().isDictionaryOfType("/Metadata", "/XML")) {
363
11.8k
        return false;
364
11.8k
    }
365
4
    auto root_metadata = qpdf()->getRoot().getKey("/Metadata");
366
4
    return root_metadata.isSameObjectAs(obj);
367
11.8k
}
368
369
bool
370
Stream::filterable(
371
    qpdf_stream_decode_level_e decode_level,
372
    std::vector<std::shared_ptr<QPDFStreamFilter>>& filters)
373
19.0k
{
374
19.0k
    auto s = stream();
375
    // Check filters
376
377
19.0k
    auto filter_obj = s->stream_dict.getKey("/Filter");
378
379
19.0k
    if (filter_obj.isNull()) {
380
        // No filters
381
10.2k
        return true;
382
10.2k
    }
383
8.78k
    if (filter_obj.isName()) {
384
        // One filter
385
7.04k
        auto ff = s->filter_factory(filter_obj.getName());
386
7.04k
        if (!ff) {
387
180
            return false;
388
180
        }
389
6.86k
        filters.emplace_back(ff());
390
6.86k
    } else if (auto array = filter_obj.as_array(strict)) {
391
        // Potentially multiple filters
392
10.4k
        for (auto const& item: array) {
393
10.4k
            if (!item.isName()) {
394
22
                warn("stream filter type is not name or array");
395
22
                return false;
396
22
            }
397
10.4k
            auto ff = s->filter_factory(item.getName());
398
10.4k
            if (!ff) {
399
155
                filters.clear();
400
155
                return false;
401
155
            }
402
10.3k
            filters.emplace_back(ff());
403
10.3k
        }
404
1.71k
    } else {
405
18
        warn("stream filter type is not name or array");
406
18
        return false;
407
18
    }
408
409
    // filters now contains a list of filters to be applied in order. See which ones we can support.
410
    // See if we can support any decode parameters that are specified.
411
412
8.40k
    auto decode_obj = s->stream_dict.getKey("/DecodeParms");
413
414
8.40k
    auto can_filter = // linebreak
415
15.2k
        [](auto d_level, auto& filter, auto& d_obj) -> bool {
416
15.2k
        if (!filter.setDecodeParms(d_obj) ||
417
15.2k
            (d_level < qpdf_dl_all && filter.isLossyCompression()) ||
418
15.2k
            (d_level < qpdf_dl_specialized && filter.isSpecializedCompression())) {
419
183
            return false;
420
183
        }
421
15.0k
        return true;
422
15.2k
    };
423
424
8.40k
    auto decode_array = decode_obj.as_array(strict);
425
8.40k
    if (!decode_array || decode_array.size() == 0) {
426
8.36k
        if (decode_array) {
427
3
            decode_obj = QPDFObjectHandle::newNull();
428
3
        }
429
430
15.1k
        for (auto& filter: filters) {
431
15.1k
            if (!can_filter(decode_level, *filter, decode_obj)) {
432
180
                return false;
433
180
            }
434
15.1k
        }
435
8.36k
    } else {
436
        // Ignore /DecodeParms entirely if /Filters is empty.  At least one case of a file whose
437
        // /DecodeParms was [ << >> ] when /Filters was empty has been seen in the wild.
438
43
        if (!filters.empty() && QIntC::to_size(decode_array.size()) != filters.size()) {
439
10
            warn("stream /DecodeParms length is inconsistent with filters");
440
10
            return false;
441
10
        }
442
443
33
        int i = -1;
444
33
        for (auto& filter: filters) {
445
33
            auto d_obj = decode_array.get(++i);
446
33
            if (!can_filter(decode_level, *filter, d_obj)) {
447
3
                return false;
448
3
            }
449
33
        }
450
33
    }
451
452
8.21k
    return true;
453
8.40k
}
454
455
bool
456
Stream::pipeStreamData(
457
    Pipeline* pipeline,
458
    bool* filterp,
459
    int encode_flags,
460
    qpdf_stream_decode_level_e decode_level,
461
    bool suppress_warnings,
462
    bool will_retry)
463
19.0k
{
464
19.0k
    auto s = stream();
465
19.0k
    std::vector<std::shared_ptr<QPDFStreamFilter>> filters;
466
19.0k
    bool ignored;
467
19.0k
    if (!filterp) {
468
0
        filterp = &ignored;
469
0
    }
470
19.0k
    bool& filter = *filterp;
471
472
19.0k
    const bool empty_stream = !s->stream_provider && !s->stream_data && s->length == 0;
473
19.0k
    const bool empty_stream_data = s->stream_data && s->stream_data->getSize() == 0;
474
19.0k
    const bool empty = empty_stream || empty_stream_data;
475
476
19.0k
    if (empty_stream || empty_stream_data) {
477
440
        filter = true;
478
440
    }
479
480
19.0k
    filter = empty || encode_flags || decode_level != qpdf_dl_none;
481
19.0k
    if (filter) {
482
19.0k
        filter = filterable(decode_level, filters);
483
19.0k
    }
484
485
19.0k
    if (!pipeline) {
486
0
        QTC::TC("qpdf", "QPDF_Stream pipeStreamData with null pipeline");
487
        // Return value is whether we can filter in this case.
488
0
        return filter;
489
0
    }
490
491
    // Construct the pipeline in reverse order. Force pipelines we create to be deleted when this
492
    // function finishes. Pipelines created by QPDFStreamFilter objects will be deleted by those
493
    // objects.
494
19.0k
    std::vector<std::unique_ptr<Pipeline>> to_delete;
495
496
19.0k
    ContentNormalizer normalizer;
497
19.0k
    if (filter) {
498
18.4k
        if (encode_flags & qpdf_ef_compress) {
499
0
            auto new_pipeline =
500
0
                std::make_unique<Pl_Flate>("compress stream", pipeline, Pl_Flate::a_deflate);
501
0
            pipeline = new_pipeline.get();
502
0
            to_delete.push_back(std::move(new_pipeline));
503
0
        }
504
505
18.4k
        if (encode_flags & qpdf_ef_normalize) {
506
0
            auto new_pipeline =
507
0
                std::make_unique<Pl_QPDFTokenizer>("normalizer", &normalizer, pipeline);
508
0
            pipeline = new_pipeline.get();
509
0
            to_delete.push_back(std::move(new_pipeline));
510
0
        }
511
512
18.4k
        for (auto iter = s->token_filters.rbegin(); iter != s->token_filters.rend(); ++iter) {
513
0
            auto new_pipeline =
514
0
                std::make_unique<Pl_QPDFTokenizer>("token filter", (*iter).get(), pipeline);
515
0
            pipeline = new_pipeline.get();
516
0
            to_delete.push_back(std::move(new_pipeline));
517
0
        }
518
519
33.4k
        for (auto f_iter = filters.rbegin(); f_iter != filters.rend(); ++f_iter) {
520
15.0k
            if (auto decode_pipeline = (*f_iter)->getDecodePipeline(pipeline)) {
521
14.9k
                pipeline = decode_pipeline;
522
14.9k
            }
523
15.0k
            auto* flate = dynamic_cast<Pl_Flate*>(pipeline);
524
15.0k
            if (flate) {
525
6.86k
                flate->setWarnCallback([this](char const* msg, int code) { warn(msg); });
526
6.86k
            }
527
15.0k
        }
528
18.4k
    }
529
530
19.0k
    if (s->stream_data.get()) {
531
4.39k
        QTC::TC("qpdf", "QPDF_Stream pipe replaced stream data");
532
4.39k
        pipeline->write(s->stream_data->getBuffer(), s->stream_data->getSize());
533
4.39k
        pipeline->finish();
534
14.6k
    } else if (s->stream_provider.get()) {
535
2.77k
        Pl_Count count("stream provider count", pipeline);
536
2.77k
        if (s->stream_provider->supportsRetry()) {
537
0
            if (!s->stream_provider->provideStreamData(
538
0
                    obj->getObjGen(), &count, suppress_warnings, will_retry)) {
539
0
                filter = false;
540
0
                return false;
541
0
            }
542
2.77k
        } else {
543
2.77k
            s->stream_provider->provideStreamData(obj->getObjGen(), &count);
544
2.77k
        }
545
2.77k
        qpdf_offset_t actual_length = count.getCount();
546
2.77k
        if (s->stream_dict.hasKey("/Length")) {
547
0
            auto desired_length = s->stream_dict.getKey("/Length").getIntValue();
548
0
            if (actual_length != desired_length) {
549
0
                QTC::TC("qpdf", "QPDF_Stream provider length mismatch");
550
                // This would be caused by programmer error on the part of a library user, not by
551
                // invalid input data.
552
0
                throw std::runtime_error(
553
0
                    "stream data provider for " + obj->getObjGen().unparse(' ') + " provided " +
554
0
                    std::to_string(actual_length) + " bytes instead of expected " +
555
0
                    std::to_string(desired_length) + " bytes");
556
0
            }
557
2.77k
        } else {
558
2.77k
            QTC::TC("qpdf", "QPDF_Stream provider length not provided");
559
2.77k
            s->stream_dict.replaceKey("/Length", QPDFObjectHandle::newInteger(actual_length));
560
2.77k
        }
561
11.8k
    } else {
562
11.8k
        if (obj->getParsedOffset() == 0) {
563
0
            QTC::TC("qpdf", "QPDF_Stream pipe no stream data");
564
0
            throw std::logic_error("pipeStreamData called for stream with no data");
565
0
        }
566
11.8k
        QTC::TC("qpdf", "QPDF_Stream pipe original stream data");
567
11.8k
        if (!QPDF::Pipe::pipeStreamData(
568
11.8k
                obj->getQPDF(),
569
11.8k
                obj->getObjGen(),
570
11.8k
                obj->getParsedOffset(),
571
11.8k
                s->length,
572
11.8k
                s->stream_dict,
573
11.8k
                isRootMetadata(),
574
11.8k
                pipeline,
575
11.8k
                suppress_warnings,
576
11.8k
                will_retry)) {
577
2.22k
            filter = false;
578
2.22k
            return false;
579
2.22k
        }
580
11.8k
    }
581
582
16.8k
    if (filter && !suppress_warnings && normalizer.anyBadTokens()) {
583
0
        warn("content normalization encountered bad tokens");
584
0
        if (normalizer.lastTokenWasBad()) {
585
0
            QTC::TC("qpdf", "QPDF_Stream bad token at end during normalize");
586
0
            warn(
587
0
                "normalized content ended with a bad token; you may be able to resolve this by "
588
0
                "coalescing content streams in combination with normalizing content. From the "
589
0
                "command line, specify --coalesce-contents");
590
0
        }
591
0
        warn(
592
0
            "Resulting stream data may be corrupted but is may still useful for manual "
593
0
            "inspection. For more information on this warning, search for content normalization "
594
0
            "in the manual.");
595
0
    }
596
597
16.8k
    return true;
598
19.0k
}
599
600
void
601
Stream::replaceStreamData(
602
    std::shared_ptr<Buffer> data,
603
    QPDFObjectHandle const& filter,
604
    QPDFObjectHandle const& decode_parms)
605
7.59k
{
606
7.59k
    auto s = stream();
607
7.59k
    s->stream_data = data;
608
7.59k
    s->stream_provider = nullptr;
609
7.59k
    replaceFilterData(filter, decode_parms, data->getSize());
610
7.59k
}
611
612
void
613
Stream::replaceStreamData(
614
    std::shared_ptr<QPDFObjectHandle::StreamDataProvider> provider,
615
    QPDFObjectHandle const& filter,
616
    QPDFObjectHandle const& decode_parms)
617
2.77k
{
618
2.77k
    auto s = stream();
619
2.77k
    s->stream_provider = provider;
620
2.77k
    s->stream_data = nullptr;
621
2.77k
    replaceFilterData(filter, decode_parms, 0);
622
2.77k
}
623
624
void
625
Stream::replaceFilterData(
626
    QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms, size_t length)
627
10.3k
{
628
10.3k
    auto s = stream();
629
10.3k
    if (filter) {
630
10.3k
        s->stream_dict.replaceKey("/Filter", filter);
631
10.3k
    }
632
10.3k
    if (decode_parms) {
633
10.3k
        s->stream_dict.replaceKey("/DecodeParms", decode_parms);
634
10.3k
    }
635
10.3k
    if (length == 0) {
636
2.77k
        QTC::TC("qpdf", "QPDF_Stream unknown stream length");
637
2.77k
        s->stream_dict.removeKey("/Length");
638
7.59k
    } else {
639
7.59k
        s->stream_dict.replaceKey(
640
7.59k
            "/Length", QPDFObjectHandle::newInteger(QIntC::to_longlong(length)));
641
7.59k
    }
642
10.3k
}
643
644
void
645
Stream::warn(std::string const& message)
646
2.17k
{
647
2.17k
    obj->getQPDF()->warn(qpdf_e_damaged_pdf, "", obj->getParsedOffset(), message);
648
2.17k
}
649
650
QPDFObjectHandle
651
QPDFObjectHandle::getDict() const
652
74.2k
{
653
74.2k
    return as_stream(error).getDict();
654
74.2k
}
655
656
void
657
QPDFObjectHandle::setFilterOnWrite(bool val)
658
0
{
659
0
    as_stream(error).setFilterOnWrite(val);
660
0
}
661
662
bool
663
QPDFObjectHandle::getFilterOnWrite()
664
0
{
665
0
    return as_stream(error).getFilterOnWrite();
666
0
}
667
668
bool
669
QPDFObjectHandle::isDataModified()
670
0
{
671
0
    return as_stream(error).isDataModified();
672
0
}
673
674
void
675
QPDFObjectHandle::replaceDict(QPDFObjectHandle const& new_dict)
676
3.10k
{
677
3.10k
    as_stream(error).replaceDict(new_dict);
678
3.10k
}
679
680
bool
681
QPDFObjectHandle::isRootMetadata() const
682
0
{
683
0
    return as_stream(error).isRootMetadata();
684
0
}
685
686
std::shared_ptr<Buffer>
687
QPDFObjectHandle::getStreamData(qpdf_stream_decode_level_e level)
688
6.27k
{
689
6.27k
    return std::make_shared<Buffer>(as_stream(error).getStreamData(level));
690
6.27k
}
691
692
std::shared_ptr<Buffer>
693
QPDFObjectHandle::getRawStreamData()
694
0
{
695
0
    return std::make_shared<Buffer>(as_stream(error).getRawStreamData());
696
0
}
697
698
bool
699
QPDFObjectHandle::pipeStreamData(
700
    Pipeline* p,
701
    bool* filtering_attempted,
702
    int encode_flags,
703
    qpdf_stream_decode_level_e decode_level,
704
    bool suppress_warnings,
705
    bool will_retry)
706
0
{
707
0
    return as_stream(error).pipeStreamData(
708
0
        p, filtering_attempted, encode_flags, decode_level, suppress_warnings, will_retry);
709
0
}
710
711
bool
712
QPDFObjectHandle::pipeStreamData(
713
    Pipeline* p,
714
    int encode_flags,
715
    qpdf_stream_decode_level_e decode_level,
716
    bool suppress_warnings,
717
    bool will_retry)
718
11.1k
{
719
11.1k
    bool filtering_attempted;
720
11.1k
    as_stream(error).pipeStreamData(
721
11.1k
        p, &filtering_attempted, encode_flags, decode_level, suppress_warnings, will_retry);
722
11.1k
    return filtering_attempted;
723
11.1k
}
724
725
bool
726
QPDFObjectHandle::pipeStreamData(Pipeline* p, bool filter, bool normalize, bool compress)
727
0
{
728
0
    int encode_flags = 0;
729
0
    qpdf_stream_decode_level_e decode_level = qpdf_dl_none;
730
0
    if (filter) {
731
0
        decode_level = qpdf_dl_generalized;
732
0
        if (normalize) {
733
0
            encode_flags |= qpdf_ef_normalize;
734
0
        }
735
0
        if (compress) {
736
0
            encode_flags |= qpdf_ef_compress;
737
0
        }
738
0
    }
739
0
    return pipeStreamData(p, encode_flags, decode_level, false);
740
0
}
741
742
void
743
QPDFObjectHandle::replaceStreamData(
744
    std::shared_ptr<Buffer> data,
745
    QPDFObjectHandle const& filter,
746
    QPDFObjectHandle const& decode_parms)
747
0
{
748
0
    as_stream(error).replaceStreamData(data, filter, decode_parms);
749
0
}
750
751
void
752
QPDFObjectHandle::replaceStreamData(
753
    std::string const& data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms)
754
7.59k
{
755
7.59k
    auto b = std::make_shared<Buffer>(data.length());
756
7.59k
    unsigned char* bp = b->getBuffer();
757
7.59k
    if (bp) {
758
7.59k
        memcpy(bp, data.c_str(), data.length());
759
7.59k
    }
760
7.59k
    as_stream(error).replaceStreamData(b, filter, decode_parms);
761
7.59k
}
762
763
void
764
QPDFObjectHandle::replaceStreamData(
765
    std::shared_ptr<StreamDataProvider> provider,
766
    QPDFObjectHandle const& filter,
767
    QPDFObjectHandle const& decode_parms)
768
2.77k
{
769
2.77k
    as_stream(error).replaceStreamData(provider, filter, decode_parms);
770
2.77k
}
771
772
namespace
773
{
774
    class FunctionProvider: public QPDFObjectHandle::StreamDataProvider
775
    {
776
      public:
777
        FunctionProvider(std::function<void(Pipeline*)> provider) :
778
0
            StreamDataProvider(false),
779
0
            p1(provider),
780
0
            p2(nullptr)
781
0
        {
782
0
        }
783
        FunctionProvider(std::function<bool(Pipeline*, bool, bool)> provider) :
784
0
            StreamDataProvider(true),
785
0
            p1(nullptr),
786
0
            p2(provider)
787
0
        {
788
0
        }
789
790
        void
791
        provideStreamData(QPDFObjGen const&, Pipeline* pipeline) override
792
0
        {
793
0
            p1(pipeline);
794
0
        }
795
796
        bool
797
        provideStreamData(
798
            QPDFObjGen const&, Pipeline* pipeline, bool suppress_warnings, bool will_retry) override
799
0
        {
800
0
            return p2(pipeline, suppress_warnings, will_retry);
801
0
        }
802
803
      private:
804
        std::function<void(Pipeline*)> p1;
805
        std::function<bool(Pipeline*, bool, bool)> p2;
806
    };
807
} // namespace
808
809
void
810
QPDFObjectHandle::replaceStreamData(
811
    std::function<void(Pipeline*)> provider,
812
    QPDFObjectHandle const& filter,
813
    QPDFObjectHandle const& decode_parms)
814
0
{
815
0
    auto sdp = std::shared_ptr<StreamDataProvider>(new FunctionProvider(provider));
816
0
    as_stream(error).replaceStreamData(sdp, filter, decode_parms);
817
0
}
818
819
void
820
QPDFObjectHandle::replaceStreamData(
821
    std::function<bool(Pipeline*, bool, bool)> provider,
822
    QPDFObjectHandle const& filter,
823
    QPDFObjectHandle const& decode_parms)
824
0
{
825
0
    auto sdp = std::shared_ptr<StreamDataProvider>(new FunctionProvider(provider));
826
0
    as_stream(error).replaceStreamData(sdp, filter, decode_parms);
827
0
}
828
829
JSON
830
QPDFObjectHandle::getStreamJSON(
831
    int json_version,
832
    qpdf_json_stream_data_e json_data,
833
    qpdf_stream_decode_level_e decode_level,
834
    Pipeline* p,
835
    std::string const& data_filename)
836
0
{
837
0
    return as_stream(error).getStreamJSON(json_version, json_data, decode_level, p, data_filename);
838
0
}