Commit 40f00122b811ca5c8788856f5baf4e5e088926eb

Authored by Jay Berkenbilt
1 parent dd8dad74

Convert object parsing errors to warnings

QPDFObjectHandle::parseInternal now issues warnings instead of
throwing exceptions for all error conditions that it finds (except
internal logic errors) and has stronger recovery for things like
invalid tokens and malformed dictionaries. This should improve qpdf's
ability to recover from a wide range of broken files that currently
cause it to fail.
ChangeLog
  1 +2017-07-27 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Significantly improve recoverability from invalid qpdf objects.
  4 + Most conditions in basic object parsing that used to cause qpdf to
  5 + exit are now warnings. There are still many more opportunities for
  6 + improvements of this sort beyond just object parsing.
  7 +
1 2017-07-26 Jay Berkenbilt <ejb@ql.org> 8 2017-07-26 Jay Berkenbilt <ejb@ql.org>
2 9
3 * Fixes to infinite loops below also fix problems reported in 10 * Fixes to infinite loops below also fix problems reported in
include/qpdf/QPDF.hh
@@ -522,6 +522,18 @@ class QPDF @@ -522,6 +522,18 @@ class QPDF
522 }; 522 };
523 friend class Resolver; 523 friend class Resolver;
524 524
  525 + // Warner class allows QPDFObjectHandle to create warnings
  526 + class Warner
  527 + {
  528 + friend class QPDFObjectHandle;
  529 + private:
  530 + static void warn(QPDF* qpdf, QPDFExc const& e)
  531 + {
  532 + qpdf->warn(e);
  533 + }
  534 + };
  535 + friend class Warner;
  536 +
525 // Pipe class is restricted to QPDF_Stream 537 // Pipe class is restricted to QPDF_Stream
526 class Pipe 538 class Pipe
527 { 539 {
include/qpdf/QPDFObjectHandle.hh
@@ -28,6 +28,7 @@ class QPDF; @@ -28,6 +28,7 @@ class QPDF;
28 class QPDF_Dictionary; 28 class QPDF_Dictionary;
29 class QPDF_Array; 29 class QPDF_Array;
30 class QPDFTokenizer; 30 class QPDFTokenizer;
  31 +class QPDFExc;
31 32
32 class QPDFObjectHandle 33 class QPDFObjectHandle
33 { 34 {
@@ -623,6 +624,9 @@ class QPDFObjectHandle @@ -623,6 +624,9 @@ class QPDFObjectHandle
623 static void parseContentStream_internal( 624 static void parseContentStream_internal(
624 QPDFObjectHandle stream, ParserCallbacks* callbacks); 625 QPDFObjectHandle stream, ParserCallbacks* callbacks);
625 626
  627 + // Other methods
  628 + static void warn(QPDF*, QPDFExc const&);
  629 +
626 bool initialized; 630 bool initialized;
627 631
628 QPDF* qpdf; // 0 for direct object 632 QPDF* qpdf; // 0 for direct object
libqpdf/QPDF.cc
@@ -334,8 +334,9 @@ QPDF::reconstruct_xref(QPDFExc&amp; e) @@ -334,8 +334,9 @@ QPDF::reconstruct_xref(QPDFExc&amp; e)
334 { 334 {
335 if (this->reconstructed_xref) 335 if (this->reconstructed_xref)
336 { 336 {
337 - // Avoid xref reconstruction infinite loops  
338 - QTC::TC("qpdf", "QPDF caught recursive xref reconstruction"); 337 + // Avoid xref reconstruction infinite loops. This is getting
  338 + // very hard to reproduce because qpdf is throwing many fewer
  339 + // exceptions while parsing. Most situations are warnings now.
339 throw e; 340 throw e;
340 } 341 }
341 342
libqpdf/QPDFObjectHandle.cc
@@ -850,6 +850,11 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input, @@ -850,6 +850,11 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
850 bool in_array, bool in_dictionary, 850 bool in_array, bool in_dictionary,
851 bool content_stream) 851 bool content_stream)
852 { 852 {
  853 + // This method must take care not to resolve any objects. Don't
  854 + // check the tpye of any object without first ensuring that it is
  855 + // a direct object. Otherwise, doing so may have the side effect
  856 + // of reading the object and changing the file pointer.
  857 +
853 empty = false; 858 empty = false;
854 if (in_dictionary && in_array) 859 if (in_dictionary && in_array)
855 { 860 {
@@ -891,12 +896,13 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input, @@ -891,12 +896,13 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
891 896
892 case QPDFTokenizer::tt_brace_open: 897 case QPDFTokenizer::tt_brace_open:
893 case QPDFTokenizer::tt_brace_close: 898 case QPDFTokenizer::tt_brace_close:
894 - // Don't know what to do with these for now  
895 QTC::TC("qpdf", "QPDFObjectHandle bad brace"); 899 QTC::TC("qpdf", "QPDFObjectHandle bad brace");
896 - throw QPDFExc(qpdf_e_damaged_pdf, input->getName(),  
897 - object_description,  
898 - input->getLastOffset(),  
899 - "unexpected brace token"); 900 + warn(context,
  901 + QPDFExc(qpdf_e_damaged_pdf, input->getName(),
  902 + object_description,
  903 + input->getLastOffset(),
  904 + "treating unexpected brace token as null"));
  905 + object = newNull();
900 break; 906 break;
901 907
902 case QPDFTokenizer::tt_array_close: 908 case QPDFTokenizer::tt_array_close:
@@ -907,10 +913,12 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input, @@ -907,10 +913,12 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
907 else 913 else
908 { 914 {
909 QTC::TC("qpdf", "QPDFObjectHandle bad array close"); 915 QTC::TC("qpdf", "QPDFObjectHandle bad array close");
910 - throw QPDFExc(qpdf_e_damaged_pdf, input->getName(),  
911 - object_description,  
912 - input->getLastOffset(),  
913 - "unexpected array close token"); 916 + warn(context,
  917 + QPDFExc(qpdf_e_damaged_pdf, input->getName(),
  918 + object_description,
  919 + input->getLastOffset(),
  920 + "treating unexpected array close token as null"));
  921 + object = newNull();
914 } 922 }
915 break; 923 break;
916 924
@@ -922,10 +930,12 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input, @@ -922,10 +930,12 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
922 else 930 else
923 { 931 {
924 QTC::TC("qpdf", "QPDFObjectHandle bad dictionary close"); 932 QTC::TC("qpdf", "QPDFObjectHandle bad dictionary close");
925 - throw QPDFExc(qpdf_e_damaged_pdf, input->getName(),  
926 - object_description,  
927 - input->getLastOffset(),  
928 - "unexpected dictionary close token"); 933 + warn(context,
  934 + QPDFExc(qpdf_e_damaged_pdf, input->getName(),
  935 + object_description,
  936 + input->getLastOffset(),
  937 + "unexpected dictionary close token"));
  938 + object = newNull();
929 } 939 }
930 break; 940 break;
931 941
@@ -1002,11 +1012,14 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input, @@ -1002,11 +1012,14 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
1002 } 1012 }
1003 else 1013 else
1004 { 1014 {
1005 - throw QPDFExc(qpdf_e_damaged_pdf, input->getName(),  
1006 - object_description,  
1007 - input->getLastOffset(),  
1008 - "unknown token while reading object (" +  
1009 - value + ")"); 1015 + QTC::TC("qpdf", "QPDFObjectHandle treat word as string");
  1016 + warn(context,
  1017 + QPDFExc(qpdf_e_damaged_pdf, input->getName(),
  1018 + object_description,
  1019 + input->getLastOffset(),
  1020 + "unknown token while reading object;"
  1021 + " treating as string"));
  1022 + object = newString(value);
1010 } 1023 }
1011 } 1024 }
1012 break; 1025 break;
@@ -1024,10 +1037,13 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input, @@ -1024,10 +1037,13 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
1024 break; 1037 break;
1025 1038
1026 default: 1039 default:
1027 - throw QPDFExc(qpdf_e_damaged_pdf, input->getName(),  
1028 - object_description,  
1029 - input->getLastOffset(),  
1030 - "unknown token type while reading object"); 1040 + warn(context,
  1041 + QPDFExc(qpdf_e_damaged_pdf, input->getName(),
  1042 + object_description,
  1043 + input->getLastOffset(),
  1044 + "treating unknown token type as null while "
  1045 + "reading object"));
  1046 + object = newNull();
1031 break; 1047 break;
1032 } 1048 }
1033 1049
@@ -1040,10 +1056,12 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input, @@ -1040,10 +1056,12 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
1040 } 1056 }
1041 else if (! object.isInitialized()) 1057 else if (! object.isInitialized())
1042 { 1058 {
1043 - throw QPDFExc(qpdf_e_damaged_pdf, input->getName(),  
1044 - object_description,  
1045 - input->getLastOffset(),  
1046 - "parse error while reading object"); 1059 + warn(context,
  1060 + QPDFExc(qpdf_e_damaged_pdf, input->getName(),
  1061 + object_description,
  1062 + input->getLastOffset(),
  1063 + "parse error while reading object"));
  1064 + object = newNull();
1047 } 1065 }
1048 else 1066 else
1049 { 1067 {
@@ -1057,30 +1075,65 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input, @@ -1057,30 +1075,65 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
1057 } 1075 }
1058 else if (in_dictionary) 1076 else if (in_dictionary)
1059 { 1077 {
1060 - // Convert list to map. Alternating elements are keys.  
1061 - std::map<std::string, QPDFObjectHandle> dict;  
1062 - if (olist.size() % 2)  
1063 - {  
1064 - QTC::TC("qpdf", "QPDFObjectHandle dictionary odd number of elements");  
1065 - throw QPDFExc(  
1066 - qpdf_e_damaged_pdf, input->getName(),  
1067 - object_description, input->getLastOffset(),  
1068 - "dictionary ending here has an odd number of elements");  
1069 - }  
1070 - for (unsigned int i = 0; i < olist.size(); i += 2)  
1071 - {  
1072 - QPDFObjectHandle key_obj = olist.at(i);  
1073 - QPDFObjectHandle val = olist.at(i + 1);  
1074 - if (! key_obj.isName())  
1075 - {  
1076 - throw QPDFExc(  
1077 - qpdf_e_damaged_pdf,  
1078 - input->getName(), object_description, offset,  
1079 - std::string("dictionary key is not not a name token"));  
1080 - }  
1081 - dict[key_obj.getName()] = val;  
1082 - }  
1083 - object = newDictionary(dict); 1078 + // Convert list to map. Alternating elements are keys. Attempt
  1079 + // to recover more or less gracefully from invalid
  1080 + // dictionaries.
  1081 + std::set<std::string> names;
  1082 + for (std::vector<QPDFObjectHandle>::iterator iter = olist.begin();
  1083 + iter != olist.end(); ++iter)
  1084 + {
  1085 + if ((! (*iter).isIndirect()) && (*iter).isName())
  1086 + {
  1087 + names.insert((*iter).getName());
  1088 + }
  1089 + }
  1090 +
  1091 + std::map<std::string, QPDFObjectHandle> dict;
  1092 + int next_fake_key = 1;
  1093 + for (unsigned int i = 0; i < olist.size(); ++i)
  1094 + {
  1095 + QPDFObjectHandle key_obj = olist.at(i);
  1096 + QPDFObjectHandle val;
  1097 + if (key_obj.isIndirect() || (! key_obj.isName()))
  1098 + {
  1099 + bool found_fake = false;
  1100 + std::string candidate;
  1101 + while (! found_fake)
  1102 + {
  1103 + candidate =
  1104 + "/QPDFFake" + QUtil::int_to_string(next_fake_key++);
  1105 + found_fake = (names.count(candidate) == 0);
  1106 + QTC::TC("qpdf", "QPDFObjectHandle found fake",
  1107 + (found_fake ? 0 : 1));
  1108 + }
  1109 + warn(context,
  1110 + QPDFExc(
  1111 + qpdf_e_damaged_pdf,
  1112 + input->getName(), object_description, offset,
  1113 + "expected dictionary key but found"
  1114 + " non-name object; inserting key " +
  1115 + candidate));
  1116 + val = key_obj;
  1117 + key_obj = newName(candidate);
  1118 + }
  1119 + else if (i + 1 >= olist.size())
  1120 + {
  1121 + QTC::TC("qpdf", "QPDFObjectHandle no val for last key");
  1122 + warn(context,
  1123 + QPDFExc(
  1124 + qpdf_e_damaged_pdf,
  1125 + input->getName(), object_description, offset,
  1126 + "dictionary ended prematurely; using null as value"
  1127 + " for last key"));
  1128 + val = newNull();
  1129 + }
  1130 + else
  1131 + {
  1132 + val = olist.at(++i);
  1133 + }
  1134 + dict[key_obj.getName()] = val;
  1135 + }
  1136 + object = newDictionary(dict);
1084 } 1137 }
1085 1138
1086 return object; 1139 return object;
@@ -1544,3 +1597,20 @@ QPDFObjectHandle::dereference() @@ -1544,3 +1597,20 @@ QPDFObjectHandle::dereference()
1544 } 1597 }
1545 } 1598 }
1546 } 1599 }
  1600 +
  1601 +void
  1602 +QPDFObjectHandle::warn(QPDF* qpdf, QPDFExc const& e)
  1603 +{
  1604 + // If parsing on behalf of a QPDF object and want to give a
  1605 + // warning, we can warn through the object. If parsing for some
  1606 + // other reason, such as an explicit creation of an object from a
  1607 + // string, then just throw the exception.
  1608 + if (qpdf)
  1609 + {
  1610 + QPDF::Warner::warn(qpdf, e);
  1611 + }
  1612 + else
  1613 + {
  1614 + throw e;
  1615 + }
  1616 +}
libqpdf/qpdf-c.cc
@@ -261,7 +261,15 @@ QPDF_ERROR_CODE qpdf_read(qpdf_data qpdf, char const* filename, @@ -261,7 +261,15 @@ QPDF_ERROR_CODE qpdf_read(qpdf_data qpdf, char const* filename,
261 qpdf->filename = filename; 261 qpdf->filename = filename;
262 qpdf->password = password; 262 qpdf->password = password;
263 status = trap_errors(qpdf, &call_read); 263 status = trap_errors(qpdf, &call_read);
264 - QTC::TC("qpdf", "qpdf-c called qpdf_read", status); 264 + // We no longer have a good way to exercise a file with both
  265 + // warnings and errors because qpdf is getting much better at
  266 + // recovering.
  267 + QTC::TC("qpdf", "qpdf-c called qpdf_read",
  268 + (status == 0) ? 0
  269 + : (status & QPDF_WARNINGS) ? 1
  270 + : (status & QPDF_ERRORS) ? 2 :
  271 + -1
  272 + );
265 return status; 273 return status;
266 } 274 }
267 275
qpdf/qpdf.testcov
@@ -61,7 +61,6 @@ QPDF trailer size not integer 0 @@ -61,7 +61,6 @@ QPDF trailer size not integer 0
61 QPDF trailer prev not integer 0 61 QPDF trailer prev not integer 0
62 QPDFObjectHandle bad brace 0 62 QPDFObjectHandle bad brace 0
63 QPDFObjectHandle bad array close 0 63 QPDFObjectHandle bad array close 0
64 -QPDFObjectHandle dictionary odd number of elements 0  
65 QPDF stream without length 0 64 QPDF stream without length 0
66 QPDF stream length not integer 0 65 QPDF stream length not integer 0
67 QPDF missing endstream 0 66 QPDF missing endstream 0
@@ -124,7 +123,7 @@ qpdf-c qpdf_next_warning returned warning 0 @@ -124,7 +123,7 @@ qpdf-c qpdf_next_warning returned warning 0
124 qpdf-c called qpdf_set_suppress_warnings 0 123 qpdf-c called qpdf_set_suppress_warnings 0
125 qpdf-c called qpdf_set_ignore_xref_streams 0 124 qpdf-c called qpdf_set_ignore_xref_streams 0
126 qpdf-c called qpdf_set_attempt_recovery 0 125 qpdf-c called qpdf_set_attempt_recovery 0
127 -qpdf-c called qpdf_read 3 126 +qpdf-c called qpdf_read 2
128 qpdf-c called qpdf_get_pdf_version 0 127 qpdf-c called qpdf_get_pdf_version 0
129 qpdf-c called qpdf_get_user_password 0 128 qpdf-c called qpdf_get_user_password 0
130 qpdf-c called qpdf_is_linearized 0 129 qpdf-c called qpdf_is_linearized 0
@@ -275,5 +274,7 @@ QPDFWriter deterministic with no data 0 @@ -275,5 +274,7 @@ QPDFWriter deterministic with no data 0
275 qpdf-c called qpdf_set_deterministic_ID 0 274 qpdf-c called qpdf_set_deterministic_ID 0
276 QPDFObjectHandle indirect with 0 objid 0 275 QPDFObjectHandle indirect with 0 objid 0
277 QPDF object id 0 0 276 QPDF object id 0 0
278 -QPDF caught recursive xref reconstruction 0  
279 QPDF recursion loop in resolve 0 277 QPDF recursion loop in resolve 0
  278 +QPDFObjectHandle treat word as string 0
  279 +QPDFObjectHandle found fake 1
  280 +QPDFObjectHandle no val for last key 0
qpdf/qtest/qpdf.test
@@ -220,22 +220,22 @@ $td-&gt;runtest(&quot;C API: qpdf version&quot;, @@ -220,22 +220,22 @@ $td-&gt;runtest(&quot;C API: qpdf version&quot;,
220 220
221 # Files to reproduce various bugs 221 # Files to reproduce various bugs
222 foreach my $d ( 222 foreach my $d (
223 - ["51", "resolve loop"],  
224 - ["99", "object 0"],  
225 - ["99b", "object 0"],  
226 - ["100","xref reconstruction loop"],  
227 - ["101", "resolve for exception text"],  
228 - ["117", "other infinite loop"],  
229 - ["118", "other infinite loop"],  
230 - ["119", "other infinite loop"],  
231 - ["120", "other infinite loop"], 223 + ["51", "resolve loop", 2],
  224 + ["99", "object 0", 2],
  225 + ["99b", "object 0", 2],
  226 + ["100", "xref reconstruction loop", 2],
  227 + ["101", "resolve for exception text", 2],
  228 + ["117", "other infinite loop", 2],
  229 + ["118", "other infinite loop", 2],
  230 + ["119", "other infinite loop", 3],
  231 + ["120", "other infinite loop", 2],
232 ) 232 )
233 { 233 {
234 - my ($n, $description) = @$d; 234 + my ($n, $description, $exit_status) = @$d;
235 $td->runtest($description, 235 $td->runtest($description,
236 {$td->COMMAND => "qpdf issue-$n.pdf a.pdf"}, 236 {$td->COMMAND => "qpdf issue-$n.pdf a.pdf"},
237 {$td->FILE => "issue-$n.out", 237 {$td->FILE => "issue-$n.out",
238 - $td->EXIT_STATUS => 2}, 238 + $td->EXIT_STATUS => $exit_status},
239 $td->NORMALIZE_NEWLINES); 239 $td->NORMALIZE_NEWLINES);
240 } 240 }
241 241
@@ -593,7 +593,7 @@ $td-&gt;runtest(&quot;no type key for page nodes&quot;, @@ -593,7 +593,7 @@ $td-&gt;runtest(&quot;no type key for page nodes&quot;,
593 $td->NORMALIZE_NEWLINES); 593 $td->NORMALIZE_NEWLINES);
594 $td->runtest("ensure arguments to R are direct", 594 $td->runtest("ensure arguments to R are direct",
595 {$td->COMMAND => "qpdf --check indirect-r-arg.pdf"}, 595 {$td->COMMAND => "qpdf --check indirect-r-arg.pdf"},
596 - {$td->FILE => "indirect-r-arg.out", $td->EXIT_STATUS => 2}, 596 + {$td->FILE => "indirect-r-arg.out", $td->EXIT_STATUS => 3},
597 $td->NORMALIZE_NEWLINES); 597 $td->NORMALIZE_NEWLINES);
598 $td->runtest("detect loops in pages structure", 598 $td->runtest("detect loops in pages structure",
599 {$td->COMMAND => "qpdf --check pages-loop.pdf"}, 599 {$td->COMMAND => "qpdf --check pages-loop.pdf"},
@@ -784,16 +784,19 @@ my @badfiles = (&quot;not a PDF file&quot;, # 1 @@ -784,16 +784,19 @@ my @badfiles = (&quot;not a PDF file&quot;, # 1
784 "invalid stream /Filter and xref", # 33 784 "invalid stream /Filter and xref", # 33
785 "obj/gen in wrong place", # 34 785 "obj/gen in wrong place", # 34
786 "object stream of wrong type", # 35 786 "object stream of wrong type", # 35
  787 + "bad dictionary key", # 36
787 ); 788 );
788 789
789 -$n_tests += @badfiles + 5; 790 +$n_tests += @badfiles + 4;
790 791
791 # Test 6 contains errors in the free table consistency, but we no 792 # Test 6 contains errors in the free table consistency, but we no
792 # longer have any consistency check for this since it is not important 793 # longer have any consistency check for this since it is not important
793 # neither Acrobat nor other PDF viewers really care. Tests 12 and 28 794 # neither Acrobat nor other PDF viewers really care. Tests 12 and 28
794 # have error conditions that used to be fatal but are now considered 795 # have error conditions that used to be fatal but are now considered
795 # non-fatal. 796 # non-fatal.
796 -my %badtest_overrides = (6 => 0, 12 => 0, 28 => 0, 31 => 0); 797 +my %badtest_overrides = (6 => 0, 12 => 0, 13 => 0,
  798 + 14 => 0, 15 => 0, 17 => 0,
  799 + 28 => 0, 31 => 0, 36 => 0);
797 for (my $i = 1; $i <= scalar(@badfiles); ++$i) 800 for (my $i = 1; $i <= scalar(@badfiles); ++$i)
798 { 801 {
799 my $status = $badtest_overrides{$i}; 802 my $status = $badtest_overrides{$i};
@@ -810,11 +813,6 @@ $td-&gt;runtest(&quot;C API: errors&quot;, @@ -810,11 +813,6 @@ $td-&gt;runtest(&quot;C API: errors&quot;,
810 {$td->FILE => "c-read-errors.out", 813 {$td->FILE => "c-read-errors.out",
811 $td->EXIT_STATUS => 0}, 814 $td->EXIT_STATUS => 0},
812 $td->NORMALIZE_NEWLINES); 815 $td->NORMALIZE_NEWLINES);
813 -$td->runtest("C API: warnings and errors",  
814 - {$td->COMMAND => "qpdf-ctest 2 bad17.pdf '' a.pdf"},  
815 - {$td->FILE => "c-read-warnings-and-errors.out",  
816 - $td->EXIT_STATUS => 0},  
817 - $td->NORMALIZE_NEWLINES);  
818 $td->runtest("C API: errors writing", 816 $td->runtest("C API: errors writing",
819 {$td->COMMAND => "qpdf-ctest 2 bad30.pdf '' a.pdf"}, 817 {$td->COMMAND => "qpdf-ctest 2 bad30.pdf '' a.pdf"},
820 {$td->FILE => "c-write-errors.out", 818 {$td->FILE => "c-write-errors.out",
@@ -842,7 +840,7 @@ $n_tests += @badfiles + 8; @@ -842,7 +840,7 @@ $n_tests += @badfiles + 8;
842 # though in some cases it may. Acrobat Reader would not be able to 840 # though in some cases it may. Acrobat Reader would not be able to
843 # recover any of these files any better. 841 # recover any of these files any better.
844 my %recover_failures = (); 842 my %recover_failures = ();
845 -for (1, 7, 13..21, 24, 29..30, 33, 35) 843 +for (1, 7, 16, 18..21, 24, 29..30, 33, 35)
846 { 844 {
847 $recover_failures{$_} = 1; 845 $recover_failures{$_} = 1;
848 } 846 }
qpdf/qtest/qpdf/bad13-recover.out
1 -WARNING: bad13.pdf: file is damaged  
2 -WARNING: bad13.pdf (trailer, file position 753): unexpected brace token  
3 -WARNING: bad13.pdf: Attempting to reconstruct cross-reference table  
4 -bad13.pdf (trailer, file position 753): unexpected brace token 1 +WARNING: bad13.pdf (trailer, file position 753): treating unexpected brace token as null
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 1 done
qpdf/qtest/qpdf/bad13.out
1 -bad13.pdf (trailer, file position 753): unexpected brace token 1 +WARNING: bad13.pdf (trailer, file position 753): treating unexpected brace token as null
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 0 done
qpdf/qtest/qpdf/bad14-recover.out
1 -WARNING: bad14.pdf: file is damaged  
2 -WARNING: bad14.pdf (trailer, file position 753): unexpected brace token  
3 -WARNING: bad14.pdf: Attempting to reconstruct cross-reference table  
4 -bad14.pdf (trailer, file position 753): unexpected brace token 1 +WARNING: bad14.pdf (trailer, file position 753): treating unexpected brace token as null
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 1 done
qpdf/qtest/qpdf/bad14.out
1 -bad14.pdf (trailer, file position 753): unexpected brace token 1 +WARNING: bad14.pdf (trailer, file position 753): treating unexpected brace token as null
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 0 done
qpdf/qtest/qpdf/bad15-recover.out
1 -WARNING: bad15.pdf: file is damaged  
2 -WARNING: bad15.pdf (trailer, file position 753): unexpected array close token  
3 -WARNING: bad15.pdf: Attempting to reconstruct cross-reference table  
4 -bad15.pdf (trailer, file position 753): unexpected array close token 1 +WARNING: bad15.pdf (trailer, file position 753): treating unexpected array close token as null
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 1 done
qpdf/qtest/qpdf/bad15.out
1 -bad15.pdf (trailer, file position 753): unexpected array close token 1 +WARNING: bad15.pdf (trailer, file position 753): treating unexpected array close token as null
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 0 done
qpdf/qtest/qpdf/bad16-recover.out
1 -WARNING: bad16.pdf: file is damaged  
2 WARNING: bad16.pdf (trailer, file position 753): unexpected dictionary close token 1 WARNING: bad16.pdf (trailer, file position 753): unexpected dictionary close token
  2 +WARNING: bad16.pdf (trailer, file position 756): unexpected dictionary close token
  3 +WARNING: bad16.pdf (trailer, file position 759): unknown token while reading object; treating as string
  4 +WARNING: bad16.pdf: file is damaged
  5 +WARNING: bad16.pdf (trailer, file position 773): EOF while reading token
3 WARNING: bad16.pdf: Attempting to reconstruct cross-reference table 6 WARNING: bad16.pdf: Attempting to reconstruct cross-reference table
4 -bad16.pdf (trailer, file position 753): unexpected dictionary close token 7 +WARNING: bad16.pdf (trailer, file position 753): unexpected dictionary close token
  8 +WARNING: bad16.pdf (trailer, file position 756): unexpected dictionary close token
  9 +WARNING: bad16.pdf (trailer, file position 759): unknown token while reading object; treating as string
  10 +bad16.pdf (trailer, file position 773): EOF while reading token
qpdf/qtest/qpdf/bad16.out
1 -bad16.pdf (trailer, file position 753): unexpected dictionary close token 1 +WARNING: bad16.pdf (trailer, file position 753): unexpected dictionary close token
  2 +WARNING: bad16.pdf (trailer, file position 756): unexpected dictionary close token
  3 +WARNING: bad16.pdf (trailer, file position 759): unknown token while reading object; treating as string
  4 +bad16.pdf (trailer, file position 773): EOF while reading token
qpdf/qtest/qpdf/bad17-recover.out
1 -WARNING: bad17.pdf: file is damaged  
2 -WARNING: bad17.pdf (trailer, file position 753): dictionary ending here has an odd number of elements  
3 -WARNING: bad17.pdf: Attempting to reconstruct cross-reference table  
4 -bad17.pdf (trailer, file position 753): dictionary ending here has an odd number of elements 1 +WARNING: bad17.pdf (trailer, file position 715): dictionary ended prematurely; using null as value for last key
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 1 done
qpdf/qtest/qpdf/bad17.out
1 -bad17.pdf (trailer, file position 753): dictionary ending here has an odd number of elements 1 +WARNING: bad17.pdf (trailer, file position 715): dictionary ended prematurely; using null as value for last key
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 0 done
qpdf/qtest/qpdf/bad36-recover.out 0 → 100644
  1 +WARNING: bad36.pdf (trailer, file position 764): unknown token while reading object; treating as string
  2 +WARNING: bad36.pdf (trailer, file position 715): expected dictionary key but found non-name object; inserting key /QPDFFake2
  3 +WARNING: bad36.pdf (trailer, file position 715): dictionary ended prematurely; using null as value for last key
  4 +/QTest is implicit
  5 +/QTest is direct and has type null (2)
  6 +/QTest is null
  7 +unparse: null
  8 +unparseResolved: null
  9 +test 1 done
qpdf/qtest/qpdf/bad36.out 0 → 100644
  1 +WARNING: bad36.pdf (trailer, file position 764): unknown token while reading object; treating as string
  2 +WARNING: bad36.pdf (trailer, file position 715): expected dictionary key but found non-name object; inserting key /QPDFFake2
  3 +WARNING: bad36.pdf (trailer, file position 715): dictionary ended prematurely; using null as value for last key
  4 +/QTest is implicit
  5 +/QTest is direct and has type null (2)
  6 +/QTest is null
  7 +unparse: null
  8 +unparseResolved: null
  9 +test 0 done
qpdf/qtest/qpdf/bad36.pdf 0 → 100644
  1 +%PDF-1.3
  2 +1 0 obj
  3 +<<
  4 + /Type /Catalog
  5 + /Pages 2 0 R
  6 +>>
  7 +endobj
  8 +
  9 +2 0 obj
  10 +<<
  11 + /Type /Pages
  12 + /Kids [
  13 + 3 0 R
  14 + ]
  15 + /Count 1
  16 +>>
  17 +endobj
  18 +
  19 +3 0 obj
  20 +<<
  21 + /Type /Page
  22 + /Parent 2 0 R
  23 + /MediaBox [0 0 612 792]
  24 + /Contents 4 0 R
  25 + /Resources <<
  26 + /ProcSet 5 0 R
  27 + /Font <<
  28 + /F1 6 0 R
  29 + >>
  30 + >>
  31 +>>
  32 +endobj
  33 +
  34 +4 0 obj
  35 +<<
  36 + /Length 44
  37 +>>
  38 +stream
  39 +BT
  40 + /F1 24 Tf
  41 + 72 720 Td
  42 + (Potato) Tj
  43 +ET
  44 +endstream
  45 +endobj
  46 +
  47 +5 0 obj
  48 +[
  49 + /PDF
  50 + /Text
  51 +]
  52 +endobj
  53 +
  54 +6 0 obj
  55 +<<
  56 + /Type /Font
  57 + /Subtype /Type1
  58 + /Name /F1
  59 + /BaseFont /Helvetica
  60 + /Encoding /WinAnsiEncoding
  61 +>>
  62 +endobj
  63 +
  64 +xref
  65 +0 7
  66 +0000000000 65535 f
  67 +0000000009 00000 n
  68 +0000000063 00000 n
  69 +0000000135 00000 n
  70 +0000000307 00000 n
  71 +0000000403 00000 n
  72 +0000000438 00000 n
  73 +trailer <<
  74 + /Size 7
  75 + /Root 1 0 R
  76 + /QPDFFake1 (potato)
  77 + x /Something
  78 +>>
  79 +startxref
  80 +556
  81 +%%EOF
qpdf/qtest/qpdf/c-read-warnings-and-errors.out deleted
1 -warning: bad17.pdf: file is damaged  
2 - code: 5  
3 - file: bad17.pdf  
4 - pos : 0  
5 - text: file is damaged  
6 -warning: bad17.pdf (trailer, file position 753): dictionary ending here has an odd number of elements  
7 - code: 5  
8 - file: bad17.pdf  
9 - pos : 753  
10 - text: dictionary ending here has an odd number of elements  
11 -warning: bad17.pdf: Attempting to reconstruct cross-reference table  
12 - code: 5  
13 - file: bad17.pdf  
14 - pos : 0  
15 - text: Attempting to reconstruct cross-reference table  
16 -error: bad17.pdf (trailer, file position 753): dictionary ending here has an odd number of elements  
17 - code: 5  
18 - file: bad17.pdf  
19 - pos : 753  
20 - text: dictionary ending here has an odd number of elements  
qpdf/qtest/qpdf/indirect-r-arg.out
1 -indirect-r-arg.pdf (file position 76): unknown token while reading object (R) 1 +WARNING: indirect-r-arg.pdf (file position 76): unknown token while reading object; treating as string
  2 +WARNING: indirect-r-arg.pdf (file position 62): expected dictionary key but found non-name object; inserting key /QPDFFake1
  3 +WARNING: indirect-r-arg.pdf (file position 62): expected dictionary key but found non-name object; inserting key /QPDFFake2
  4 +checking indirect-r-arg.pdf
  5 +PDF Version: 1.3
  6 +File is not encrypted
  7 +File is not linearized
qpdf/qtest/qpdf/issue-100.out
1 WARNING: issue-100.pdf: file is damaged 1 WARNING: issue-100.pdf: file is damaged
2 WARNING: issue-100.pdf (file position 736): xref not found 2 WARNING: issue-100.pdf (file position 736): xref not found
3 WARNING: issue-100.pdf: Attempting to reconstruct cross-reference table 3 WARNING: issue-100.pdf: Attempting to reconstruct cross-reference table
  4 +WARNING: issue-100.pdf (file position 268): unknown token while reading object; treating as string
  5 +WARNING: issue-100.pdf (file position 286): unknown token while reading object; treating as string
  6 +WARNING: issue-100.pdf (file position 289): unknown token while reading object; treating as string
  7 +WARNING: issue-100.pdf (file position 294): unknown token while reading object; treating as string
  8 +WARNING: issue-100.pdf (file position 297): unknown token while reading object; treating as string
  9 +WARNING: issue-100.pdf (file position 304): unknown token while reading object; treating as string
4 WARNING: issue-100.pdf (object 5 0, file position 489): attempting to recover stream length 10 WARNING: issue-100.pdf (object 5 0, file position 489): attempting to recover stream length
5 -issue-100.pdf (object 6 0, file position 59): expected n n obj 11 +WARNING: issue-100.pdf (trailer, file position 953): expected dictionary key but found non-name object; inserting key /QPDFFake1
  12 +WARNING: issue-100.pdf (trailer, file position 953): dictionary ended prematurely; using null as value for last key
  13 +operation for Dictionary object attempted on object of wrong type
qpdf/qtest/qpdf/issue-101.out
1 WARNING: issue-101.pdf: file is damaged 1 WARNING: issue-101.pdf: file is damaged
2 WARNING: issue-101.pdf (file position 3526): xref not found 2 WARNING: issue-101.pdf (file position 3526): xref not found
3 WARNING: issue-101.pdf: Attempting to reconstruct cross-reference table 3 WARNING: issue-101.pdf: Attempting to reconstruct cross-reference table
  4 +WARNING: issue-101.pdf (file position 1242): expected dictionary key but found non-name object; inserting key /QPDFFake1
  5 +WARNING: issue-101.pdf (file position 1242): dictionary ended prematurely; using null as value for last key
4 WARNING: issue-101.pdf (object 5 0, file position 1509): attempting to recover stream length 6 WARNING: issue-101.pdf (object 5 0, file position 1509): attempting to recover stream length
5 -WARNING: issue-101.pdf (object 5 0, file position 2097): attempting to recover stream length  
6 -issue-101.pdf (trailer, file position 2928): unknown token while reading object (ÿ) 7 +WARNING: issue-101.pdf (trailer, file position 2097): attempting to recover stream length
  8 +WARNING: issue-101.pdf (trailer, file position 2928): unknown token while reading object; treating as string
  9 +WARNING: issue-101.pdf (trailer, file position 2930): unknown token while reading object; treating as string
  10 +WARNING: issue-101.pdf (trailer, file position 2928): expected dictionary key but found non-name object; inserting key /QPDFFake1
  11 +WARNING: issue-101.pdf (trailer, file position 2928): expected dictionary key but found non-name object; inserting key /QPDFFake2
  12 +WARNING: issue-101.pdf (trailer, file position 2928): expected dictionary key but found non-name object; inserting key /QPDFFake3
  13 +WARNING: issue-101.pdf (trailer, file position 2996): attempting to recover stream length
  14 +WARNING: issue-101.pdf (trailer, file position 3410): attempting to recover stream length
  15 +WARNING: issue-101.pdf (trailer, file position 3631): attempting to recover stream length
  16 +WARNING: issue-101.pdf (trailer, file position 4184): attempting to recover stream length
  17 +issue-101.pdf (trailer, file position 4184): unable to recover stream data
qpdf/qtest/qpdf/issue-119.out
1 -WARNING: issue-119.pdf (file position 336): loop detected resolving object 4 0  
2 -issue-119.pdf (file position 298): dictionary key is not not a name token 1 +WARNING: issue-119.pdf (file position 298): expected dictionary key but found non-name object; inserting key /QPDFFake1
  2 +WARNING: issue-119.pdf (file position 298): expected dictionary key but found non-name object; inserting key /QPDFFake2
  3 +qpdf: operation succeeded with warnings; resulting file may have some problems