diff --git a/libqpdf/CMakeLists.txt b/libqpdf/CMakeLists.txt index d8f15ca..1f1580b 100644 --- a/libqpdf/CMakeLists.txt +++ b/libqpdf/CMakeLists.txt @@ -100,6 +100,7 @@ set(libqpdf_SOURCES ResourceFinder.cc SecureRandomDataProvider.cc SF_FlateLzwDecode.cc + global.cc qpdf-c.cc qpdfjob-c.cc qpdflogger-c.cc) diff --git a/libqpdf/QPDFParser.cc b/libqpdf/QPDFParser.cc index d147e60..7b6d855 100644 --- a/libqpdf/QPDFParser.cc +++ b/libqpdf/QPDFParser.cc @@ -15,6 +15,8 @@ using namespace qpdf; using ObjectPtr = std::shared_ptr; +static uint32_t const& max_nesting{global::Limits::objects_max_nesting()}; + // The ParseGuard class allows QPDFParser to detect re-entrant parsing. It also provides // special access to allow the parser to create unresolved objects and dangling references. class QPDF::Doc::ParseGuard @@ -49,16 +51,18 @@ QPDFObjectHandle QPDFParser::parse(InputSource& input, std::string const& object_description, QPDF* context) { qpdf::Tokenizer tokenizer; - bool empty = false; - return QPDFParser( - input, - make_description(input.getName(), object_description), - object_description, - tokenizer, - nullptr, - context, - false) - .parse(empty, false); + if (auto result = QPDFParser( + input, + make_description(input.getName(), object_description), + object_description, + tokenizer, + nullptr, + context, + false) + .parse()) { + return result; + } + return {QPDFObject::create()}; } QPDFObjectHandle @@ -68,19 +72,24 @@ QPDFParser::parse_content( qpdf::Tokenizer& tokenizer, QPDF* context) { - bool empty = false; - return QPDFParser( - input, - std::move(sp_description), - "content", - tokenizer, - nullptr, - context, - true, - 0, - 0, - context && context->doc().reconstructed_xref()) - .parse(empty, true); + static const std::string content("content"); // GCC12 - make constexpr + auto p = QPDFParser( + input, + std::move(sp_description), + content, + tokenizer, + nullptr, + context, + true, + 0, + 0, + context && context->doc().reconstructed_xref()); + auto result = p.parse(true); + if (result || p.empty_) { + // In content stream mode, leave object uninitialized to indicate EOF + return result; + } + return {QPDFObject::create()}; } QPDFObjectHandle @@ -92,18 +101,25 @@ QPDFParser::parse( QPDFObjectHandle::StringDecrypter* decrypter, QPDF* context) { - return QPDFParser( - input, - make_description(input.getName(), object_description), - object_description, - *tokenizer.m, - decrypter, - context, - false) - .parse(empty, false); + // ABI: This parse overload is only used by the deprecated QPDFObjectHandle::parse. It is the + // only user of the 'empty' member. When removing this overload also remove 'empty'. + auto p = QPDFParser( + input, + make_description(input.getName(), object_description), + object_description, + *tokenizer.m, + decrypter, + context, + false); + auto result = p.parse(); + empty = p.empty_; + if (result) { + return result; + } + return {QPDFObject::create()}; } -std::pair +QPDFObjectHandle QPDFParser::parse( InputSource& input, std::string const& object_description, @@ -112,54 +128,65 @@ QPDFParser::parse( QPDF& context, bool sanity_checks) { - bool empty{false}; - auto result = QPDFParser( - input, - make_description(input.getName(), object_description), - object_description, - tokenizer, - decrypter, - &context, - true, - 0, - 0, - sanity_checks) - .parse(empty, false); - return {result, empty}; + return QPDFParser( + input, + make_description(input.getName(), object_description), + object_description, + tokenizer, + decrypter, + &context, + true, + 0, + 0, + sanity_checks) + .parse(); } -std::pair +QPDFObjectHandle QPDFParser::parse( is::OffsetBuffer& input, int stream_id, int obj_id, qpdf::Tokenizer& tokenizer, QPDF& context) { - bool empty{false}; - auto result = QPDFParser( - input, - std::make_shared( - QPDFObject::ObjStreamDescr(stream_id, obj_id)), - "", - tokenizer, - nullptr, - &context, - true, - stream_id, - obj_id) - .parse(empty, false); - return {result, empty}; + return QPDFParser( + input, + std::make_shared( + QPDFObject::ObjStreamDescr(stream_id, obj_id)), + "", + tokenizer, + nullptr, + &context, + true, + stream_id, + obj_id) + .parse(); +} + +QPDFObjectHandle +QPDFParser::parse(bool content_stream) +{ + try { + return parse_first(content_stream); + } catch (Error&) { + return {}; + } catch (QPDFExc& e) { + throw e; + } catch (std::logic_error& e) { + throw e; + } catch (std::exception& e) { + warn("treating object as null because of error during parsing : "s + e.what()); + return {}; + } } QPDFObjectHandle -QPDFParser::parse(bool& empty, bool content_stream) +QPDFParser::parse_first(bool content_stream) { // This method must take care not to resolve any objects. Don't check the type of any object // without first ensuring that it is a direct object. Otherwise, doing so may have the side // effect of reading the object and changing the file pointer. If you do this, it will cause a // logic error to be thrown from QPDF::inParse(). - ParseGuard pg(context); - empty = false; + QPDF::Doc::ParseGuard pg(context); start = input.tell(); - if (!tokenizer.nextToken(input, object_description)) { warn(tokenizer.getErrorMessage()); } @@ -168,31 +195,27 @@ QPDFParser::parse(bool& empty, bool content_stream) case QPDFTokenizer::tt_eof: if (content_stream) { // In content stream mode, leave object uninitialized to indicate EOF + empty_ = true; return {}; } - QTC::TC("qpdf", "QPDFParser eof in parse"); warn("unexpected EOF"); - return {QPDFObject::create()}; + return {}; case QPDFTokenizer::tt_bad: - QTC::TC("qpdf", "QPDFParser bad token in parse"); - return {QPDFObject::create()}; + return {}; case QPDFTokenizer::tt_brace_open: case QPDFTokenizer::tt_brace_close: - QTC::TC("qpdf", "QPDFParser bad brace"); warn("treating unexpected brace token as null"); - return {QPDFObject::create()}; + return {}; case QPDFTokenizer::tt_array_close: - QTC::TC("qpdf", "QPDFParser bad array close"); warn("treating unexpected array close token as null"); - return {QPDFObject::create()}; + return {}; case QPDFTokenizer::tt_dict_close: - QTC::TC("qpdf", "QPDFParser bad dictionary close"); warn("unexpected dictionary close token"); - return {QPDFObject::create()}; + return {}; case QPDFTokenizer::tt_array_open: case QPDFTokenizer::tt_dict_open: @@ -224,13 +247,17 @@ QPDFParser::parse(bool& empty, bool content_stream) if (content_stream) { return withDescription(value); } else if (value == "endobj") { - // We just saw endobj without having read anything. Treat this as a null and do - // not move the input source's offset. + // We just saw endobj without having read anything. Nothing in the PDF spec appears + // to allow empty objects, but they have been encountered in actual PDF files and + // Adobe Reader appears to ignore them. Treat this as a null and do not move the + // input source's offset. + empty_ = true; input.seek(input.getLastOffset(), SEEK_SET); - empty = true; - return {QPDFObject::create()}; + if (!content_stream) { + warn("empty object treated as null"); + } + return {}; } else { - QTC::TC("qpdf", "QPDFParser treat word as string"); warn("unknown token while reading object; treating as string"); return withDescription(value); } @@ -247,7 +274,7 @@ QPDFParser::parse(bool& empty, bool content_stream) default: warn("treating unknown token type as null while reading object"); - return {QPDFObject::create()}; + return {}; } } @@ -283,19 +310,19 @@ QPDFParser::parseRemainder(bool content_stream) } else if ( int_count >= 2 && tokenizer.getType() == QPDFTokenizer::tt_word && tokenizer.getValue() == "R") { - if (context == nullptr) { - QTC::TC("qpdf", "QPDFParser indirect without context"); + if (!context) { throw std::logic_error( - "QPDFParser::parse called without context on an object " - "with indirect references"); + "QPDFParser::parse called without context on an object with indirect " + "references"); } auto id = QIntC::to_int(int_buffer[(int_count - 1) % 2]); auto gen = QIntC::to_int(int_buffer[(int_count) % 2]); if (!(id < 1 || gen < 0 || gen >= 65535)) { add(ParseGuard::getObject(context, id, gen, parse_pdf)); } else { - QTC::TC("qpdf", "QPDFParser invalid objgen"); - addNull(); + add_bad_null( + "treating bad indirect reference (" + std::to_string(id) + " " + + std::to_string(gen) + " R) as null"); } int_count = 0; continue; @@ -317,34 +344,20 @@ QPDFParser::parseRemainder(bool content_stream) // In content stream mode, leave object uninitialized to indicate EOF return {}; } - QTC::TC("qpdf", "QPDFParser eof in parseRemainder"); warn("unexpected EOF"); - return {QPDFObject::create()}; + return {}; case QPDFTokenizer::tt_bad: - QTC::TC("qpdf", "QPDFParser bad token in parseRemainder"); - if (tooManyBadTokens()) { - return {QPDFObject::create()}; - } + check_too_many_bad_tokens(); addNull(); continue; case QPDFTokenizer::tt_brace_open: case QPDFTokenizer::tt_brace_close: - QTC::TC("qpdf", "QPDFParser bad brace in parseRemainder"); - warn("treating unexpected brace token as null"); - if (tooManyBadTokens()) { - return {QPDFObject::create()}; - } - addNull(); + add_bad_null("treating unexpected brace token as null"); continue; case QPDFTokenizer::tt_array_close: - if ((bad_count || sanity_checks) && !max_bad_count) { - // Trigger warning. - (void)tooManyBadTokens(); - return {QPDFObject::create()}; - } if (frame->state == st_array) { auto object = frame->null_count > 100 ? QPDFObject::create(std::move(frame->olist), true) @@ -361,33 +374,22 @@ QPDFParser::parseRemainder(bool content_stream) frame = &stack.back(); add(std::move(object)); } else { - QTC::TC("qpdf", "QPDFParser bad array close in parseRemainder"); if (sanity_checks) { // During sanity checks, assume nesting of containers is corrupt and object is // unusable. warn("unexpected array close token; giving up on reading object"); - return {QPDFObject::create()}; + return {}; } - warn("treating unexpected array close token as null"); - if (tooManyBadTokens()) { - return {QPDFObject::create()}; - } - addNull(); + add_bad_null("treating unexpected array close token as null"); } continue; case QPDFTokenizer::tt_dict_close: - if ((bad_count || sanity_checks) && !max_bad_count) { - // Trigger warning. - (void)tooManyBadTokens(); - return {QPDFObject::create()}; - } if (frame->state <= st_dictionary_value) { // Attempt to recover more or less gracefully from invalid dictionaries. auto& dict = frame->dict; if (frame->state == st_dictionary_value) { - QTC::TC("qpdf", "QPDFParser no val for last key"); warn( frame->offset, "dictionary ended prematurely; using null as value for last key"); @@ -426,22 +428,17 @@ QPDFParser::parseRemainder(bool content_stream) // During sanity checks, assume nesting of containers is corrupt and object is // unusable. warn("unexpected dictionary close token; giving up on reading object"); - return {QPDFObject::create()}; + return {}; } - warn("unexpected dictionary close token"); - if (tooManyBadTokens()) { - return {QPDFObject::create()}; - } - addNull(); + add_bad_null("unexpected dictionary close token"); } continue; case QPDFTokenizer::tt_array_open: case QPDFTokenizer::tt_dict_open: - if (stack.size() > 499) { - QTC::TC("qpdf", "QPDFParser too deep"); + if (stack.size() > max_nesting) { warn("ignoring excessively deeply nested data structure"); - return {QPDFObject::create()}; + return {}; } else { b_contents = false; stack.emplace_back( @@ -499,22 +496,15 @@ QPDFParser::parseRemainder(bool content_stream) warn( "unexpected 'endobj' or 'endstream' while reading object; giving up on " "reading object"); - return {QPDFObject::create()}; + return {}; } - warn("unknown token while reading object; treating as null"); - if (tooManyBadTokens()) { - return {QPDFObject::create()}; - } - addNull(); + add_bad_null("unknown token while reading object; treating as null"); continue; } - QTC::TC("qpdf", "QPDFParser treat word as string in parseRemainder"); warn("unknown token while reading object; treating as string"); - if (tooManyBadTokens()) { - return {QPDFObject::create()}; - } + check_too_many_bad_tokens(); addScalar(tokenizer.getValue()); continue; @@ -538,11 +528,7 @@ QPDFParser::parseRemainder(bool content_stream) continue; default: - warn("treating unknown token type as null while reading object"); - if (tooManyBadTokens()) { - return {QPDFObject::create()}; - } - addNull(); + add_bad_null("treating unknown token type as null while reading object"); } } } @@ -581,6 +567,14 @@ QPDFParser::addNull() } void +QPDFParser::add_bad_null(std::string const& msg) +{ + warn(msg); + check_too_many_bad_tokens(); + addNull(); +} + +void QPDFParser::addInt(int count) { auto obj = QPDFObject::create(int_buffer[count % 2]); @@ -592,12 +586,12 @@ template void QPDFParser::addScalar(Args&&... args) { - if ((bad_count || sanity_checks) && - (frame->olist.size() > 5'000 || frame->dict.size() > 5'000)) { + auto limit = Limits::objects_max_container_size(bad_count || sanity_checks); + if (frame->olist.size() > limit || frame->dict.size() > limit) { // Stop adding scalars. We are going to abort when the close token or a bad token is // encountered. max_bad_count = 0; - return; + check_too_many_bad_tokens(); // always throws Error() } auto obj = QPDFObject::create(std::forward(args)...); obj->setDescription(context, description, input.getLastOffset()); @@ -647,34 +641,34 @@ QPDFParser::fixMissingKeys() } } -bool -QPDFParser::tooManyBadTokens() +void +QPDFParser::check_too_many_bad_tokens() { - if (frame->olist.size() > 5'000 || frame->dict.size() > 5'000) { + auto limit = Limits::objects_max_container_size(bad_count || sanity_checks); + if (frame->olist.size() > limit || frame->dict.size() > limit) { if (bad_count) { warn( - "encountered errors while parsing an array or dictionary with more than 5000 " - "elements; giving up on reading object"); - return true; + "encountered errors while parsing an array or dictionary with more than " + + std::to_string(limit) + " elements; giving up on reading object"); + throw Error(); } warn( - "encountered an array or dictionary with more than 5000 elements during xref recovery; " - "giving up on reading object"); + "encountered an array or dictionary with more than " + std::to_string(limit) + + " elements during xref recovery; giving up on reading object"); } if (max_bad_count && --max_bad_count > 0 && good_count > 4) { good_count = 0; bad_count = 1; - return false; + return; } if (++bad_count > 5 || (frame->state != st_array && QIntC::to_size(max_bad_count) < frame->olist.size())) { // Give up after 5 errors in close proximity or if the number of missing dictionary keys // exceeds the remaining number of allowable total errors. warn("too many errors; giving up on reading object"); - return true; + throw Error(); } good_count = 0; - return false; } void @@ -693,7 +687,6 @@ QPDFParser::warn(QPDFExc const& e) const void QPDFParser::warnDuplicateKey() { - QTC::TC("qpdf", "QPDFParser duplicate dict key"); warn( frame->offset, "dictionary has duplicated key " + frame->key + "; last occurrence overrides earlier ones"); diff --git a/libqpdf/QPDF_objects.cc b/libqpdf/QPDF_objects.cc index fd96a0d..14f6cd0 100644 --- a/libqpdf/QPDF_objects.cc +++ b/libqpdf/QPDF_objects.cc @@ -1233,13 +1233,9 @@ QPDFObjectHandle Objects::readTrailer() { qpdf_offset_t offset = m->file->tell(); - auto [object, empty] = + auto object = QPDFParser::parse(*m->file, "trailer", m->tokenizer, nullptr, qpdf, m->reconstructed_xref); - if (empty) { - // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in - // actual PDF files and Adobe Reader appears to ignore them. - warn(damagedPDF("trailer", "empty object treated as null")); - } else if (object.isDictionary() && m->objects.readToken(*m->file).isWord("stream")) { + if (object.isDictionary() && m->objects.readToken(*m->file).isWord("stream")) { warn(damagedPDF("trailer", m->file->tell(), "stream keyword found in trailer")); } // Override last_offset so that it points to the beginning of the object we just read @@ -1255,19 +1251,15 @@ Objects::readObject(std::string const& description, QPDFObjGen og) StringDecrypter decrypter{&qpdf, og}; StringDecrypter* decrypter_ptr = m->encp->encrypted ? &decrypter : nullptr; - auto [object, empty] = QPDFParser::parse( + auto object = QPDFParser::parse( *m->file, m->last_object_description, m->tokenizer, decrypter_ptr, qpdf, m->reconstructed_xref || m->in_read_xref_stream); - ; - if (empty) { - // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in - // actual PDF files and Adobe Reader appears to ignore them. - warn(damagedPDF(*m->file, m->file->getLastOffset(), "empty object treated as null")); - return object; + if (!object) { + return {}; } auto token = readToken(*m->file); if (object.isDictionary() && token.isWord("stream")) { @@ -1366,24 +1358,6 @@ Objects::validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_off } } -QPDFObjectHandle -Objects::readObjectInStream(is::OffsetBuffer& input, int stream_id, int obj_id) -{ - auto [object, empty] = QPDFParser::parse(input, stream_id, obj_id, m->tokenizer, qpdf); - if (empty) { - // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in - // actual PDF files and Adobe Reader appears to ignore them. - warn(QPDFExc( - qpdf_e_damaged_pdf, - m->file->getName() + " object stream " + std::to_string(stream_id), - +"object " + std::to_string(obj_id) + " 0, offset " + - std::to_string(input.getLastOffset()), - 0, - "empty object treated as null")); - } - return object; -} - bool Objects ::findEndstream() { @@ -1536,25 +1510,25 @@ Objects::readObjectAtOffset( return; } - QPDFObjectHandle oh = readObject(description, og); + if (auto oh = readObject(description, og)) { + // Determine the end offset of this object before and after white space. We use these + // numbers to validate linearization hint tables. Offsets and lengths of objects may imply + // the end of an object to be anywhere between these values. + qpdf_offset_t end_before_space = m->file->tell(); - // Determine the end offset of this object before and after white space. We use these - // numbers to validate linearization hint tables. Offsets and lengths of objects may imply - // the end of an object to be anywhere between these values. - qpdf_offset_t end_before_space = m->file->tell(); - - // skip over spaces - while (true) { - char ch; - if (!m->file->read(&ch, 1)) { - throw damagedPDF(m->file->tell(), "EOF after endobj"); - } - if (!isspace(static_cast(ch))) { - m->file->seek(-1, SEEK_CUR); - break; + // skip over spaces + while (true) { + char ch; + if (!m->file->read(&ch, 1)) { + throw damagedPDF(m->file->tell(), "EOF after endobj"); + } + if (!isspace(static_cast(ch))) { + m->file->seek(-1, SEEK_CUR); + break; + } } + m->objects.updateCache(og, oh.obj_sp(), end_before_space, m->file->tell()); } - m->objects.updateCache(og, oh.obj_sp(), end_before_space, m->file->tell()); } QPDFObjectHandle @@ -1564,7 +1538,7 @@ Objects::readObjectAtOffset( auto og = read_object_start(offset); auto oh = readObject(description, og); - if (!m->objects.isUnresolved(og)) { + if (!oh || !m->objects.isUnresolved(og)) { return oh; } @@ -1804,8 +1778,9 @@ Objects::resolveObjectsInStream(int obj_stream_number) if (entry != m->xref_table.end() && entry->second.getType() == 2 && entry->second.getObjStreamNumber() == obj_stream_number) { is::OffsetBuffer in("", {b_start + obj_offset, obj_size}, obj_offset); - auto oh = readObjectInStream(in, obj_stream_number, obj_id); - updateCache(og, oh.obj_sp(), end_before_space, end_after_space); + if (auto oh = QPDFParser::parse(in, obj_stream_number, obj_id, m->tokenizer, qpdf)) { + updateCache(og, oh.obj_sp(), end_before_space, end_after_space); + } } else { QTC::TC("qpdf", "QPDF not caching overridden objstm object"); } diff --git a/libqpdf/global.cc b/libqpdf/global.cc new file mode 100644 index 0000000..33f778d --- /dev/null +++ b/libqpdf/global.cc @@ -0,0 +1,5 @@ +#include + +using namespace qpdf; + +global::Limits global::Limits::l; diff --git a/libqpdf/qpdf/QPDFObject_private.hh b/libqpdf/qpdf/QPDFObject_private.hh index 43ad341..36f973a 100644 --- a/libqpdf/qpdf/QPDFObject_private.hh +++ b/libqpdf/qpdf/QPDFObject_private.hh @@ -5,11 +5,12 @@ // include/qpdf/QPDFObject.hh. See comments there for an explanation. #include +#include + #include #include #include #include -#include #include #include diff --git a/libqpdf/qpdf/QPDFParser.hh b/libqpdf/qpdf/QPDFParser.hh index bd8d6dd..062b315 100644 --- a/libqpdf/qpdf/QPDFParser.hh +++ b/libqpdf/qpdf/QPDFParser.hh @@ -5,13 +5,24 @@ #include #include #include +#include #include #include +using namespace qpdf; +using namespace qpdf::global; + class QPDFParser { public: + class Error: public std::exception + { + public: + Error() = default; + virtual ~Error() noexcept = default; + }; + static QPDFObjectHandle parse(InputSource& input, std::string const& object_description, QPDF* context); @@ -30,8 +41,8 @@ class QPDFParser QPDFObjectHandle::StringDecrypter* decrypter, QPDF* context); - // For use by QPDF. Return parsed object and whether it is empty. - static std::pair parse( + // For use by QPDF. + static QPDFObjectHandle parse( InputSource& input, std::string const& object_description, qpdf::Tokenizer& tokenizer, @@ -39,7 +50,7 @@ class QPDFParser QPDF& context, bool sanity_checks); - static std::pair parse( + static QPDFObjectHandle parse( qpdf::is::OffsetBuffer& input, int stream_id, int obj_id, @@ -101,14 +112,16 @@ class QPDFParser int null_count{0}; }; - QPDFObjectHandle parse(bool& empty, bool content_stream); + QPDFObjectHandle parse(bool content_stream = false); + QPDFObjectHandle parse_first(bool content_stream); QPDFObjectHandle parseRemainder(bool content_stream); void add(std::shared_ptr&& obj); void addNull(); + void add_bad_null(std::string const& msg); void addInt(int count); template void addScalar(Args&&... args); - bool tooManyBadTokens(); + void check_too_many_bad_tokens(); void warnDuplicateKey(); void fixMissingKeys(); void warn(qpdf_offset_t offset, std::string const& msg) const; @@ -136,7 +149,7 @@ class QPDFParser // it only gets incremented or reset when a bad token is encountered. int bad_count{0}; // Number of bad tokens (remaining) before giving up. - int max_bad_count{15}; + uint32_t max_bad_count{Limits::objects_max_errors()}; // Number of good tokens since last bad token. Irrelevant if bad_count == 0. int good_count{0}; // Start offset including any leading whitespace. @@ -145,6 +158,7 @@ class QPDFParser int int_count{0}; long long int_buffer[2]{0, 0}; qpdf_offset_t last_offset_buffer[2]{0, 0}; + bool empty_{false}; }; #endif // QPDFPARSER_HH diff --git a/libqpdf/qpdf/QPDF_private.hh b/libqpdf/qpdf/QPDF_private.hh index a5738eb..6191836 100644 --- a/libqpdf/qpdf/QPDF_private.hh +++ b/libqpdf/qpdf/QPDF_private.hh @@ -1039,7 +1039,6 @@ class QPDF::Doc::Objects: Common QPDFObjectHandle readObject(std::string const& description, QPDFObjGen og); void readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset); void validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset); - QPDFObjectHandle readObjectInStream(qpdf::is::OffsetBuffer& input, int stream_id, int obj_id); size_t recoverStreamLength( std::shared_ptr input, QPDFObjGen og, qpdf_offset_t stream_offset); diff --git a/libqpdf/qpdf/global_private.hh b/libqpdf/qpdf/global_private.hh new file mode 100644 index 0000000..334f351 --- /dev/null +++ b/libqpdf/qpdf/global_private.hh @@ -0,0 +1,57 @@ + +#ifndef GLOBAL_PRIVATE_HH +#define GLOBAL_PRIVATE_HH + +#include + +#include +#include + +namespace qpdf +{ + namespace global + { + class Limits + { + public: + Limits(Limits const&) = delete; + Limits(Limits&&) = delete; + Limits& operator=(Limits const&) = delete; + Limits& operator=(Limits&&) = delete; + + static uint32_t const& + objects_max_nesting() + { + return l.objects_max_nesting_; + } + + static uint32_t const& + objects_max_errors() + { + return l.objects_max_errors_; + } + + static uint32_t const& + objects_max_container_size(bool damaged) + { + return damaged ? l.objects_max_container_size_damaged_ + : l.objects_max_container_size_; + } + + private: + Limits() = default; + ~Limits() = default; + + static Limits l; + + uint32_t objects_max_nesting_{499}; + uint32_t objects_max_errors_{15}; + uint32_t objects_max_container_size_{std::numeric_limits::max()}; + uint32_t objects_max_container_size_damaged_{5'000}; + }; + + } // namespace global + +} // namespace qpdf + +#endif // GLOBAL_PRIVATE_HH diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 7ef90c4..9749f07 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -27,11 +27,6 @@ main QTest stream 0 QPDF lin write nshared_total > nshared_first_page 1 QPDFWriter encrypted hint stream 0 QPDF xref gen > 0 1 -QPDFParser bad brace 0 -QPDFParser bad brace in parseRemainder 0 -QPDFParser bad array close 0 -QPDFParser bad array close in parseRemainder 0 -QPDFParser bad dictionary close 0 QPDFTokenizer bad ) 0 QPDFTokenizer bad > 0 QPDFTokenizer bad hexstring character 0 @@ -123,7 +118,6 @@ QPDF_Stream provider length not provided 0 QPDF_Stream unknown stream length 0 QPDF replaceReserved 0 QPDFWriter copy use_aes 1 -QPDFParser indirect without context 0 QPDFObjectHandle trailing data in parse 0 QPDFTokenizer EOF reading token 0 QPDFTokenizer EOF reading appendable token 0 @@ -145,11 +139,7 @@ QPDFJob pages range omitted in middle 0 QPDFWriter standard deterministic ID 1 QPDFWriter linearized deterministic ID 1 qpdf-c called qpdf_set_deterministic_ID 0 -QPDFParser invalid objgen 0 -QPDFParser treat word as string 0 -QPDFParser treat word as string in parseRemainder 0 QPDFParser found fake 1 -QPDFParser no val for last key 0 QPDFObjectHandle errors in parsecontent 0 QPDFJob split-pages %d 0 QPDFJob split-pages .pdf 0 @@ -168,10 +158,6 @@ Pl_QPDFTokenizer found ID 0 QPDFObjectHandle coalesce called on stream 0 QPDFObjectHandle coalesce provide stream data 0 QPDF_Stream bad token at end during normalize 0 -QPDFParser bad token in parse 0 -QPDFParser bad token in parseRemainder 0 -QPDFParser eof in parse 0 -QPDFParser eof in parseRemainder 0 QPDFObjectHandle boolean returning false 0 QPDFObjectHandle real returning 0.0 0 QPDFObjectHandle operator returning fake value 0 @@ -189,7 +175,6 @@ QPDFObjectHandle dictionary ignoring replaceKey 0 QPDFObjectHandle numeric non-numeric 0 QPDFObjectHandle erase array bounds 0 qpdf-c called qpdf_check_pdf 0 -QPDFParser too deep 0 QPDFFormFieldObjectHelper TU present 0 QPDFFormFieldObjectHelper TM present 0 QPDFFormFieldObjectHelper TU absent 0 @@ -252,7 +237,6 @@ QPDFJob image optimize bits per component 0 QPDF eof skipping spaces before xref 1 QPDF_encryption user matches owner V < 5 0 QPDF_encryption same password 1 -QPDFParser duplicate dict key 0 QPDFWriter no encryption sig contents 0 QPDFPageObjectHelper colorspace lookup 0 QPDFPageObjectHelper filter form xobject 0 diff --git a/qpdf/qtest/qpdf/issue-100.out b/qpdf/qtest/qpdf/issue-100.out index 8ff6730..ef71e91 100644 --- a/qpdf/qtest/qpdf/issue-100.out +++ b/qpdf/qtest/qpdf/issue-100.out @@ -7,7 +7,6 @@ WARNING: issue-100.pdf (trailer, offset 950): recovered trailer has no /Root ent WARNING: issue-100.pdf (trailer, offset 488): stream keyword found in trailer WARNING: issue-100.pdf (trailer, offset 418): recovered trailer has no /Root entry WARNING: issue-100.pdf (object 1 0, offset 83): unexpected dictionary close token -WARNING: issue-100.pdf (object 1 0, offset 87): expected endobj WARNING: issue-100.pdf (object 5 0, offset 268): unknown token while reading object; treating as null WARNING: issue-100.pdf (object 5 0, offset 286): unknown token while reading object; treating as null WARNING: issue-100.pdf (object 5 0, offset 289): unknown token while reading object; treating as null @@ -15,9 +14,7 @@ WARNING: issue-100.pdf (object 5 0, offset 294): unknown token while reading obj WARNING: issue-100.pdf (object 5 0, offset 297): unknown token while reading object; treating as null WARNING: issue-100.pdf (object 5 0, offset 304): unknown token while reading object; treating as null WARNING: issue-100.pdf (object 5 0, offset 304): too many errors; giving up on reading object -WARNING: issue-100.pdf (object 5 0, offset 308): expected endobj WARNING: issue-100.pdf (object 8 0, offset 107): invalid character ()) in hexstring -WARNING: issue-100.pdf (object 8 0, offset 109): expected endobj WARNING: issue-100.pdf (object 9 0, offset 527): unknown token while reading object; treating as string WARNING: issue-100.pdf (object 9 0, offset 529): expected endobj WARNING: issue-100.pdf (object 10 0, offset 573): expected endobj diff --git a/qpdf/qtest/qpdf/issue-101.out b/qpdf/qtest/qpdf/issue-101.out index d3fb8f2..ae8db9d 100644 --- a/qpdf/qtest/qpdf/issue-101.out +++ b/qpdf/qtest/qpdf/issue-101.out @@ -8,7 +8,6 @@ WARNING: issue-101.pdf (object 11 0, offset 637): unknown token while reading ob WARNING: issue-101.pdf (object 11 0, offset 639): unknown token while reading object; treating as null WARNING: issue-101.pdf (object 11 0, offset 644): unknown token while reading object; treating as null WARNING: issue-101.pdf (object 11 0, offset 644): too many errors; giving up on reading object -WARNING: issue-101.pdf (object 11 0, offset 647): expected endobj WARNING: issue-101.pdf (trailer, offset 4433): recovered trailer has no /Root entry WARNING: issue-101.pdf (trailer, offset 4183): stream keyword found in trailer WARNING: issue-101.pdf (trailer, offset 4113): recovered trailer has no /Root entry @@ -31,6 +30,8 @@ WARNING: issue-101.pdf (trailer, offset 1508): stream keyword found in trailer WARNING: issue-101.pdf (trailer, offset 1438): recovered trailer has no /Root entry WARNING: issue-101.pdf (object 2 0, offset 244): unknown token while reading object; treating as null WARNING: issue-101.pdf (object 2 0, offset 29): dictionary has duplicated key /Parent; last occurrence overrides earlier ones +WARNING: issue-101.pdf (object 2 0, offset 333): treating bad indirect reference (0 0 R) as null +WARNING: issue-101.pdf (object 5 0, offset 1247): treating bad indirect reference (0 0 R) as null WARNING: issue-101.pdf (object 5 0, offset 1242): dictionary ended prematurely; using null as value for last key WARNING: issue-101.pdf (object 5 0, offset 1242): expected dictionary keys but found non-name objects; ignoring WARNING: issue-101.pdf (object 7 0, offset 3855): unknown token while reading object; treating as null @@ -40,9 +41,7 @@ WARNING: issue-101.pdf (object 7 0, offset 3866): unknown token while reading ob WARNING: issue-101.pdf (object 7 0, offset 3873): unknown token while reading object; treating as null WARNING: issue-101.pdf (object 7 0, offset 3879): unknown token while reading object; treating as null WARNING: issue-101.pdf (object 7 0, offset 3879): too many errors; giving up on reading object -WARNING: issue-101.pdf (object 7 0, offset 3888): expected endobj WARNING: issue-101.pdf (object 8 0, offset 4067): invalid character ()) in hexstring -WARNING: issue-101.pdf (object 8 0, offset 4069): expected endobj WARNING: issue-101.pdf (object 9 0, offset 2832): unknown token while reading object; treating as string WARNING: issue-101.pdf (object 9 0, offset 2834): expected endobj qpdf: issue-101.pdf: unable to find trailer dictionary while recovering damaged file diff --git a/qpdf/qtest/qpdf/issue-118.out b/qpdf/qtest/qpdf/issue-118.out index 1dc9df1..f1c9f2c 100644 --- a/qpdf/qtest/qpdf/issue-118.out +++ b/qpdf/qtest/qpdf/issue-118.out @@ -1,4 +1,5 @@ WARNING: issue-118.pdf: can't find PDF header +WARNING: issue-118.pdf (xref stream: object 8 0, offset 720): treating bad indirect reference (0 0 R) as null WARNING: issue-118.pdf (xref stream, offset 732): self-referential object stream 2 WARNING: issue-118.pdf (xref stream, offset 732): object stream id 12336 for object 3 is impossibly large WARNING: issue-118.pdf (xref stream, offset 732): object stream id 12336 for object 4 is impossibly large diff --git a/qpdf/qtest/qpdf/issue-150.out b/qpdf/qtest/qpdf/issue-150.out index 9fe8b5a..765f70d 100644 --- a/qpdf/qtest/qpdf/issue-150.out +++ b/qpdf/qtest/qpdf/issue-150.out @@ -1,6 +1,7 @@ WARNING: issue-150.pdf: can't find PDF header +WARNING: issue-150.pdf (xref stream: object 8 0, offset 56): treating object as null because of error during parsing : overflow/underflow converting 9900000000000000000 to 64-bit integer WARNING: issue-150.pdf: file is damaged -WARNING: issue-150.pdf: error reading xref: overflow/underflow converting 9900000000000000000 to 64-bit integer +WARNING: issue-150.pdf (offset 4): xref not found WARNING: issue-150.pdf: Attempting to reconstruct cross-reference table WARNING: issue-150.pdf (object 8 0): object has offset 0 qpdf: issue-150.pdf: unable to find trailer dictionary while recovering damaged file diff --git a/qpdf/qtest/qpdf/issue-1503.out b/qpdf/qtest/qpdf/issue-1503.out index a57585c..e33a852 100644 --- a/qpdf/qtest/qpdf/issue-1503.out +++ b/qpdf/qtest/qpdf/issue-1503.out @@ -6,11 +6,8 @@ WARNING: issue-1503.pdf (object 31 0, offset 813): unknown token while reading o WARNING: issue-1503.pdf (object 31 0, offset 851): unknown token while reading object; treating as null WARNING: issue-1503.pdf (object 31 0, offset 856): unknown token while reading object; treating as null WARNING: issue-1503.pdf (object 31 0, offset 861): unexpected 'endobj' or 'endstream' while reading object; giving up on reading object -WARNING: issue-1503.pdf (object 31 0, offset 871): expected endobj WARNING: issue-1503.pdf (object 38 0, offset 1126): unexpected 'endobj' or 'endstream' while reading object; giving up on reading object -WARNING: issue-1503.pdf (object 38 0, offset 1133): expected endobj WARNING: issue-1503.pdf (object 40 0, offset 1195): unexpected array close token; giving up on reading object -WARNING: issue-1503.pdf (object 40 0, offset 1198): expected endobj WARNING: issue-1503.pdf (object 41 0, offset 1359): stream dictionary lacks /Length key WARNING: issue-1503.pdf (object 41 0, offset 1411): attempting to recover stream length WARNING: issue-1503.pdf (object 41 0, offset 1411): recovered stream length: 54 @@ -22,9 +19,7 @@ WARNING: issue-1503.pdf (object 44 0, offset 1814): name with stray # will not w WARNING: issue-1503.pdf (object 44 0, offset 1821): unknown token while reading object; treating as null WARNING: issue-1503.pdf (object 44 0, offset 1826): unknown token while reading object; treating as null WARNING: issue-1503.pdf (object 44 0, offset 1826): too many errors; giving up on reading object -WARNING: issue-1503.pdf (object 44 0, offset 1829): expected endobj WARNING: issue-1503.pdf (object 46 0, offset 1923): unexpected array close token; giving up on reading object -WARNING: issue-1503.pdf (object 46 0, offset 1926): expected endobj WARNING: issue-1503.pdf (object 47 0, offset 2087): stream dictionary lacks /Length key WARNING: issue-1503.pdf (object 47 0, offset 2139): attempting to recover stream length WARNING: issue-1503.pdf (object 47 0, offset 2139): recovered stream length: 54 @@ -59,8 +54,6 @@ WARNING: issue-1503.pdf (object 151 0, offset 3836): unknown token while reading WARNING: issue-1503.pdf (object 151 0, offset 3958): unknown token while reading object; treating as null WARNING: issue-1503.pdf (object 152 0, offset 4088): parse error while reading object WARNING: issue-1503.pdf (object 152 0, offset 4088): unexpected EOF -WARNING: issue-1503.pdf (object 152 0, offset 4088): expected endobj -WARNING: issue-1503.pdf (object 152 0, offset 4088): EOF after endobj WARNING: issue-1503.pdf (object 155 0, offset 162): unknown token while reading object; treating as null WARNING: issue-1503.pdf (object 155 0, offset 342): unknown token while reading object; treating as null WARNING: issue-1503.pdf (object 155 0, offset 345): unknown token while reading object; treating as null diff --git a/qpdf/qtest/qpdf/issue-335a.out b/qpdf/qtest/qpdf/issue-335a.out index 6771fe6..33e1c37 100644 --- a/qpdf/qtest/qpdf/issue-335a.out +++ b/qpdf/qtest/qpdf/issue-335a.out @@ -39,12 +39,14 @@ WARNING: issue-335a.pdf (trailer, offset 22844): expected dictionary keys but fo WARNING: issue-335a.pdf (trailer, offset 22880): stream keyword found in trailer WARNING: issue-335a.pdf (trailer, offset 22840): recovered trailer has no /Root entry WARNING: issue-335a.pdf (trailer, offset 22702): unknown token while reading object; treating as null +WARNING: issue-335a.pdf (trailer, offset 22713): treating bad indirect reference (0 0 R) as null WARNING: issue-335a.pdf (trailer, offset 22701): expected dictionary keys but found non-name objects; ignoring WARNING: issue-335a.pdf (trailer, offset 22746): stream keyword found in trailer WARNING: issue-335a.pdf (trailer, offset 22697): recovered trailer has no /Root entry WARNING: issue-335a.pdf (trailer, offset 22687): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 22690): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 22702): unknown token while reading object; treating as null +WARNING: issue-335a.pdf (trailer, offset 22713): treating bad indirect reference (0 0 R) as null WARNING: issue-335a.pdf (trailer, offset 22701): expected dictionary keys but found non-name objects; ignoring WARNING: issue-335a.pdf (trailer, offset 22740): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 22748): unknown token while reading object; treating as null @@ -58,6 +60,7 @@ WARNING: issue-335a.pdf (trailer, offset 22675): unknown token while reading obj WARNING: issue-335a.pdf (trailer, offset 22687): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 22690): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 22702): unknown token while reading object; treating as null +WARNING: issue-335a.pdf (trailer, offset 22713): treating bad indirect reference (0 0 R) as null WARNING: issue-335a.pdf (trailer, offset 22701): expected dictionary keys but found non-name objects; ignoring WARNING: issue-335a.pdf (trailer, offset 22740): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 22748): unknown token while reading object; treating as null @@ -66,6 +69,8 @@ WARNING: issue-335a.pdf (trailer, offset 22791): unknown token while reading obj WARNING: issue-335a.pdf (trailer, offset 22794): unexpected > WARNING: issue-335a.pdf (trailer, offset 22794): too many errors; giving up on reading object WARNING: issue-335a.pdf (trailer, offset 22437): unknown token while reading object; treating as null +WARNING: issue-335a.pdf (trailer, offset 22448): treating bad indirect reference (0 0 R) as null +WARNING: issue-335a.pdf (trailer, offset 22471): treating bad indirect reference (20 -1 R) as null WARNING: issue-335a.pdf (trailer, offset 22436): expected dictionary keys but found non-name objects; ignoring WARNING: issue-335a.pdf (trailer, offset 22482): stream keyword found in trailer WARNING: issue-335a.pdf (trailer, offset 22432): recovered trailer has no /Root entry @@ -98,27 +103,30 @@ WARNING: issue-335a.pdf (trailer, offset 22134): stream keyword found in trailer WARNING: issue-335a.pdf (trailer, offset 22083): recovered trailer has no /Root entry WARNING: issue-335a.pdf (trailer, offset 22000): invalid character (t) in hexstring WARNING: issue-335a.pdf (trailer, offset 21937): unknown token while reading object; treating as null +WARNING: issue-335a.pdf (trailer, offset 21948): treating bad indirect reference (0 0 R) as null WARNING: issue-335a.pdf (trailer, offset 21962): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 21991): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 22000): invalid character (t) in hexstring WARNING: issue-335a.pdf (trailer, offset 22003): unknown token while reading object; treating as null +WARNING: issue-335a.pdf (trailer, offset 22026): treating bad indirect reference (-4 0 R) as null WARNING: issue-335a.pdf (trailer, offset 21936): dictionary has duplicated key /Length; last occurrence overrides earlier ones WARNING: issue-335a.pdf (trailer, offset 22028): unexpected > WARNING: issue-335a.pdf (trailer, offset 22030): unknown token while reading object; treating as null -WARNING: issue-335a.pdf (trailer, offset 22038): unknown token while reading object; treating as null -WARNING: issue-335a.pdf (trailer, offset 22038): too many errors; giving up on reading object +WARNING: issue-335a.pdf (trailer, offset 22030): too many errors; giving up on reading object WARNING: issue-335a.pdf (trailer, offset 21918): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 21925): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 21937): unknown token while reading object; treating as null +WARNING: issue-335a.pdf (trailer, offset 21948): treating bad indirect reference (0 0 R) as null WARNING: issue-335a.pdf (trailer, offset 21962): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 21991): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 22000): invalid character (t) in hexstring WARNING: issue-335a.pdf (trailer, offset 22003): unknown token while reading object; treating as null +WARNING: issue-335a.pdf (trailer, offset 22026): treating bad indirect reference (-4 0 R) as null WARNING: issue-335a.pdf (trailer, offset 21936): dictionary has duplicated key /Length; last occurrence overrides earlier ones WARNING: issue-335a.pdf (trailer, offset 22028): unexpected > -WARNING: issue-335a.pdf (trailer, offset 22030): unknown token while reading object; treating as null -WARNING: issue-335a.pdf (trailer, offset 22030): too many errors; giving up on reading object +WARNING: issue-335a.pdf (trailer, offset 22028): too many errors; giving up on reading object WARNING: issue-335a.pdf (trailer, offset 21837): unknown token while reading object; treating as null +WARNING: issue-335a.pdf (trailer, offset 21861): treating bad indirect reference (0 0 R) as null WARNING: issue-335a.pdf (trailer, offset 21850): expected dictionary keys but found non-name objects; ignoring WARNING: issue-335a.pdf (trailer, offset 21892): unknown token while reading object; treating as null WARNING: issue-335a.pdf (trailer, offset 21900): unknown token while reading object; treating as null diff --git a/qpdf/qtest/qpdf/issue-51.out b/qpdf/qtest/qpdf/issue-51.out index 2777ab6..eecb96f 100644 --- a/qpdf/qtest/qpdf/issue-51.out +++ b/qpdf/qtest/qpdf/issue-51.out @@ -1,5 +1,6 @@ WARNING: issue-51.pdf: can't find PDF header WARNING: issue-51.pdf: reported number of objects (0) is not one plus the highest object number (8) +WARNING: issue-51.pdf (object 7 0, offset 500): treating bad indirect reference (0 0 R) as null WARNING: issue-51.pdf (object 7 0, offset 476): dictionary has duplicated key /0000; last occurrence overrides earlier ones WARNING: issue-51.pdf (object 7 0, offset 553): expected endobj issue-51.pdf: unable to find page tree diff --git a/qpdf/qtest/qpdf/issue-99.out b/qpdf/qtest/qpdf/issue-99.out index 5cc37c7..a9a454e 100644 --- a/qpdf/qtest/qpdf/issue-99.out +++ b/qpdf/qtest/qpdf/issue-99.out @@ -1,6 +1,7 @@ WARNING: issue-99.pdf: file is damaged WARNING: issue-99.pdf (offset 3526): xref not found WARNING: issue-99.pdf: Attempting to reconstruct cross-reference table +WARNING: issue-99.pdf (trailer, offset 4635): treating bad indirect reference (0 0 R) as null WARNING: issue-99.pdf (trailer, offset 4613): recovered trailer has no /Root entry WARNING: issue-99.pdf (object 1 0, offset 775): unknown token while reading object; treating as null WARNING: issue-99.pdf (object 1 0, offset 795): unknown token while reading object; treating as null @@ -8,15 +9,13 @@ WARNING: issue-99.pdf (object 1 0, offset 815): unknown token while reading obje WARNING: issue-99.pdf (object 1 0, offset 835): unknown token while reading object; treating as null WARNING: issue-99.pdf (object 1 0, offset 855): unknown token while reading object; treating as null WARNING: issue-99.pdf (object 1 0, offset 855): too many errors; giving up on reading object -WARNING: issue-99.pdf (object 1 0, offset 858): expected endobj WARNING: issue-99.pdf (object 2 0, offset 64): expected endobj WARNING: issue-99.pdf (object 5 0, offset 2452): unknown token while reading object; treating as string WARNING: issue-99.pdf (object 6 0, offset 2506): unexpected array close token; giving up on reading object -WARNING: issue-99.pdf (object 6 0, offset 2507): expected endobj +WARNING: issue-99.pdf (object 8 0, offset 4281): treating bad indirect reference (0 0 R) as null WARNING: issue-99.pdf (object 10 0, offset 3708): expected dictionary keys but found non-name objects; ignoring WARNING: issue-99.pdf (object 11 0, offset 4485): unknown token while reading object; treating as null WARNING: issue-99.pdf (object 11 0, offset 4497): unexpected array close token; giving up on reading object -WARNING: issue-99.pdf (object 11 0, offset 4499): expected endobj WARNING: issue-99.pdf: unable to find trailer dictionary while recovering damaged file WARNING: object 1 0: Pages tree includes non-dictionary object; ignoring WARNING: object 1 0: operation for dictionary attempted on object of type null: ignoring key replacement request diff --git a/qpdf/qtest/qpdf/parse-object.out b/qpdf/qtest/qpdf/parse-object.out index 47843fd..16efee5 100644 --- a/qpdf/qtest/qpdf/parse-object.out +++ b/qpdf/qtest/qpdf/parse-object.out @@ -6,6 +6,9 @@ WARNING: parsed object: treating unexpected brace token as null WARNING: parsed object: treating unexpected brace token as null WARNING: parsed object: unexpected dictionary close token WARNING: bad-parse.qdf (object 7 0, offset 1121): unexpected EOF -WARNING: bad-parse.qdf (object 7 0, offset 1121): expected endobj -WARNING: bad-parse.qdf (object 7 0, offset 1121): EOF after endobj +WARNING: parsed object (offset 5): treating bad indirect reference (0 0 R) as null +WARNING: parsed object (offset 12): treating bad indirect reference (-1 0 R) as null +WARNING: parsed object (offset 22): treating bad indirect reference (1 65535 R) as null +WARNING: parsed object (offset 33): treating bad indirect reference (1 100000 R) as null +WARNING: parsed object (offset 40): treating bad indirect reference (1 -1 R) as null test 31 done