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,15 +71,3 @@ ContentNormalizer::handleToken(QPDFTokenizer::Token const& token) | ||
| 71 | write("\n"); | 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,33 +27,37 @@ using namespace qpdf; | ||
| 27 | 27 | ||
| 28 | namespace | 28 | namespace |
| 29 | { | 29 | { |
| 30 | - class SF_Crypt: public QPDFStreamFilter | 30 | + class SF_Crypt final: public QPDFStreamFilter |
| 31 | { | 31 | { |
| 32 | public: | 32 | public: |
| 33 | SF_Crypt() = default; | 33 | SF_Crypt() = default; |
| 34 | - ~SF_Crypt() override = default; | 34 | + ~SF_Crypt() final = default; |
| 35 | 35 | ||
| 36 | bool | 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 | Pipeline* | 59 | Pipeline* |
| 56 | - getDecodePipeline(Pipeline*) override | 60 | + getDecodePipeline(Pipeline*) final |
| 57 | { | 61 | { |
| 58 | // Not used -- handled by pipeStreamData | 62 | // Not used -- handled by pipeStreamData |
| 59 | return nullptr; | 63 | return nullptr; |
| @@ -78,31 +82,67 @@ namespace | @@ -78,31 +82,67 @@ namespace | ||
| 78 | Stream stream; | 82 | Stream stream; |
| 79 | qpdf_stream_decode_level_e decode_level; | 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 | } // namespace | 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 | // The PDF specification provides these filter abbreviations for use in inline images, but | 114 | // The PDF specification provides these filter abbreviations for use in inline images, but |
| 85 | // according to table H.1 in the pre-ISO versions of the PDF specification, Adobe Reader also | 115 | // according to table H.1 in the pre-ISO versions of the PDF specification, Adobe Reader also |
| 86 | // accepts them for stream filters. | 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 | Stream::Stream( | 147 | Stream::Stream( |
| 108 | QPDF& qpdf, QPDFObjGen og, QPDFObjectHandle stream_dict, qpdf_offset_t offset, size_t length) : | 148 | QPDF& qpdf, QPDFObjGen og, QPDFObjectHandle stream_dict, qpdf_offset_t offset, size_t length) : |
| @@ -292,112 +332,88 @@ Stream::isRootMetadata() const | @@ -292,112 +332,88 @@ Stream::isRootMetadata() const | ||
| 292 | 332 | ||
| 293 | bool | 333 | bool |
| 294 | Stream::filterable( | 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 | auto s = stream(); | 338 | auto s = stream(); |
| 300 | // Check filters | 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 | if (filter_obj.isNull()) { | 343 | if (filter_obj.isNull()) { |
| 308 | // No filters | 344 | // No filters |
| 309 | - } else if (filter_obj.isName()) { | 345 | + return true; |
| 346 | + } | ||
| 347 | + if (filter_obj.isName()) { | ||
| 310 | // One filter | 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 | // Potentially multiple filters | 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 | } else { | 368 | } else { |
| 324 | - filters_okay = false; | ||
| 325 | - } | ||
| 326 | - | ||
| 327 | - if (!filters_okay) { | ||
| 328 | - QTC::TC("qpdf", "QPDF_Stream invalid filter"); | ||
| 329 | warn("stream filter type is not name or array"); | 369 | warn("stream filter type is not name or array"); |
| 330 | return false; | 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 | } else { | 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 | bool | 419 | bool |
| @@ -411,33 +427,17 @@ Stream::pipeStreamData( | @@ -411,33 +427,17 @@ Stream::pipeStreamData( | ||
| 411 | { | 427 | { |
| 412 | auto s = stream(); | 428 | auto s = stream(); |
| 413 | std::vector<std::shared_ptr<QPDFStreamFilter>> filters; | 429 | std::vector<std::shared_ptr<QPDFStreamFilter>> filters; |
| 414 | - bool specialized_compression = false; | ||
| 415 | - bool lossy_compression = false; | ||
| 416 | bool ignored; | 430 | bool ignored; |
| 417 | - if (filterp == nullptr) { | 431 | + if (!filterp) { |
| 418 | filterp = &ignored; | 432 | filterp = &ignored; |
| 419 | } | 433 | } |
| 420 | bool& filter = *filterp; | 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 | if (filter) { | 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 | QTC::TC("qpdf", "QPDF_Stream pipeStreamData with null pipeline"); | 441 | QTC::TC("qpdf", "QPDF_Stream pipeStreamData with null pipeline"); |
| 442 | // Return value is whether we can filter in this case. | 442 | // Return value is whether we can filter in this case. |
| 443 | return filter; | 443 | return filter; |
| @@ -446,40 +446,37 @@ Stream::pipeStreamData( | @@ -446,40 +446,37 @@ Stream::pipeStreamData( | ||
| 446 | // Construct the pipeline in reverse order. Force pipelines we create to be deleted when this | 446 | // Construct the pipeline in reverse order. Force pipelines we create to be deleted when this |
| 447 | // function finishes. Pipelines created by QPDFStreamFilter objects will be deleted by those | 447 | // function finishes. Pipelines created by QPDFStreamFilter objects will be deleted by those |
| 448 | // objects. | 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 | if (filter) { | 452 | if (filter) { |
| 454 | if (encode_flags & qpdf_ef_compress) { | 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 | pipeline = new_pipeline.get(); | 456 | pipeline = new_pipeline.get(); |
| 457 | + to_delete.push_back(std::move(new_pipeline)); | ||
| 459 | } | 458 | } |
| 460 | 459 | ||
| 461 | if (encode_flags & qpdf_ef_normalize) { | 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 | pipeline = new_pipeline.get(); | 463 | pipeline = new_pipeline.get(); |
| 464 | + to_delete.push_back(std::move(new_pipeline)); | ||
| 467 | } | 465 | } |
| 468 | 466 | ||
| 469 | for (auto iter = s->token_filters.rbegin(); iter != s->token_filters.rend(); ++iter) { | 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 | pipeline = new_pipeline.get(); | 470 | pipeline = new_pipeline.get(); |
| 471 | + to_delete.push_back(std::move(new_pipeline)); | ||
| 474 | } | 472 | } |
| 475 | 473 | ||
| 476 | for (auto f_iter = filters.rbegin(); f_iter != filters.rend(); ++f_iter) { | 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 | pipeline = decode_pipeline; | 476 | pipeline = decode_pipeline; |
| 480 | } | 477 | } |
| 481 | auto* flate = dynamic_cast<Pl_Flate*>(pipeline); | 478 | auto* flate = dynamic_cast<Pl_Flate*>(pipeline); |
| 482 | - if (flate != nullptr) { | 479 | + if (flate) { |
| 483 | flate->setWarnCallback([this](char const* msg, int code) { warn(msg); }); | 480 | flate->setWarnCallback([this](char const* msg, int code) { warn(msg); }); |
| 484 | } | 481 | } |
| 485 | } | 482 | } |
| @@ -495,18 +492,15 @@ Stream::pipeStreamData( | @@ -495,18 +492,15 @@ Stream::pipeStreamData( | ||
| 495 | if (!s->stream_provider->provideStreamData( | 492 | if (!s->stream_provider->provideStreamData( |
| 496 | obj->getObjGen(), &count, suppress_warnings, will_retry)) { | 493 | obj->getObjGen(), &count, suppress_warnings, will_retry)) { |
| 497 | filter = false; | 494 | filter = false; |
| 498 | - success = false; | 495 | + return false; |
| 499 | } | 496 | } |
| 500 | } else { | 497 | } else { |
| 501 | s->stream_provider->provideStreamData(obj->getObjGen(), &count); | 498 | s->stream_provider->provideStreamData(obj->getObjGen(), &count); |
| 502 | } | 499 | } |
| 503 | qpdf_offset_t actual_length = count.getCount(); | 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 | QTC::TC("qpdf", "QPDF_Stream provider length mismatch"); | 504 | QTC::TC("qpdf", "QPDF_Stream provider length mismatch"); |
| 511 | // This would be caused by programmer error on the part of a library user, not by | 505 | // This would be caused by programmer error on the part of a library user, not by |
| 512 | // invalid input data. | 506 | // invalid input data. |
| @@ -515,14 +509,15 @@ Stream::pipeStreamData( | @@ -515,14 +509,15 @@ Stream::pipeStreamData( | ||
| 515 | std::to_string(actual_length) + " bytes instead of expected " + | 509 | std::to_string(actual_length) + " bytes instead of expected " + |
| 516 | std::to_string(desired_length) + " bytes"); | 510 | std::to_string(desired_length) + " bytes"); |
| 517 | } | 511 | } |
| 518 | - } else if (success) { | 512 | + } else { |
| 519 | QTC::TC("qpdf", "QPDF_Stream provider length not provided"); | 513 | QTC::TC("qpdf", "QPDF_Stream provider length not provided"); |
| 520 | s->stream_dict.replaceKey("/Length", QPDFObjectHandle::newInteger(actual_length)); | 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 | } else { | 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 | QTC::TC("qpdf", "QPDF_Stream pipe original stream data"); | 521 | QTC::TC("qpdf", "QPDF_Stream pipe original stream data"); |
| 527 | if (!QPDF::Pipe::pipeStreamData( | 522 | if (!QPDF::Pipe::pipeStreamData( |
| 528 | obj->getQPDF(), | 523 | obj->getQPDF(), |
| @@ -535,13 +530,13 @@ Stream::pipeStreamData( | @@ -535,13 +530,13 @@ Stream::pipeStreamData( | ||
| 535 | suppress_warnings, | 530 | suppress_warnings, |
| 536 | will_retry)) { | 531 | will_retry)) { |
| 537 | filter = false; | 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 | warn("content normalization encountered bad tokens"); | 538 | warn("content normalization encountered bad tokens"); |
| 544 | - if (normalizer->lastTokenWasBad()) { | 539 | + if (normalizer.lastTokenWasBad()) { |
| 545 | QTC::TC("qpdf", "QPDF_Stream bad token at end during normalize"); | 540 | QTC::TC("qpdf", "QPDF_Stream bad token at end during normalize"); |
| 546 | warn( | 541 | warn( |
| 547 | "normalized content ended with a bad token; you may be able to resolve this by " | 542 | "normalized content ended with a bad token; you may be able to resolve this by " |
| @@ -554,7 +549,7 @@ Stream::pipeStreamData( | @@ -554,7 +549,7 @@ Stream::pipeStreamData( | ||
| 554 | "in the manual."); | 549 | "in the manual."); |
| 555 | } | 550 | } |
| 556 | 551 | ||
| 557 | - return success; | 552 | + return true; |
| 558 | } | 553 | } |
| 559 | 554 | ||
| 560 | void | 555 | void |
libqpdf/SF_FlateLzwDecode.cc
| @@ -69,48 +69,37 @@ SF_FlateLzwDecode::setDecodeParms(QPDFObjectHandle decode_parms) | @@ -69,48 +69,37 @@ SF_FlateLzwDecode::setDecodeParms(QPDFObjectHandle decode_parms) | ||
| 69 | Pipeline* | 69 | Pipeline* |
| 70 | SF_FlateLzwDecode::getDecodePipeline(Pipeline* next) | 70 | SF_FlateLzwDecode::getDecodePipeline(Pipeline* next) |
| 71 | { | 71 | { |
| 72 | - std::shared_ptr<Pipeline> pipeline; | 72 | + std::unique_ptr<Pipeline> pipeline; |
| 73 | if (predictor >= 10 && predictor <= 15) { | 73 | if (predictor >= 10 && predictor <= 15) { |
| 74 | QTC::TC("qpdf", "SF_FlateLzwDecode PNG filter"); | 74 | QTC::TC("qpdf", "SF_FlateLzwDecode PNG filter"); |
| 75 | - pipeline = std::make_shared<Pl_PNGFilter>( | 75 | + pipeline = std::make_unique<Pl_PNGFilter>( |
| 76 | "png decode", | 76 | "png decode", |
| 77 | next, | 77 | next, |
| 78 | Pl_PNGFilter::a_decode, | 78 | Pl_PNGFilter::a_decode, |
| 79 | QIntC::to_uint(columns), | 79 | QIntC::to_uint(columns), |
| 80 | QIntC::to_uint(colors), | 80 | QIntC::to_uint(colors), |
| 81 | QIntC::to_uint(bits_per_component)); | 81 | QIntC::to_uint(bits_per_component)); |
| 82 | - pipelines.push_back(pipeline); | ||
| 83 | next = pipeline.get(); | 82 | next = pipeline.get(); |
| 83 | + pipelines.push_back(std::move(pipeline)); | ||
| 84 | } else if (predictor == 2) { | 84 | } else if (predictor == 2) { |
| 85 | QTC::TC("qpdf", "SF_FlateLzwDecode TIFF predictor"); | 85 | QTC::TC("qpdf", "SF_FlateLzwDecode TIFF predictor"); |
| 86 | - pipeline = std::make_shared<Pl_TIFFPredictor>( | 86 | + pipeline = std::make_unique<Pl_TIFFPredictor>( |
| 87 | "tiff decode", | 87 | "tiff decode", |
| 88 | next, | 88 | next, |
| 89 | Pl_TIFFPredictor::a_decode, | 89 | Pl_TIFFPredictor::a_decode, |
| 90 | QIntC::to_uint(columns), | 90 | QIntC::to_uint(columns), |
| 91 | QIntC::to_uint(colors), | 91 | QIntC::to_uint(colors), |
| 92 | QIntC::to_uint(bits_per_component)); | 92 | QIntC::to_uint(bits_per_component)); |
| 93 | - pipelines.push_back(pipeline); | ||
| 94 | next = pipeline.get(); | 93 | next = pipeline.get(); |
| 94 | + pipelines.push_back(std::move(pipeline)); | ||
| 95 | } | 95 | } |
| 96 | 96 | ||
| 97 | if (lzw) { | 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 | } else { | 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,15 +3,23 @@ | ||
| 3 | 3 | ||
| 4 | #include <qpdf/QPDFObjectHandle.hh> | 4 | #include <qpdf/QPDFObjectHandle.hh> |
| 5 | 5 | ||
| 6 | -class ContentNormalizer: public QPDFObjectHandle::TokenFilter | 6 | +class ContentNormalizer final: public QPDFObjectHandle::TokenFilter |
| 7 | { | 7 | { |
| 8 | public: | 8 | public: |
| 9 | ContentNormalizer(); | 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 | private: | 24 | private: |
| 17 | bool any_bad_tokens; | 25 | bool any_bad_tokens; |
libqpdf/qpdf/QPDFObjectHandle_private.hh
| @@ -312,17 +312,14 @@ namespace qpdf | @@ -312,17 +312,14 @@ namespace qpdf | ||
| 312 | return nullptr; // unreachable | 312 | return nullptr; // unreachable |
| 313 | } | 313 | } |
| 314 | bool filterable( | 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 | void replaceFilterData( | 317 | void replaceFilterData( |
| 319 | QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms, size_t length); | 318 | QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms, size_t length); |
| 320 | 319 | ||
| 321 | void warn(std::string const& message); | 320 | void warn(std::string const& message); |
| 322 | 321 | ||
| 323 | static std::map<std::string, std::string> filter_abbreviations; | 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 | template <typename T> | 325 | template <typename T> |
libqpdf/qpdf/QPDFObject_private.hh
| @@ -226,6 +226,8 @@ class QPDF_Stream final | @@ -226,6 +226,8 @@ class QPDF_Stream final | ||
| 226 | std::shared_ptr<Buffer> stream_data; | 226 | std::shared_ptr<Buffer> stream_data; |
| 227 | std::shared_ptr<QPDFObjectHandle::StreamDataProvider> stream_provider; | 227 | std::shared_ptr<QPDFObjectHandle::StreamDataProvider> stream_provider; |
| 228 | std::vector<std::shared_ptr<QPDFObjectHandle::TokenFilter>> token_filters; | 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 | friend class QPDFObject; | 233 | friend class QPDFObject; |
libqpdf/qpdf/SF_FlateLzwDecode.hh
| @@ -17,8 +17,16 @@ class SF_FlateLzwDecode final: public QPDFStreamFilter | @@ -17,8 +17,16 @@ class SF_FlateLzwDecode final: public QPDFStreamFilter | ||
| 17 | bool setDecodeParms(QPDFObjectHandle decode_parms) final; | 17 | bool setDecodeParms(QPDFObjectHandle decode_parms) final; |
| 18 | Pipeline* getDecodePipeline(Pipeline* next) final; | 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 | private: | 31 | private: |
| 24 | bool lzw{}; | 32 | bool lzw{}; |
| @@ -28,7 +36,7 @@ class SF_FlateLzwDecode final: public QPDFStreamFilter | @@ -28,7 +36,7 @@ class SF_FlateLzwDecode final: public QPDFStreamFilter | ||
| 28 | int colors{1}; | 36 | int colors{1}; |
| 29 | int bits_per_component{8}; | 37 | int bits_per_component{8}; |
| 30 | bool early_code_change{true}; | 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 | #endif // SF_FLATELZWDECODE_HH | 42 | #endif // SF_FLATELZWDECODE_HH |
qpdf/qpdf.testcov
| @@ -77,7 +77,6 @@ QPDFTokenizer bad hexstring 2nd character 0 | @@ -77,7 +77,6 @@ QPDFTokenizer bad hexstring 2nd character 0 | ||
| 77 | QPDFTokenizer null in name 0 | 77 | QPDFTokenizer null in name 0 |
| 78 | QPDFTokenizer bad name 1 0 | 78 | QPDFTokenizer bad name 1 0 |
| 79 | QPDFTokenizer bad name 2 0 | 79 | QPDFTokenizer bad name 2 0 |
| 80 | -QPDF_Stream invalid filter 0 | ||
| 81 | QPDF UseOutlines but no Outlines 0 | 80 | QPDF UseOutlines but no Outlines 0 |
| 82 | QPDFObjectHandle makeDirect loop 0 | 81 | QPDFObjectHandle makeDirect loop 0 |
| 83 | QPDFObjectHandle copy stream 1 | 82 | QPDFObjectHandle copy stream 1 |
| @@ -168,7 +167,6 @@ qpdf-c called qpdf_has_error 0 | @@ -168,7 +167,6 @@ qpdf-c called qpdf_has_error 0 | ||
| 168 | qpdf-c called qpdf_get_qpdf_version 0 | 167 | qpdf-c called qpdf_get_qpdf_version 0 |
| 169 | QPDF_Stream pipe original stream data 0 | 168 | QPDF_Stream pipe original stream data 0 |
| 170 | QPDF_Stream pipe replaced stream data 0 | 169 | QPDF_Stream pipe replaced stream data 0 |
| 171 | -QPDF_Stream pipe use stream provider 0 | ||
| 172 | QPDF_Stream provider length mismatch 0 | 170 | QPDF_Stream provider length mismatch 0 |
| 173 | QPDFObjectHandle newStream 0 | 171 | QPDFObjectHandle newStream 0 |
| 174 | QPDFObjectHandle newStream with data 0 | 172 | QPDFObjectHandle newStream with data 0 |
| @@ -177,7 +175,6 @@ QPDFObjectHandle prepend page contents 0 | @@ -177,7 +175,6 @@ QPDFObjectHandle prepend page contents 0 | ||
| 177 | QPDFObjectHandle append page contents 0 | 175 | QPDFObjectHandle append page contents 0 |
| 178 | QPDF_Stream getRawStreamData 0 | 176 | QPDF_Stream getRawStreamData 0 |
| 179 | QPDF_Stream getStreamData 0 | 177 | QPDF_Stream getStreamData 0 |
| 180 | -QPDF_Stream expand filter abbreviation 0 | ||
| 181 | qpdf-c called qpdf_read_memory 0 | 178 | qpdf-c called qpdf_read_memory 0 |
| 182 | QPDF stream without newline 0 | 179 | QPDF stream without newline 0 |
| 183 | QPDF stream with CR only 0 | 180 | QPDF stream with CR only 0 |
| @@ -281,7 +278,6 @@ QPDF ignore second extra space in xref entry 0 | @@ -281,7 +278,6 @@ QPDF ignore second extra space in xref entry 0 | ||
| 281 | QPDF ignore length error xref entry 0 | 278 | QPDF ignore length error xref entry 0 |
| 282 | QPDF_encryption pad short parameter 0 | 279 | QPDF_encryption pad short parameter 0 |
| 283 | QPDFObjectHandle found old angle 1 | 280 | QPDFObjectHandle found old angle 1 |
| 284 | -QPDF_Stream special filters 3 | ||
| 285 | QPDFTokenizer block long token 0 | 281 | QPDFTokenizer block long token 0 |
| 286 | qpdf-c called qpdf_set_decode_level 0 | 282 | qpdf-c called qpdf_set_decode_level 0 |
| 287 | qpdf-c called qpdf_set_compress_streams 0 | 283 | qpdf-c called qpdf_set_compress_streams 0 |