Commit 2a4495dd8a8bf9f578fc43626a9b039e510aad17
Committed by
GitHub
Merge pull request #1457 from m-holger/stream
Refactor Stream::filterable and Stream::pipeStreamData
Showing
8 changed files
with
197 additions
and
214 deletions
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 | ... | ... |