diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 13b66977..4541db64 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -910,8 +910,7 @@ class QPDF } }; - // The ParseGuard class allows QPDFObjectHandle to detect - // re-entrant parsing. + // The ParseGuard class allows QPDFParser to detect re-entrant parsing. class ParseGuard { friend class QPDFParser; @@ -933,7 +932,7 @@ class QPDF QPDF* qpdf; }; - // Pipe class is restricted to QPDF_Stream + // Pipe class is restricted to QPDF_Stream. class Pipe { friend class QPDF_Stream; @@ -961,6 +960,20 @@ class QPDF } }; + // JobSetter class is restricted to QPDFJob. + class JobSetter + { + friend class QPDFJob; + + private: + // Enable enhanced warnings for pdf file checking. + static void + setCheckMode(QPDF& qpdf, bool val) + { + qpdf.m->check_mode = val; + } + }; + // For testing only -- do not add to DLL static bool test_json_validators(); @@ -1698,6 +1711,7 @@ class QPDF bool ignore_xref_streams{false}; bool suppress_warnings{false}; bool attempt_recovery{true}; + bool check_mode{false}; std::shared_ptr encp; std::string pdf_version; std::map xref_table; diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 69508b08..c6f6ae6e 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -2461,6 +2461,13 @@ QPDF::getRoot() QPDFObjectHandle root = this->m->trailer.getKey("/Root"); if (!root.isDictionary()) { throw damagedPDF("", 0, "unable to find /Root dictionary"); + } else if ( + // Check_mode is an interim solution to request #810 pending a more + // comprehensive review of the approach to more extensive checks and + // warning levels. + m->check_mode && !root.getKey("/Type").isNameAndEquals("/Catalog")) { + warn(damagedPDF("", 0, "catalog /Type entry missing or invalid")); + root.replaceKey("/Type", "/Catalog"_qpdf); } return root; } diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index 4916be41..b700378e 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -798,6 +798,7 @@ QPDFJob::doCheck(QPDF& pdf) bool okay = true; auto& cout = *this->m->log->getInfo(); cout << "checking " << m->infilename.get() << "\n"; + QPDF::JobSetter::setCheckMode(pdf, true); try { int extension_level = pdf.getExtensionLevel(); cout << "PDF Version: " << pdf.getPDFVersion(); diff --git a/qpdf/qtest/invalid-objects.test b/qpdf/qtest/invalid-objects.test index 6491ccdb..1ece3810 100644 --- a/qpdf/qtest/invalid-objects.test +++ b/qpdf/qtest/invalid-objects.test @@ -14,7 +14,7 @@ cleanup(); my $td = new TestDriver('invalid-objects'); -my $n_tests = 3; +my $n_tests = 4; $td->runtest("closed input source", {$td->COMMAND => "test_driver 73 minimal.pdf"}, @@ -33,5 +33,10 @@ $td->runtest("object with zero offset", {$td->FILE => "zero-offset.out", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); +$td->runtest("catalog with invalid type entry", + {$td->COMMAND => "qpdf --check catalgg.pdf"}, + {$td->FILE => "catalgg.out", $td->EXIT_STATUS => 3}, + $td->NORMALIZE_NEWLINES); + cleanup(); $td->report($n_tests); diff --git a/qpdf/qtest/qpdf/catalgg.out b/qpdf/qtest/qpdf/catalgg.out new file mode 100644 index 00000000..58fb244c --- /dev/null +++ b/qpdf/qtest/qpdf/catalgg.out @@ -0,0 +1,6 @@ +checking catalgg.pdf +WARNING: catalgg.pdf: catalog /Type entry missing or invalid +PDF Version: 1.3 +File is not encrypted +File is not linearized +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/catalgg.pdf b/qpdf/qtest/qpdf/catalgg.pdf new file mode 100644 index 00000000..7208c4b6 --- /dev/null +++ b/qpdf/qtest/qpdf/catalgg.pdf @@ -0,0 +1,79 @@ +%PDF-1.3 +1 0 obj +<< + /Type /Catalgg + /Pages 2 0 R +>> +endobj + +2 0 obj +<< + /Type /Pages + /Kids [ + 3 0 R + ] + /Count 1 +>> +endobj + +3 0 obj +<< + /Type /Page + /Parent 2 0 R + /MediaBox [0 0 612 792] + /Contents 4 0 R + /Resources << + /ProcSet 5 0 R + /Font << + /F1 6 0 R + >> + >> +>> +endobj + +4 0 obj +<< + /Length 44 +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato) Tj +ET +endstream +endobj + +5 0 obj +[ + /PDF + /Text +] +endobj + +6 0 obj +<< + /Type /Font + /Subtype /Type1 + /Name /F1 + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding +>> +endobj + +xref +0 7 +0000000000 65535 f +0000000009 00000 n +0000000063 00000 n +0000000135 00000 n +0000000307 00000 n +0000000403 00000 n +0000000438 00000 n +trailer << + /Size 7 + /Root 1 0 R +>> +startxref +556 +%%EOF