Commit 2a4495dd8a8bf9f578fc43626a9b039e510aad17

Authored by m-holger
Committed by GitHub
2 parents e9359c72 5c1a5d43

Merge pull request #1457 from m-holger/stream

Refactor Stream::filterable and Stream::pipeStreamData
libqpdf/ContentNormalizer.cc
... ... @@ -71,15 +71,3 @@ ContentNormalizer::handleToken(QPDFTokenizer::Token const& token)
71 71 write("\n");
72 72 }
73 73 }
74   -
75   -bool
76   -ContentNormalizer::anyBadTokens() const
77   -{
78   - return this->any_bad_tokens;
79   -}
80   -
81   -bool
82   -ContentNormalizer::lastTokenWasBad() const
83   -{
84   - return this->last_token_was_bad;
85   -}
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -27,33 +27,37 @@ using namespace qpdf;
27 27  
28 28 namespace
29 29 {
30   - class SF_Crypt: public QPDFStreamFilter
  30 + class SF_Crypt final: public QPDFStreamFilter
31 31 {
32 32 public:
33 33 SF_Crypt() = default;
34   - ~SF_Crypt() override = default;
  34 + ~SF_Crypt() final = default;
35 35  
36 36 bool
37   - setDecodeParms(QPDFObjectHandle decode_parms) override
  37 + setDecodeParms(QPDFObjectHandle decode_parms) final
38 38 {
39   - if (decode_parms.isNull()) {
40   - return true;
41   - }
42   - bool filterable = true;
43   - for (auto const& key: decode_parms.getKeys()) {
44   - if (((key == "/Type") || (key == "/Name")) &&
45   - ((!decode_parms.hasKey("/Type")) ||
46   - decode_parms.isDictionaryOfType("/CryptFilterDecodeParms"))) {
47   - // we handle this in decryptStream
48   - } else {
49   - filterable = false;
  39 + // we only validate here - processing happens in decryptStream
  40 + if (auto dict = decode_parms.as_dictionary(optional)) {
  41 + for (auto const& [key, value]: dict) {
  42 + if (key == "/Type" &&
  43 + (value.null() ||
  44 + (value.isName() && value.getName() == "/CryptFilterDecodeParms"))) {
  45 + continue;
  46 + }
  47 + if (key == "/Name") {
  48 + continue;
  49 + }
  50 + if (!value.null()) {
  51 + return false;
  52 + }
50 53 }
  54 + return true;
51 55 }
52   - return filterable;
  56 + return false;
53 57 }
54 58  
55 59 Pipeline*
56   - getDecodePipeline(Pipeline*) override
  60 + getDecodePipeline(Pipeline*) final
57 61 {
58 62 // Not used -- handled by pipeStreamData
59 63 return nullptr;
... ... @@ -78,31 +82,67 @@ namespace
78 82 Stream stream;
79 83 qpdf_stream_decode_level_e decode_level;
80 84 };
  85 +
  86 + /// User defined streamfilter factories
  87 + std::map<std::string, std::function<std::shared_ptr<QPDFStreamFilter>()>> filter_factories;
81 88 } // namespace
82 89  
83   -std::map<std::string, std::string> Stream::filter_abbreviations = {
  90 +std::function<std::shared_ptr<QPDFStreamFilter>()>
  91 +QPDF_Stream::Members::filter_factory(std::string const& name) const
  92 +{
  93 + if (name == "/FlateDecode") {
  94 + return SF_FlateLzwDecode::flate_factory;
  95 + }
  96 + if (name == "/Crypt") {
  97 + return []() { return std::make_shared<SF_Crypt>(); };
  98 + }
  99 + if (name == "/LZWDecode") {
  100 + return SF_FlateLzwDecode::lzw_factory;
  101 + }
  102 + if (name == "/RunLengthDecode") {
  103 + return SF_RunLengthDecode::factory;
  104 + }
  105 + if (name == "/DCTDecode") {
  106 + return SF_DCTDecode::factory;
  107 + }
  108 + if (name == "/ASCII85Decode") {
  109 + return SF_ASCII85Decode::factory;
  110 + }
  111 + if (name == "/ASCIIHexDecode") {
  112 + return SF_ASCIIHexDecode::factory;
  113 + }
84 114 // The PDF specification provides these filter abbreviations for use in inline images, but
85 115 // according to table H.1 in the pre-ISO versions of the PDF specification, Adobe Reader also
86 116 // accepts them for stream filters.
87   - {"/AHx", "/ASCIIHexDecode"},
88   - {"/A85", "/ASCII85Decode"},
89   - {"/LZW", "/LZWDecode"},
90   - {"/Fl", "/FlateDecode"},
91   - {"/RL", "/RunLengthDecode"},
92   - {"/CCF", "/CCITTFaxDecode"},
93   - {"/DCT", "/DCTDecode"},
94   -};
95   -
96   -std::map<std::string, std::function<std::shared_ptr<QPDFStreamFilter>()>> Stream::filter_factories =
97   - {
98   - {"/Crypt", []() { return std::make_shared<SF_Crypt>(); }},
99   - {"/FlateDecode", SF_FlateLzwDecode::flate_factory},
100   - {"/LZWDecode", SF_FlateLzwDecode::lzw_factory},
101   - {"/RunLengthDecode", SF_RunLengthDecode::factory},
102   - {"/DCTDecode", SF_DCTDecode::factory},
103   - {"/ASCII85Decode", SF_ASCII85Decode::factory},
104   - {"/ASCIIHexDecode", SF_ASCIIHexDecode::factory},
105   -};
  117 +
  118 + if (name == "/Fl") {
  119 + return SF_FlateLzwDecode::flate_factory;
  120 + }
  121 + if (name == "/AHx") {
  122 + return SF_ASCIIHexDecode::factory;
  123 + }
  124 + if (name == "/A85") {
  125 + return SF_ASCII85Decode::factory;
  126 + }
  127 + if (name == "/LZW") {
  128 + return SF_FlateLzwDecode::lzw_factory;
  129 + }
  130 + if (name == "/RL") {
  131 + return SF_RunLengthDecode::factory;
  132 + }
  133 + if (name == "/DCT") {
  134 + return SF_DCTDecode::factory;
  135 + }
  136 + if (filter_factories.empty()) {
  137 + return nullptr;
  138 + }
  139 + auto ff =
  140 + name == "/CCF" ? filter_factories.find("/CCITTFaxDecode") : filter_factories.find(name);
  141 + if (ff == filter_factories.end()) {
  142 + return nullptr;
  143 + }
  144 + return ff->second;
  145 +}
106 146  
107 147 Stream::Stream(
108 148 QPDF& qpdf, QPDFObjGen og, QPDFObjectHandle stream_dict, qpdf_offset_t offset, size_t length) :
... ... @@ -292,112 +332,88 @@ Stream::isRootMetadata() const
292 332  
293 333 bool
294 334 Stream::filterable(
295   - std::vector<std::shared_ptr<QPDFStreamFilter>>& filters,
296   - bool& specialized_compression,
297   - bool& lossy_compression)
  335 + qpdf_stream_decode_level_e decode_level,
  336 + std::vector<std::shared_ptr<QPDFStreamFilter>>& filters)
298 337 {
299 338 auto s = stream();
300 339 // Check filters
301 340  
302   - QPDFObjectHandle filter_obj = s->stream_dict.getKey("/Filter");
303   - bool filters_okay = true;
304   -
305   - std::vector<std::string> filter_names;
  341 + auto filter_obj = s->stream_dict.getKey("/Filter");
306 342  
307 343 if (filter_obj.isNull()) {
308 344 // No filters
309   - } else if (filter_obj.isName()) {
  345 + return true;
  346 + }
  347 + if (filter_obj.isName()) {
310 348 // One filter
311   - filter_names.push_back(filter_obj.getName());
312   - } else if (filter_obj.isArray()) {
  349 + auto ff = s->filter_factory(filter_obj.getName());
  350 + if (!ff) {
  351 + return false;
  352 + }
  353 + filters.emplace_back(ff());
  354 + } else if (auto array = filter_obj.as_array(strict)) {
313 355 // Potentially multiple filters
314   - int n = filter_obj.getArrayNItems();
315   - for (int i = 0; i < n; ++i) {
316   - QPDFObjectHandle item = filter_obj.getArrayItem(i);
317   - if (item.isName()) {
318   - filter_names.push_back(item.getName());
319   - } else {
320   - filters_okay = false;
  356 + for (auto const& item: array) {
  357 + if (!item.isName()) {
  358 + warn("stream filter type is not name or array");
  359 + return false;
321 360 }
  361 + auto ff = s->filter_factory(item.getName());
  362 + if (!ff) {
  363 + filters.clear();
  364 + return false;
  365 + }
  366 + filters.emplace_back(ff());
322 367 }
323 368 } else {
324   - filters_okay = false;
325   - }
326   -
327   - if (!filters_okay) {
328   - QTC::TC("qpdf", "QPDF_Stream invalid filter");
329 369 warn("stream filter type is not name or array");
330 370 return false;
331 371 }
332 372  
333   - bool filterable = true;
  373 + // filters now contains a list of filters to be applied in order. See which ones we can support.
  374 + // See if we can support any decode parameters that are specified.
334 375  
335   - for (auto& filter_name: filter_names) {
336   - if (filter_abbreviations.count(filter_name)) {
337   - QTC::TC("qpdf", "QPDF_Stream expand filter abbreviation");
338   - filter_name = filter_abbreviations[filter_name];
339   - }
  376 + auto decode_obj = s->stream_dict.getKey("/DecodeParms");
340 377  
341   - auto ff = filter_factories.find(filter_name);
342   - if (ff == filter_factories.end()) {
343   - filterable = false;
344   - } else {
345   - filters.push_back((ff->second)());
  378 + auto can_filter = // linebreak
  379 + [](auto d_level, auto& filter, auto& d_obj) -> bool {
  380 + if (!filter.setDecodeParms(d_obj) ||
  381 + (d_level < qpdf_dl_all && filter.isLossyCompression()) ||
  382 + (d_level < qpdf_dl_specialized && filter.isSpecializedCompression())) {
  383 + return false;
346 384 }
347   - }
348   -
349   - if (!filterable) {
350   - return false;
351   - }
352   -
353   - // filters now contains a list of filters to be applied in order. See which ones we can support.
  385 + return true;
  386 + };
354 387  
355   - // See if we can support any decode parameters that are specified.
  388 + auto decode_array = decode_obj.as_array(strict);
  389 + if (!decode_array || decode_array.size() == 0) {
  390 + if (decode_array) {
  391 + decode_obj = QPDFObjectHandle::newNull();
  392 + }
356 393  
357   - QPDFObjectHandle decode_obj = s->stream_dict.getKey("/DecodeParms");
358   - std::vector<QPDFObjectHandle> decode_parms;
359   - if (decode_obj.isArray() && (decode_obj.getArrayNItems() == 0)) {
360   - decode_obj = QPDFObjectHandle::newNull();
361   - }
362   - if (decode_obj.isArray()) {
363   - for (int i = 0; i < decode_obj.getArrayNItems(); ++i) {
364   - decode_parms.push_back(decode_obj.getArrayItem(i));
  394 + for (auto& filter: filters) {
  395 + if (!can_filter(decode_level, *filter, decode_obj)) {
  396 + return false;
  397 + }
365 398 }
366 399 } else {
367   - for (unsigned int i = 0; i < filter_names.size(); ++i) {
368   - decode_parms.push_back(decode_obj);
  400 + // Ignore /DecodeParms entirely if /Filters is empty. At least one case of a file whose
  401 + // /DecodeParms was [ << >> ] when /Filters was empty has been seen in the wild.
  402 + if (!filters.empty() && QIntC::to_size(decode_array.size()) != filters.size()) {
  403 + warn("stream /DecodeParms length is inconsistent with filters");
  404 + return false;
369 405 }
370   - }
371   -
372   - // Ignore /DecodeParms entirely if /Filters is empty. At least one case of a file whose
373   - // /DecodeParms was [ << >> ] when /Filters was empty has been seen in the wild.
374   - if ((filters.size() != 0) && (decode_parms.size() != filters.size())) {
375   - warn("stream /DecodeParms length is inconsistent with filters");
376   - filterable = false;
377   - }
378   -
379   - if (!filterable) {
380   - return false;
381   - }
382 406  
383   - for (size_t i = 0; i < filters.size(); ++i) {
384   - auto filter = filters.at(i);
385   - auto decode_item = decode_parms.at(i);
386   -
387   - if (filter->setDecodeParms(decode_item)) {
388   - if (filter->isSpecializedCompression()) {
389   - specialized_compression = true;
390   - }
391   - if (filter->isLossyCompression()) {
392   - specialized_compression = true;
393   - lossy_compression = true;
  407 + int i = -1;
  408 + for (auto& filter: filters) {
  409 + auto d_obj = decode_array.at(++i).second;
  410 + if (!can_filter(decode_level, *filter, d_obj)) {
  411 + return false;
394 412 }
395   - } else {
396   - filterable = false;
397 413 }
398 414 }
399 415  
400   - return filterable;
  416 + return true;
401 417 }
402 418  
403 419 bool
... ... @@ -411,33 +427,17 @@ Stream::pipeStreamData(
411 427 {
412 428 auto s = stream();
413 429 std::vector<std::shared_ptr<QPDFStreamFilter>> filters;
414   - bool specialized_compression = false;
415   - bool lossy_compression = false;
416 430 bool ignored;
417   - if (filterp == nullptr) {
  431 + if (!filterp) {
418 432 filterp = &ignored;
419 433 }
420 434 bool& filter = *filterp;
421   - filter = (!((encode_flags == 0) && (decode_level == qpdf_dl_none)));
422   - bool success = true;
  435 + filter = encode_flags || decode_level != qpdf_dl_none;
423 436 if (filter) {
424   - filter = filterable(filters, specialized_compression, lossy_compression);
425   - if ((decode_level < qpdf_dl_all) && lossy_compression) {
426   - filter = false;
427   - }
428   - if ((decode_level < qpdf_dl_specialized) && specialized_compression) {
429   - filter = false;
430   - }
431   - QTC::TC(
432   - "qpdf",
433   - "QPDF_Stream special filters",
434   - (!filter) ? 0
435   - : lossy_compression ? 1
436   - : specialized_compression ? 2
437   - : 3);
  437 + filter = filterable(decode_level, filters);
438 438 }
439 439  
440   - if (pipeline == nullptr) {
  440 + if (!pipeline) {
441 441 QTC::TC("qpdf", "QPDF_Stream pipeStreamData with null pipeline");
442 442 // Return value is whether we can filter in this case.
443 443 return filter;
... ... @@ -446,40 +446,37 @@ Stream::pipeStreamData(
446 446 // Construct the pipeline in reverse order. Force pipelines we create to be deleted when this
447 447 // function finishes. Pipelines created by QPDFStreamFilter objects will be deleted by those
448 448 // objects.
449   - std::vector<std::shared_ptr<Pipeline>> to_delete;
  449 + std::vector<std::unique_ptr<Pipeline>> to_delete;
450 450  
451   - std::shared_ptr<ContentNormalizer> normalizer;
452   - std::shared_ptr<Pipeline> new_pipeline;
  451 + ContentNormalizer normalizer;
453 452 if (filter) {
454 453 if (encode_flags & qpdf_ef_compress) {
455   - new_pipeline =
456   - std::make_shared<Pl_Flate>("compress stream", pipeline, Pl_Flate::a_deflate);
457   - to_delete.push_back(new_pipeline);
  454 + auto new_pipeline =
  455 + std::make_unique<Pl_Flate>("compress stream", pipeline, Pl_Flate::a_deflate);
458 456 pipeline = new_pipeline.get();
  457 + to_delete.push_back(std::move(new_pipeline));
459 458 }
460 459  
461 460 if (encode_flags & qpdf_ef_normalize) {
462   - normalizer = std::make_shared<ContentNormalizer>();
463   - new_pipeline =
464   - std::make_shared<Pl_QPDFTokenizer>("normalizer", normalizer.get(), pipeline);
465   - to_delete.push_back(new_pipeline);
  461 + auto new_pipeline =
  462 + std::make_unique<Pl_QPDFTokenizer>("normalizer", &normalizer, pipeline);
466 463 pipeline = new_pipeline.get();
  464 + to_delete.push_back(std::move(new_pipeline));
467 465 }
468 466  
469 467 for (auto iter = s->token_filters.rbegin(); iter != s->token_filters.rend(); ++iter) {
470   - new_pipeline =
471   - std::make_shared<Pl_QPDFTokenizer>("token filter", (*iter).get(), pipeline);
472   - to_delete.push_back(new_pipeline);
  468 + auto new_pipeline =
  469 + std::make_unique<Pl_QPDFTokenizer>("token filter", (*iter).get(), pipeline);
473 470 pipeline = new_pipeline.get();
  471 + to_delete.push_back(std::move(new_pipeline));
474 472 }
475 473  
476 474 for (auto f_iter = filters.rbegin(); f_iter != filters.rend(); ++f_iter) {
477   - auto decode_pipeline = (*f_iter)->getDecodePipeline(pipeline);
478   - if (decode_pipeline) {
  475 + if (auto decode_pipeline = (*f_iter)->getDecodePipeline(pipeline)) {
479 476 pipeline = decode_pipeline;
480 477 }
481 478 auto* flate = dynamic_cast<Pl_Flate*>(pipeline);
482   - if (flate != nullptr) {
  479 + if (flate) {
483 480 flate->setWarnCallback([this](char const* msg, int code) { warn(msg); });
484 481 }
485 482 }
... ... @@ -495,18 +492,15 @@ Stream::pipeStreamData(
495 492 if (!s->stream_provider->provideStreamData(
496 493 obj->getObjGen(), &count, suppress_warnings, will_retry)) {
497 494 filter = false;
498   - success = false;
  495 + return false;
499 496 }
500 497 } else {
501 498 s->stream_provider->provideStreamData(obj->getObjGen(), &count);
502 499 }
503 500 qpdf_offset_t actual_length = count.getCount();
504   - qpdf_offset_t desired_length = 0;
505   - if (success && s->stream_dict.hasKey("/Length")) {
506   - desired_length = s->stream_dict.getKey("/Length").getIntValue();
507   - if (actual_length == desired_length) {
508   - QTC::TC("qpdf", "QPDF_Stream pipe use stream provider");
509   - } else {
  501 + if (s->stream_dict.hasKey("/Length")) {
  502 + auto desired_length = s->stream_dict.getKey("/Length").getIntValue();
  503 + if (actual_length != desired_length) {
510 504 QTC::TC("qpdf", "QPDF_Stream provider length mismatch");
511 505 // This would be caused by programmer error on the part of a library user, not by
512 506 // invalid input data.
... ... @@ -515,14 +509,15 @@ Stream::pipeStreamData(
515 509 std::to_string(actual_length) + " bytes instead of expected " +
516 510 std::to_string(desired_length) + " bytes");
517 511 }
518   - } else if (success) {
  512 + } else {
519 513 QTC::TC("qpdf", "QPDF_Stream provider length not provided");
520 514 s->stream_dict.replaceKey("/Length", QPDFObjectHandle::newInteger(actual_length));
521 515 }
522   - } else if (obj->getParsedOffset() == 0) {
523   - QTC::TC("qpdf", "QPDF_Stream pipe no stream data");
524   - throw std::logic_error("pipeStreamData called for stream with no data");
525 516 } else {
  517 + if (obj->getParsedOffset() == 0) {
  518 + QTC::TC("qpdf", "QPDF_Stream pipe no stream data");
  519 + throw std::logic_error("pipeStreamData called for stream with no data");
  520 + }
526 521 QTC::TC("qpdf", "QPDF_Stream pipe original stream data");
527 522 if (!QPDF::Pipe::pipeStreamData(
528 523 obj->getQPDF(),
... ... @@ -535,13 +530,13 @@ Stream::pipeStreamData(
535 530 suppress_warnings,
536 531 will_retry)) {
537 532 filter = false;
538   - success = false;
  533 + return false;
539 534 }
540 535 }
541 536  
542   - if (filter && (!suppress_warnings) && normalizer.get() && normalizer->anyBadTokens()) {
  537 + if (filter && !suppress_warnings && normalizer.anyBadTokens()) {
543 538 warn("content normalization encountered bad tokens");
544   - if (normalizer->lastTokenWasBad()) {
  539 + if (normalizer.lastTokenWasBad()) {
545 540 QTC::TC("qpdf", "QPDF_Stream bad token at end during normalize");
546 541 warn(
547 542 "normalized content ended with a bad token; you may be able to resolve this by "
... ... @@ -554,7 +549,7 @@ Stream::pipeStreamData(
554 549 "in the manual.");
555 550 }
556 551  
557   - return success;
  552 + return true;
558 553 }
559 554  
560 555 void
... ...
libqpdf/SF_FlateLzwDecode.cc
... ... @@ -69,48 +69,37 @@ SF_FlateLzwDecode::setDecodeParms(QPDFObjectHandle decode_parms)
69 69 Pipeline*
70 70 SF_FlateLzwDecode::getDecodePipeline(Pipeline* next)
71 71 {
72   - std::shared_ptr<Pipeline> pipeline;
  72 + std::unique_ptr<Pipeline> pipeline;
73 73 if (predictor >= 10 && predictor <= 15) {
74 74 QTC::TC("qpdf", "SF_FlateLzwDecode PNG filter");
75   - pipeline = std::make_shared<Pl_PNGFilter>(
  75 + pipeline = std::make_unique<Pl_PNGFilter>(
76 76 "png decode",
77 77 next,
78 78 Pl_PNGFilter::a_decode,
79 79 QIntC::to_uint(columns),
80 80 QIntC::to_uint(colors),
81 81 QIntC::to_uint(bits_per_component));
82   - pipelines.push_back(pipeline);
83 82 next = pipeline.get();
  83 + pipelines.push_back(std::move(pipeline));
84 84 } else if (predictor == 2) {
85 85 QTC::TC("qpdf", "SF_FlateLzwDecode TIFF predictor");
86   - pipeline = std::make_shared<Pl_TIFFPredictor>(
  86 + pipeline = std::make_unique<Pl_TIFFPredictor>(
87 87 "tiff decode",
88 88 next,
89 89 Pl_TIFFPredictor::a_decode,
90 90 QIntC::to_uint(columns),
91 91 QIntC::to_uint(colors),
92 92 QIntC::to_uint(bits_per_component));
93   - pipelines.push_back(pipeline);
94 93 next = pipeline.get();
  94 + pipelines.push_back(std::move(pipeline));
95 95 }
96 96  
97 97 if (lzw) {
98   - pipeline = std::make_shared<Pl_LZWDecoder>("lzw decode", next, early_code_change);
  98 + pipeline = std::make_unique<Pl_LZWDecoder>("lzw decode", next, early_code_change);
99 99 } else {
100   - pipeline = std::make_shared<Pl_Flate>("stream inflate", next, Pl_Flate::a_inflate);
  100 + pipeline = std::make_unique<Pl_Flate>("stream inflate", next, Pl_Flate::a_inflate);
101 101 }
102   - pipelines.push_back(pipeline);
103   - return pipeline.get();
104   -}
105   -
106   -std::shared_ptr<QPDFStreamFilter>
107   -SF_FlateLzwDecode::flate_factory()
108   -{
109   - return std::make_shared<SF_FlateLzwDecode>(false);
110   -}
111   -
112   -std::shared_ptr<QPDFStreamFilter>
113   -SF_FlateLzwDecode::lzw_factory()
114   -{
115   - return std::make_shared<SF_FlateLzwDecode>(true);
  102 + next = pipeline.get();
  103 + pipelines.push_back(std::move(pipeline));
  104 + return next;
116 105 }
... ...
libqpdf/qpdf/ContentNormalizer.hh
... ... @@ -3,15 +3,23 @@
3 3  
4 4 #include <qpdf/QPDFObjectHandle.hh>
5 5  
6   -class ContentNormalizer: public QPDFObjectHandle::TokenFilter
  6 +class ContentNormalizer final: public QPDFObjectHandle::TokenFilter
7 7 {
8 8 public:
9 9 ContentNormalizer();
10   - ~ContentNormalizer() override = default;
11   - void handleToken(QPDFTokenizer::Token const&) override;
  10 + ~ContentNormalizer() final = default;
  11 + void handleToken(QPDFTokenizer::Token const&) final;
12 12  
13   - bool anyBadTokens() const;
14   - bool lastTokenWasBad() const;
  13 + bool
  14 + anyBadTokens() const
  15 + {
  16 + return any_bad_tokens;
  17 + }
  18 + bool
  19 + lastTokenWasBad() const
  20 + {
  21 + return last_token_was_bad;
  22 + }
15 23  
16 24 private:
17 25 bool any_bad_tokens;
... ...
libqpdf/qpdf/QPDFObjectHandle_private.hh
... ... @@ -312,17 +312,14 @@ namespace qpdf
312 312 return nullptr; // unreachable
313 313 }
314 314 bool filterable(
315   - std::vector<std::shared_ptr<QPDFStreamFilter>>& filters,
316   - bool& specialized_compression,
317   - bool& lossy_compression);
  315 + qpdf_stream_decode_level_e decode_level,
  316 + std::vector<std::shared_ptr<QPDFStreamFilter>>& filters);
318 317 void replaceFilterData(
319 318 QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms, size_t length);
320 319  
321 320 void warn(std::string const& message);
322 321  
323 322 static std::map<std::string, std::string> filter_abbreviations;
324   - static std::map<std::string, std::function<std::shared_ptr<QPDFStreamFilter>()>>
325   - filter_factories;
326 323 };
327 324  
328 325 template <typename T>
... ...
libqpdf/qpdf/QPDFObject_private.hh
... ... @@ -226,6 +226,8 @@ class QPDF_Stream final
226 226 std::shared_ptr<Buffer> stream_data;
227 227 std::shared_ptr<QPDFObjectHandle::StreamDataProvider> stream_provider;
228 228 std::vector<std::shared_ptr<QPDFObjectHandle::TokenFilter>> token_filters;
  229 + std::function<std::shared_ptr<QPDFStreamFilter>()>
  230 + filter_factory(std::string const& name) const;
229 231 };
230 232  
231 233 friend class QPDFObject;
... ...
libqpdf/qpdf/SF_FlateLzwDecode.hh
... ... @@ -17,8 +17,16 @@ class SF_FlateLzwDecode final: public QPDFStreamFilter
17 17 bool setDecodeParms(QPDFObjectHandle decode_parms) final;
18 18 Pipeline* getDecodePipeline(Pipeline* next) final;
19 19  
20   - static std::shared_ptr<QPDFStreamFilter> flate_factory();
21   - static std::shared_ptr<QPDFStreamFilter> lzw_factory();
  20 + static std::shared_ptr<QPDFStreamFilter>
  21 + flate_factory()
  22 + {
  23 + return std::make_shared<SF_FlateLzwDecode>(false);
  24 + }
  25 + static std::shared_ptr<QPDFStreamFilter>
  26 + lzw_factory()
  27 + {
  28 + return std::make_shared<SF_FlateLzwDecode>(true);
  29 + }
22 30  
23 31 private:
24 32 bool lzw{};
... ... @@ -28,7 +36,7 @@ class SF_FlateLzwDecode final: public QPDFStreamFilter
28 36 int colors{1};
29 37 int bits_per_component{8};
30 38 bool early_code_change{true};
31   - std::vector<std::shared_ptr<Pipeline>> pipelines;
  39 + std::vector<std::unique_ptr<Pipeline>> pipelines;
32 40 };
33 41  
34 42 #endif // SF_FLATELZWDECODE_HH
... ...
qpdf/qpdf.testcov
... ... @@ -77,7 +77,6 @@ QPDFTokenizer bad hexstring 2nd character 0
77 77 QPDFTokenizer null in name 0
78 78 QPDFTokenizer bad name 1 0
79 79 QPDFTokenizer bad name 2 0
80   -QPDF_Stream invalid filter 0
81 80 QPDF UseOutlines but no Outlines 0
82 81 QPDFObjectHandle makeDirect loop 0
83 82 QPDFObjectHandle copy stream 1
... ... @@ -168,7 +167,6 @@ qpdf-c called qpdf_has_error 0
168 167 qpdf-c called qpdf_get_qpdf_version 0
169 168 QPDF_Stream pipe original stream data 0
170 169 QPDF_Stream pipe replaced stream data 0
171   -QPDF_Stream pipe use stream provider 0
172 170 QPDF_Stream provider length mismatch 0
173 171 QPDFObjectHandle newStream 0
174 172 QPDFObjectHandle newStream with data 0
... ... @@ -177,7 +175,6 @@ QPDFObjectHandle prepend page contents 0
177 175 QPDFObjectHandle append page contents 0
178 176 QPDF_Stream getRawStreamData 0
179 177 QPDF_Stream getStreamData 0
180   -QPDF_Stream expand filter abbreviation 0
181 178 qpdf-c called qpdf_read_memory 0
182 179 QPDF stream without newline 0
183 180 QPDF stream with CR only 0
... ... @@ -281,7 +278,6 @@ QPDF ignore second extra space in xref entry 0
281 278 QPDF ignore length error xref entry 0
282 279 QPDF_encryption pad short parameter 0
283 280 QPDFObjectHandle found old angle 1
284   -QPDF_Stream special filters 3
285 281 QPDFTokenizer block long token 0
286 282 qpdf-c called qpdf_set_decode_level 0
287 283 qpdf-c called qpdf_set_compress_streams 0
... ...