Commit e2823965faa00a115ddc189576336d0bd7278ad0

Authored by m-holger
1 parent 27e14333

Refactor `limits_error` handling for improved clarity and consistency

- Introduce `limits_error` method in `QPDFParser` for centralized limit-related error handling.
- Enhance warnings and error messages with detailed limit identifiers (e.g., `parser-max-nesting`).
- Refactor limit checks to improve maintainability and ensure uniformity in error reporting.
- Update tests and output to reflect adjusted error handling approach.
libqpdf/QPDFParser.cc
@@ -437,18 +437,16 @@ QPDFParser::parseRemainder(bool content_stream) @@ -437,18 +437,16 @@ QPDFParser::parseRemainder(bool content_stream)
437 case QPDFTokenizer::tt_array_open: 437 case QPDFTokenizer::tt_array_open:
438 case QPDFTokenizer::tt_dict_open: 438 case QPDFTokenizer::tt_dict_open:
439 if (stack.size() > max_nesting) { 439 if (stack.size() > max_nesting) {
440 - global::Limits::error();  
441 - warn("limits error: ignoring excessively deeply nested data structure");  
442 - return {};  
443 - } else {  
444 - b_contents = false;  
445 - stack.emplace_back(  
446 - input,  
447 - (tokenizer.getType() == QPDFTokenizer::tt_array_open) ? st_array  
448 - : st_dictionary_key);  
449 - frame = &stack.back();  
450 - continue; 440 + limits_error(
  441 + "parser-max-nesting", "ignoring excessively deeply nested data structure");
451 } 442 }
  443 + b_contents = false;
  444 + stack.emplace_back(
  445 + input,
  446 + (tokenizer.getType() == QPDFTokenizer::tt_array_open) ? st_array
  447 + : st_dictionary_key);
  448 + frame = &stack.back();
  449 + continue;
452 450
453 case QPDFTokenizer::tt_bool: 451 case QPDFTokenizer::tt_bool:
454 addScalar<QPDF_Bool>(tokenizer.getValue() == "true"); 452 addScalar<QPDF_Bool>(tokenizer.getValue() == "true");
@@ -588,10 +586,10 @@ void @@ -588,10 +586,10 @@ void
588 QPDFParser::addScalar(Args&&... args) 586 QPDFParser::addScalar(Args&&... args)
589 { 587 {
590 auto limit = Limits::parser_max_container_size(bad_count || sanity_checks); 588 auto limit = Limits::parser_max_container_size(bad_count || sanity_checks);
591 - if (frame->olist.size() > limit || frame->dict.size() > limit) { 589 + if (frame->olist.size() >= limit || frame->dict.size() >= limit) {
592 // Stop adding scalars. We are going to abort when the close token or a bad token is 590 // Stop adding scalars. We are going to abort when the close token or a bad token is
593 // encountered. 591 // encountered.
594 - max_bad_count = 0; 592 + max_bad_count = 1;
595 check_too_many_bad_tokens(); // always throws Error() 593 check_too_many_bad_tokens(); // always throws Error()
596 } 594 }
597 auto obj = QPDFObject::create<T>(std::forward<Args>(args)...); 595 auto obj = QPDFObject::create<T>(std::forward<Args>(args)...);
@@ -646,26 +644,29 @@ void @@ -646,26 +644,29 @@ void
646 QPDFParser::check_too_many_bad_tokens() 644 QPDFParser::check_too_many_bad_tokens()
647 { 645 {
648 auto limit = Limits::parser_max_container_size(bad_count || sanity_checks); 646 auto limit = Limits::parser_max_container_size(bad_count || sanity_checks);
649 - if (frame->olist.size() > limit || frame->dict.size() > limit) { 647 + if (frame->olist.size() >= limit || frame->dict.size() >= limit) {
650 if (bad_count) { 648 if (bad_count) {
651 - Limits::error();  
652 - warn(  
653 - "limits error: encountered errors while parsing an array or dictionary with more "  
654 - "than " +  
655 - std::to_string(limit) + " elements; giving up on reading object");  
656 - throw Error(); 649 + limits_error(
  650 + "parser-max-container-size-damaged",
  651 + "encountered errors while parsing an array or dictionary with more than " +
  652 + std::to_string(limit) + " elements; giving up on reading object");
657 } 653 }
658 - warn( 654 + limits_error(
  655 + "parser-max-container-size",
659 "encountered an array or dictionary with more than " + std::to_string(limit) + 656 "encountered an array or dictionary with more than " + std::to_string(limit) +
660 - " elements during xref recovery; giving up on reading object"); 657 + " elements during xref recovery; giving up on reading object");
661 } 658 }
662 - if (max_bad_count && --max_bad_count > 0 && good_count > 4) { 659 + if (max_bad_count && --max_bad_count == 0) {
  660 + limits_error(
  661 + "parser-max-errors", "too many errors during parsing; treating object as null");
  662 + }
  663 + if (good_count > 4) {
663 good_count = 0; 664 good_count = 0;
664 bad_count = 1; 665 bad_count = 1;
665 return; 666 return;
666 } 667 }
667 if (++bad_count > 5 || 668 if (++bad_count > 5 ||
668 - (frame->state != st_array && QIntC::to_size(max_bad_count) < frame->olist.size())) { 669 + (frame->state != st_array && std::cmp_less(max_bad_count, frame->olist.size()))) {
669 // Give up after 5 errors in close proximity or if the number of missing dictionary keys 670 // Give up after 5 errors in close proximity or if the number of missing dictionary keys
670 // exceeds the remaining number of allowable total errors. 671 // exceeds the remaining number of allowable total errors.
671 warn("too many errors; giving up on reading object"); 672 warn("too many errors; giving up on reading object");
@@ -675,6 +676,14 @@ QPDFParser::check_too_many_bad_tokens() @@ -675,6 +676,14 @@ QPDFParser::check_too_many_bad_tokens()
675 } 676 }
676 677
677 void 678 void
  679 +QPDFParser::limits_error(std::string const& limit, std::string const& msg)
  680 +{
  681 + Limits::error();
  682 + warn("limits error("s + limit + "): " + msg);
  683 + throw Error();
  684 +}
  685 +
  686 +void
678 QPDFParser::warn(QPDFExc const& e) const 687 QPDFParser::warn(QPDFExc const& e) const
679 { 688 {
680 // If parsing on behalf of a QPDF object and want to give a warning, we can warn through the 689 // If parsing on behalf of a QPDF object and want to give a warning, we can warn through the
libqpdf/qpdf/QPDFParser.hh
@@ -124,6 +124,7 @@ class QPDFParser @@ -124,6 +124,7 @@ class QPDFParser
124 void check_too_many_bad_tokens(); 124 void check_too_many_bad_tokens();
125 void warnDuplicateKey(); 125 void warnDuplicateKey();
126 void fixMissingKeys(); 126 void fixMissingKeys();
  127 + [[noreturn]] void limits_error(std::string const& limit, std::string const& msg);
127 void warn(qpdf_offset_t offset, std::string const& msg) const; 128 void warn(qpdf_offset_t offset, std::string const& msg) const;
128 void warn(std::string const& msg) const; 129 void warn(std::string const& msg) const;
129 void warn(QPDFExc const&) const; 130 void warn(QPDFExc const&) const;
qpdf/qtest/qpdf/issue-146.out
1 WARNING: issue-146.pdf: file is damaged 1 WARNING: issue-146.pdf: file is damaged
2 WARNING: issue-146.pdf: can't find startxref 2 WARNING: issue-146.pdf: can't find startxref
3 WARNING: issue-146.pdf: Attempting to reconstruct cross-reference table 3 WARNING: issue-146.pdf: Attempting to reconstruct cross-reference table
4 -WARNING: issue-146.pdf (trailer, offset 695): limits error: ignoring excessively deeply nested data structure 4 +WARNING: issue-146.pdf (trailer, offset 695): limits error(parser-max-nesting): ignoring excessively deeply nested data structure
5 WARNING: issue-146.pdf (object 1 0, offset 92): expected endobj 5 WARNING: issue-146.pdf (object 1 0, offset 92): expected endobj
6 WARNING: issue-146.pdf (object 7 0, offset 146): unknown token while reading object; treating as null 6 WARNING: issue-146.pdf (object 7 0, offset 146): unknown token while reading object; treating as null
7 WARNING: issue-146.pdf (object 7 0, offset 168): expected endobj 7 WARNING: issue-146.pdf (object 7 0, offset 168): expected endobj
qpdf/qtest/qpdf/issue-202.out
1 -WARNING: issue-202.pdf (trailer, offset 55770): limits error: ignoring excessively deeply nested data structure 1 +WARNING: issue-202.pdf (trailer, offset 55770): limits error(parser-max-nesting): ignoring excessively deeply nested data structure
2 WARNING: issue-202.pdf: file is damaged 2 WARNING: issue-202.pdf: file is damaged
3 WARNING: issue-202.pdf (offset 54769): expected trailer dictionary 3 WARNING: issue-202.pdf (offset 54769): expected trailer dictionary
4 WARNING: issue-202.pdf: Attempting to reconstruct cross-reference table 4 WARNING: issue-202.pdf: Attempting to reconstruct cross-reference table
5 -WARNING: issue-202.pdf (trailer, offset 55770): limits error: ignoring excessively deeply nested data structure 5 +WARNING: issue-202.pdf (trailer, offset 55770): limits error(parser-max-nesting): ignoring excessively deeply nested data structure
6 WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Creator; last occurrence overrides earlier ones 6 WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Creator; last occurrence overrides earlier ones
7 WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Producer; last occurrence overrides earlier ones 7 WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Producer; last occurrence overrides earlier ones
8 WARNING: issue-202.pdf: unable to find trailer dictionary while recovering damaged file 8 WARNING: issue-202.pdf: unable to find trailer dictionary while recovering damaged file