From d0e99f195a987c483bbb6c5449cf39bee34e08a1 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Fri, 16 Feb 2018 17:25:27 -0500 Subject: [PATCH] More robust handling of type errors Give objects descriptions and context so it is possible to issue warnings instead of fatal errors for attempts to access objects of the wrong type. --- ChangeLog | 10 + TODO | 9 - include/qpdf/QPDF.hh | 1 - include/qpdf/QPDFObject.hh | 21 + include/qpdf/QPDFObjectHandle.hh | 32 +- ispell-words | 50 +++ libqpdf/QPDF.cc | 53 +-- libqpdf/QPDFExc.cc | 10 +- libqpdf/QPDFObject.cc | 35 ++ libqpdf/QPDFObjectHandle.cc | 484 +++++++++++++++++---- libqpdf/QPDFTokenizer.cc | 9 +- libqpdf/QPDF_Array.cc | 7 + libqpdf/QPDF_Dictionary.cc | 19 +- libqpdf/QPDF_Stream.cc | 31 ++ libqpdf/QPDF_linearization.cc | 8 +- libqpdf/qpdf/QPDF_Array.hh | 1 + libqpdf/qpdf/QPDF_Dictionary.hh | 3 +- libqpdf/qpdf/QPDF_Stream.hh | 3 + qpdf/qpdf.testcov | 30 +- qpdf/qtest/qpdf.test | 33 +- qpdf/qtest/qpdf/bad16-recover.out | 8 +- qpdf/qtest/qpdf/bad16.out | 4 +- qpdf/qtest/qpdf/bad18-recover.out | 9 +- qpdf/qtest/qpdf/bad18.out | 8 +- qpdf/qtest/qpdf/bad19-recover.out | 9 +- qpdf/qtest/qpdf/bad19.out | 8 +- qpdf/qtest/qpdf/bad20-recover.out | 13 +- qpdf/qtest/qpdf/bad20.out | 12 +- qpdf/qtest/qpdf/bad21-recover.out | 11 +- qpdf/qtest/qpdf/bad21.out | 10 +- qpdf/qtest/qpdf/bad29-recover.out | 11 +- qpdf/qtest/qpdf/bad29.out | 10 +- qpdf/qtest/qpdf/issue-100.out | 3 + qpdf/qtest/qpdf/issue-101.out | 70 ++- qpdf/qtest/qpdf/issue-146.out | 4 +- qpdf/qtest/qpdf/issue-51.out | 3 +- qpdf/qtest/qpdf/linearization-bounds-1.out | 2 +- qpdf/qtest/qpdf/object-types-os.out | 41 ++ qpdf/qtest/qpdf/object-types-os.pdf | Bin 0 -> 865 bytes qpdf/qtest/qpdf/object-types.out | 41 ++ qpdf/qtest/qpdf/object-types.pdf | 111 +++++ qpdf/test_driver.cc | 60 +++ 42 files changed, 1133 insertions(+), 164 deletions(-) create mode 100644 qpdf/qtest/qpdf/object-types-os.out create mode 100644 qpdf/qtest/qpdf/object-types-os.pdf create mode 100644 qpdf/qtest/qpdf/object-types.out create mode 100644 qpdf/qtest/qpdf/object-types.pdf diff --git a/ChangeLog b/ChangeLog index 0d6ab5ac..1d551461 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,15 @@ 2018-02-17 Jay Berkenbilt + * Major enhancements to handling of type errors within the qpdf + library. This fix is intended to eliminate those annoying cases + where qpdf would exit with a message like "operation for + dictionary object attemped on object of wrong type" without + providing any context. Now qpdf keeps enough context to be able to + issue a proper warning and to handle such conditions in a sensible + way. This should greatly increase the number of bad files that + qpdf can recover, and it should make it much easier to figure out + what's broken when a file contains errors. + * Error message fix: replace "file position" with "offset" in error messages that report lexical or parsing errors. Sometimes it's an offset in an object stream or a content stream rather than diff --git a/TODO b/TODO index 402a715f..6a27acb0 100644 --- a/TODO +++ b/TODO @@ -1,15 +1,6 @@ Soon ==== - * Consider whether there should be a mode in which QPDFObjectHandle - returns nulls for operations on the wrong type instead of asserting - the type. The way things are wired up now, this would have to be a - global flag. Probably it makes sense to make that be the default - behavior and to add a static method in QPDFObjectHandle and - command-line flag that enables the stricter behavior globally for - easier debugging. For cases where we have enough information to do - so, we could still warn when not in strict mode. - * Add method to push inheritable resources to a single page by walking up and copying without overwrite. Above logic will also be sufficient to fix the limitation in diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 70bfac3e..7da150f1 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -703,7 +703,6 @@ class QPDF PointerHolder input, int objid, int generation, qpdf_offset_t stream_offset); QPDFTokenizer::Token readToken(PointerHolder, - bool allow_bad = false, size_t max_len = 0); QPDFObjectHandle readObjectAtOffset( diff --git a/include/qpdf/QPDFObject.hh b/include/qpdf/QPDFObject.hh index 8d479b3c..da54c027 100644 --- a/include/qpdf/QPDFObject.hh +++ b/include/qpdf/QPDFObject.hh @@ -23,6 +23,7 @@ #define __QPDFOBJECT_HH__ #include +#include #include @@ -32,6 +33,7 @@ class QPDFObjectHandle; class QPDFObject { public: + QPDFObject(); // Objects derived from QPDFObject are accessible through // QPDFObjectHandle. Each object returns a unique type code that @@ -84,8 +86,27 @@ class QPDFObject }; friend class ObjAccessor; + virtual void setDescription(QPDF*, std::string const&); + bool getDescription(QPDF*&, std::string&); + bool hasDescription(); + protected: virtual void releaseResolved() {} + + private: + QPDFObject(QPDFObject const&); + QPDFObject& operator=(QPDFObject const&); + class Members + { + friend class QPDFObject; + public: + ~Members(); + private: + Members(); + QPDF* owning_qpdf; + std::string object_description; + }; + PointerHolder m; }; #endif // __QPDFOBJECT_HH__ diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index d12fe87d..53b219ce 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -398,6 +398,21 @@ class QPDFObjectHandle QPDF_DLL static QPDFObjectHandle newReserved(QPDF* qpdf); + // Provide an owning qpdf and object description. The library does + // this automatically with objects that are read from from the + // input PDF and with objects that are created programmatically + // and inserted into the QPDF by adding them to an array or a + // dictionary or creating a new indirect object. Most end user + // code will not need to call this. If an object has an owning + // qpdf and object description, it enables qpdf to give warnings + // with proper context in some cases where it would otherwise + // raise exceptions. + QPDF_DLL + void setObjectDescription(QPDF* owning_qpdf, + std::string const& object_description); + QPDF_DLL + bool hasObjectDescription(); + // Accessor methods. If an accessor method that is valid for only // a particular object type is called on an object of the wrong // type, an exception is thrown. @@ -498,7 +513,7 @@ class QPDFObjectHandle // Replace value of key, adding it if it does not exist QPDF_DLL - void replaceKey(std::string const& key, QPDFObjectHandle const&); + void replaceKey(std::string const& key, QPDFObjectHandle); // Remove key, doing nothing if key does not exist QPDF_DLL void removeKey(std::string const& key); @@ -769,7 +784,10 @@ class QPDFObjectHandle }; friend class ReleaseResolver; - // Convenience routine: Throws if the assumption is violated. + // Convenience routine: Throws if the assumption is violated. Your + // code will be better if you call one of the isType methods and + // handle the case of the type being wrong, but these can be + // convenient if you have already verified the type. QPDF_DLL void assertInitialized() const; @@ -832,10 +850,16 @@ class QPDFObjectHandle QPDF* qpdf, int objid, int generation, QPDFObjectHandle stream_dict, qpdf_offset_t offset, size_t length); - void assertType(char const* type_name, bool istype) const; + void typeWarning(char const* expected_type, + std::string const& warning); + void objectWarning(std::string const& warning); + void assertType(char const* type_name, bool istype); void dereference(); void makeDirectInternal(std::set& visited); void releaseResolved(); + static void setObjectDescriptionFromInput( + QPDFObjectHandle, QPDF*, std::string const&, + PointerHolder, qpdf_offset_t); static QPDFObjectHandle parseInternal( PointerHolder input, std::string const& object_description, @@ -868,7 +892,7 @@ class QPDFObjectHandle bool initialized; - QPDF* qpdf; // 0 for direct object + QPDF* qpdf; int objid; // 0 for direct object int generation; PointerHolder obj; diff --git a/ispell-words b/ispell-words index 82461656..1db7bdd9 100644 --- a/ispell-words +++ b/ispell-words @@ -23,9 +23,11 @@ activatePipelineStack ActiveState acyclic adbe +addContentTokenFilter addPage addPageAt addPageContents +addTokenFilter addToTable adjustAESStreamLength admon @@ -54,6 +56,7 @@ allowPoundAnywhereInName allowPrintHighRes allowPrintLowRes antivirus +anyBadTokens aobjid apexcovantage api @@ -71,11 +74,13 @@ argv arko arko's Arora +arrayOrStreamToStreamArray ArtBox ascii asciiHex ASCIIHexDecode ASCIIHexDecoder +asMap assertArray assertBool assertDictionary @@ -214,12 +219,15 @@ closeObject cmath cmd cmyk +coalesceContentStreams +CoalesceProvider codepage codepoint col Coldwind ColorSpace colorspace +ColorToGray com compareVersions compatbility @@ -230,6 +238,7 @@ Cond config conftest const +ContentNormalizer contrib CopiedStreamDataProvider copyEncryptionParameters @@ -300,6 +309,7 @@ deflateEnd deflateInit defq delphi +deque dereference dereferenced dest @@ -411,6 +421,7 @@ esize exc exe exp +expectInlineImage ExtensionLevel extern fb @@ -439,6 +450,7 @@ Filespec FILETIME filetrailer filterCompressedObjects +filterPageContents findAndSkipNextEOL findAttachmentStreams findEndstream @@ -509,6 +521,7 @@ getCompressibleObjects getCompressibleObjGens getCount getDataChecksum +getDescription getDict getDictAsMap getEncryptionKey @@ -562,6 +575,7 @@ getOE getOffset getOffsetLength getOperatorValue +getOriginalID getOwningQPDF getP getPaddedUserPassword @@ -626,7 +640,10 @@ handleCode handleData handleEOF handleObject +handleToken +hasDescription hasKey +hasObjectDescription hb hbp HCRYPTPROV @@ -668,6 +685,7 @@ ifeq iff ifndef ifstream +ignorable ijg Im ImageC @@ -676,6 +694,7 @@ ImageInverter ImageMask ImageProvider inbuf +includeIgnorable INDOC indx inf @@ -720,9 +739,12 @@ iostream irdp isArray isBool +isDataModified +isDelimiter isDictionary isdigit isEncrypted +isIgnorable isIndirect isInitialized isInlineImage @@ -731,6 +753,7 @@ isLinearized isName isNull isNumber +isNumeric iso isOperator isOrHasName @@ -741,10 +764,12 @@ isReal isReserved isScalar isspace +isSpace isStream isString istream istype +isType italicseq itemizedlist iter @@ -772,6 +797,7 @@ keyset LARGEFILE lastnum lastreleased +lastTokenWasBad latin lbuf lc @@ -839,6 +865,7 @@ malloc manualFinish Mateusz maxEnd +maxlen maxval md mdash @@ -933,6 +960,7 @@ NTE ntotal NUL num +numericValue numrange nval nwalsh @@ -1007,6 +1035,7 @@ parms parsecontent parseContentStream parseInternal +parsePageContents ParserCallbacks parseVersion partLen @@ -1033,6 +1062,9 @@ persistAcrossFinish ph phe php +pipeContentStreams +PipelineAccessor +pipePageContents pipeStreamData pipeStringAndFinish Pkey @@ -1072,6 +1104,7 @@ procset ProcSet procsets programlisting +programmatically Projet prov provideRandomData @@ -1106,6 +1139,7 @@ QPDF's QPDFCONSTANTS QPDFExc QPDFFake +QPDFFakeName QPDFObject QPDFObjectHandle QPDFObjectHandle's @@ -1170,6 +1204,7 @@ ReleaseResolver remotesensing removeKey removePage +removereplace repl replaceDict replaceFilterData @@ -1191,6 +1226,7 @@ retested reverseResolved rf rfont +rg rgb rhs rijndael @@ -1240,7 +1276,9 @@ setCompressStreams setContentNormalization setDataKey setDecodeLevel +setDescription setDeterministicID +setDictDescription setEncryptionParameters setEncryptionParametersInternal setExtraHeaderText @@ -1254,11 +1292,14 @@ setjmp setLastObjectDescription setLastOffset setLinearization +setLinearizationPass setLineBuf setMinimumPDFVersion setmode setNewlineBeforeEndstream setO +setObjectDescription +setObjectDescriptionFromInput setObjectStreamMode setObjGen setOutputFile @@ -1268,6 +1309,7 @@ setOutputPipeline setOutputStreams setPasswordIsHexKey setPCLm +setPipeline setprecision setPreserveEncryption setPreserveUnreferencedObjects @@ -1277,6 +1319,7 @@ setRandomDataProvider setStaticAesIV setStaticID setStreamDataMode +setStreamDescription setSuppressOriginalObjectIDs setSuppressWarnings setTrailer @@ -1329,8 +1372,10 @@ StreamDataProvider strerror StrF stricmp +StringCounter StringDecrypter stringprep +StringReverser stripesize strlen strncmp @@ -1385,10 +1430,13 @@ tobj tobjid TODO toffset +TokenFilter +TokenFilters tokenize tokenized tokenizer tokenizing +tokenTypeName toolchain Toolchains toupper @@ -1400,6 +1448,7 @@ trimTrailerForWrite tt turbo txt +typeWarning uc udata UE @@ -1428,6 +1477,7 @@ unparse unparseChild unparseObject unparseResolved +unparsing unreadCh unreferenced unresolvable diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 31c8d8e2..31f13118 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -106,6 +106,7 @@ QPDF::Members::~Members() QPDF::QPDF() : m(new Members()) { + m->tokenizer.allowEOF(); } QPDF::~QPDF() @@ -272,10 +273,10 @@ QPDF::findHeader() bool QPDF::findStartxref() { - QPDFTokenizer::Token t = readToken(this->m->file, true); + QPDFTokenizer::Token t = readToken(this->m->file); if (t == QPDFTokenizer::Token(QPDFTokenizer::tt_word, "startxref")) { - t = readToken(this->m->file, true); + t = readToken(this->m->file); if (t.getType() == QPDFTokenizer::tt_integer) { // Position in front of offset token @@ -421,7 +422,7 @@ QPDF::reconstruct_xref(QPDFExc& e) this->m->file->findAndSkipNextEOL(); qpdf_offset_t next_line_start = this->m->file->tell(); this->m->file->seek(line_start, SEEK_SET); - QPDFTokenizer::Token t1 = readToken(this->m->file, true, MAX_LEN); + QPDFTokenizer::Token t1 = readToken(this->m->file, MAX_LEN); qpdf_offset_t token_start = this->m->file->tell() - t1.getValue().length(); if (token_start >= next_line_start) @@ -440,9 +441,9 @@ QPDF::reconstruct_xref(QPDFExc& e) if (t1.getType() == QPDFTokenizer::tt_integer) { QPDFTokenizer::Token t2 = - readToken(this->m->file, true, MAX_LEN); + readToken(this->m->file, MAX_LEN); QPDFTokenizer::Token t3 = - readToken(this->m->file, true, MAX_LEN); + readToken(this->m->file, MAX_LEN); if ((t2.getType() == QPDFTokenizer::tt_integer) && (t3 == QPDFTokenizer::Token(QPDFTokenizer::tt_word, "obj"))) { @@ -1429,7 +1430,7 @@ bool QPDF::findEndstream() { // Find endstream or endobj. Position the input at that token. - QPDFTokenizer::Token t = readToken(this->m->file, true, 20); + QPDFTokenizer::Token t = readToken(this->m->file, 20); if ((t.getType() == QPDFTokenizer::tt_word) && ((t.getValue() == "endobj") || (t.getValue() == "endstream"))) @@ -1522,11 +1523,10 @@ QPDF::recoverStreamLength(PointerHolder input, } QPDFTokenizer::Token -QPDF::readToken(PointerHolder input, - bool allow_bad, size_t max_len) +QPDF::readToken(PointerHolder input, size_t max_len) { return this->m->tokenizer.readToken( - input, this->m->last_object_description, allow_bad, max_len); + input, this->m->last_object_description, true, max_len); } QPDFObjectHandle @@ -1730,16 +1730,10 @@ QPDF::resolve(int objid, int generation) } ResolveRecorder rr(this, og); - if (! this->m->obj_cache.count(og)) + // PDF spec says unknown objects resolve to the null object. + if ((! this->m->obj_cache.count(og)) && this->m->xref_table.count(og)) { - if (! this->m->xref_table.count(og)) - { - // PDF spec says unknown objects resolve to the null object. - return new QPDF_Null; - } - QPDFXRefEntry const& entry = this->m->xref_table[og]; - bool success = false; try { switch (entry.getType()) @@ -1768,7 +1762,6 @@ QPDF::resolve(int objid, int generation) QUtil::int_to_string(generation) + " has unexpected xref entry type"); } - success = true; } catch (QPDFExc& e) { @@ -1782,16 +1775,24 @@ QPDF::resolve(int objid, int generation) QUtil::int_to_string(generation) + ": error reading object: " + e.what())); } - if (! success) - { - QTC::TC("qpdf", "QPDF resolve failure to null"); - QPDFObjectHandle oh = QPDFObjectHandle::newNull(); - this->m->obj_cache[og] = - ObjCache(QPDFObjectHandle::ObjAccessor::getObject(oh), -1, -1); - } + } + if (this->m->obj_cache.count(og) == 0) + { + QTC::TC("qpdf", "QPDF resolve failure to null"); + QPDFObjectHandle oh = QPDFObjectHandle::newNull(); + this->m->obj_cache[og] = + ObjCache(QPDFObjectHandle::ObjAccessor::getObject(oh), -1, -1); } - return this->m->obj_cache[og].object; + PointerHolder result(this->m->obj_cache[og].object); + if (! result->hasDescription()) + { + result->setDescription( + this, + "object " + QUtil::int_to_string(objid) + " " + + QUtil::int_to_string(generation)); + } + return result; } void diff --git a/libqpdf/QPDFExc.cc b/libqpdf/QPDFExc.cc index 728d4ce8..b816e913 100644 --- a/libqpdf/QPDFExc.cc +++ b/libqpdf/QPDFExc.cc @@ -32,7 +32,10 @@ QPDFExc::createWhat(std::string const& filename, } if (! (object.empty() && offset == 0)) { - result += " ("; + if (! filename.empty()) + { + result += " ("; + } if (! object.empty()) { result += object; @@ -45,7 +48,10 @@ QPDFExc::createWhat(std::string const& filename, { result += "offset " + QUtil::int_to_string(offset); } - result += ")"; + if (! filename.empty()) + { + result += ")"; + } } if (! result.empty()) { diff --git a/libqpdf/QPDFObject.cc b/libqpdf/QPDFObject.cc index 8df2b480..cffb8a56 100644 --- a/libqpdf/QPDFObject.cc +++ b/libqpdf/QPDFObject.cc @@ -1 +1,36 @@ #include + +QPDFObject::Members::Members() : + owning_qpdf(0) +{ +} + +QPDFObject::Members::~Members() +{ +} + +QPDFObject::QPDFObject() : + m(new Members) +{ +} + +void +QPDFObject::setDescription(QPDF* qpdf, std::string const& description) +{ + this->m->owning_qpdf = qpdf; + this->m->object_description = description; +} + +bool +QPDFObject::getDescription(QPDF*& qpdf, std::string& description) +{ + qpdf = this->m->owning_qpdf; + description = this->m->object_description; + return this->m->owning_qpdf; +} + +bool +QPDFObject::hasDescription() +{ + return this->m->owning_qpdf; +} diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index d48461bf..2e9cc996 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -190,6 +190,18 @@ QPDFObjectHandle::releaseResolved() } } +void +QPDFObjectHandle::setObjectDescriptionFromInput( + QPDFObjectHandle object, QPDF* context, + std::string const& description, PointerHolder input, + qpdf_offset_t offset) +{ + object.setObjectDescription( + context, + input->getName() + ", " + description + + " at offset " + QUtil::int_to_string(offset)); +} + bool QPDFObjectHandle::isInitialized() const { @@ -282,7 +294,8 @@ QPDFObjectHandle::getNumericValue() } else { - throw std::logic_error("getNumericValue called for non-numeric object"); + typeWarning("number", "returning 0"); + QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric"); } return result; } @@ -363,8 +376,16 @@ QPDFObjectHandle::isScalar() bool QPDFObjectHandle::getBoolValue() { - assertBool(); - return dynamic_cast(m->obj.getPointer())->getVal(); + if (isBool()) + { + return dynamic_cast(m->obj.getPointer())->getVal(); + } + else + { + typeWarning("boolean", "returning false"); + QTC::TC("qpdf", "QPDFObjectHandle boolean returning false"); + return false; + } } // Integer accessors @@ -372,8 +393,16 @@ QPDFObjectHandle::getBoolValue() long long QPDFObjectHandle::getIntValue() { - assertInteger(); - return dynamic_cast(m->obj.getPointer())->getVal(); + if (isInteger()) + { + return dynamic_cast(m->obj.getPointer())->getVal(); + } + else + { + typeWarning("integer", "returning 0"); + QTC::TC("qpdf", "QPDFObjectHandle integer returning 0"); + return 0; + } } // Real accessors @@ -381,8 +410,16 @@ QPDFObjectHandle::getIntValue() std::string QPDFObjectHandle::getRealValue() { - assertReal(); - return dynamic_cast(m->obj.getPointer())->getVal(); + if (isReal()) + { + return dynamic_cast(m->obj.getPointer())->getVal(); + } + else + { + typeWarning("real", "returning 0.0"); + QTC::TC("qpdf", "QPDFObjectHandle real returning 0.0"); + return "0.0"; + } } // Name accessors @@ -390,8 +427,16 @@ QPDFObjectHandle::getRealValue() std::string QPDFObjectHandle::getName() { - assertName(); - return dynamic_cast(m->obj.getPointer())->getName(); + if (isName()) + { + return dynamic_cast(m->obj.getPointer())->getName(); + } + else + { + typeWarning("name", "returning dummy name"); + QTC::TC("qpdf", "QPDFObjectHandle name returning dummy name"); + return "/QPDFFakeName"; + } } // String accessors @@ -399,15 +444,31 @@ QPDFObjectHandle::getName() std::string QPDFObjectHandle::getStringValue() { - assertString(); - return dynamic_cast(m->obj.getPointer())->getVal(); + if (isString()) + { + return dynamic_cast(m->obj.getPointer())->getVal(); + } + else + { + typeWarning("string", "returning empty string"); + QTC::TC("qpdf", "QPDFObjectHandle string returning empty string"); + return ""; + } } std::string QPDFObjectHandle::getUTF8Value() { - assertString(); - return dynamic_cast(m->obj.getPointer())->getUTF8Val(); + if (isString()) + { + return dynamic_cast(m->obj.getPointer())->getUTF8Val(); + } + else + { + typeWarning("string", "returning empty string"); + QTC::TC("qpdf", "QPDFObjectHandle string returning empty utf8"); + return ""; + } } // Operator and Inline Image accessors @@ -415,15 +476,31 @@ QPDFObjectHandle::getUTF8Value() std::string QPDFObjectHandle::getOperatorValue() { - assertOperator(); - return dynamic_cast(m->obj.getPointer())->getVal(); + if (isOperator()) + { + return dynamic_cast(m->obj.getPointer())->getVal(); + } + else + { + typeWarning("operator", "returning fake value"); + QTC::TC("qpdf", "QPDFObjectHandle operator returning fake value"); + return "QPDFFAKE"; + } } std::string QPDFObjectHandle::getInlineImageValue() { - assertInlineImage(); - return dynamic_cast(m->obj.getPointer())->getVal(); + if (isInlineImage()) + { + return dynamic_cast(m->obj.getPointer())->getVal(); + } + else + { + typeWarning("inlineimage", "returning empty data"); + QTC::TC("qpdf", "QPDFObjectHandle inlineimage returning empty data"); + return ""; + } } // Array accessors @@ -431,22 +508,66 @@ QPDFObjectHandle::getInlineImageValue() int QPDFObjectHandle::getArrayNItems() { - assertArray(); - return dynamic_cast(m->obj.getPointer())->getNItems(); + if (isArray()) + { + return dynamic_cast(m->obj.getPointer())->getNItems(); + } + else + { + typeWarning("array", "treating as empty"); + QTC::TC("qpdf", "QPDFObjectHandle array treating as empty"); + return 0; + } } QPDFObjectHandle QPDFObjectHandle::getArrayItem(int n) { - assertArray(); - return dynamic_cast(m->obj.getPointer())->getItem(n); + QPDFObjectHandle result; + if (isArray() && (n < getArrayNItems()) && (n >= 0)) + { + result = dynamic_cast(m->obj.getPointer())->getItem(n); + } + else + { + result = newNull(); + if (isArray()) + { + objectWarning("returning null for out of bounds array access"); + QTC::TC("qpdf", "QPDFObjectHandle array bounds"); + } + else + { + typeWarning("array", "returning null"); + QTC::TC("qpdf", "QPDFObjectHandle array null for non-array"); + } + QPDF* context = 0; + std::string description; + if (this->m->obj->getDescription(context, description)) + { + result.setObjectDescription( + context, + description + + " -> null returned from invalid array access"); + } + } + return result; } std::vector QPDFObjectHandle::getArrayAsVector() { - assertArray(); - return dynamic_cast(m->obj.getPointer())->getAsVector(); + std::vector result; + if (isArray()) + { + result = dynamic_cast(m->obj.getPointer())->getAsVector(); + } + else + { + typeWarning("array", "treating as empty"); + QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector"); + } + return result; } // Array mutators @@ -454,36 +575,79 @@ QPDFObjectHandle::getArrayAsVector() void QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item) { - assertArray(); - return dynamic_cast(m->obj.getPointer())->setItem(n, item); + if (isArray()) + { + dynamic_cast(m->obj.getPointer())->setItem(n, item); + } + else + { + typeWarning("array", "ignoring attempt to set item"); + QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item"); + } } void QPDFObjectHandle::setArrayFromVector(std::vector const& items) { - assertArray(); - return dynamic_cast(m->obj.getPointer())->setFromVector(items); + if (isArray()) + { + dynamic_cast(m->obj.getPointer())->setFromVector(items); + } + else + { + typeWarning("array", "ignoring attempt to replace items"); + QTC::TC("qpdf", "QPDFObjectHandle array ignoring replace items"); + } } void QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item) { - assertArray(); - return dynamic_cast(m->obj.getPointer())->insertItem(at, item); + if (isArray()) + { + dynamic_cast(m->obj.getPointer())->insertItem(at, item); + } + else + { + typeWarning("array", "ignoring attempt to insert item"); + QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item"); + } } void QPDFObjectHandle::appendItem(QPDFObjectHandle const& item) { - assertArray(); - return dynamic_cast(m->obj.getPointer())->appendItem(item); + if (isArray()) + { + dynamic_cast(m->obj.getPointer())->appendItem(item); + } + else + { + typeWarning("array", "ignoring attempt to append item"); + QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item"); + } } void QPDFObjectHandle::eraseItem(int at) { - assertArray(); - return dynamic_cast(m->obj.getPointer())->eraseItem(at); + if (isArray() && (at < getArrayNItems()) && (at >= 0)) + { + dynamic_cast(m->obj.getPointer())->eraseItem(at); + } + else + { + if (isArray()) + { + objectWarning("ignoring attempt to erase out of bounds array item"); + QTC::TC("qpdf", "QPDFObjectHandle erase array bounds"); + } + else + { + typeWarning("array", "ignoring attempt to erase item"); + QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item"); + } + } } // Dictionary accessors @@ -491,29 +655,79 @@ QPDFObjectHandle::eraseItem(int at) bool QPDFObjectHandle::hasKey(std::string const& key) { - assertDictionary(); - return dynamic_cast(m->obj.getPointer())->hasKey(key); + if (isDictionary()) + { + return dynamic_cast(m->obj.getPointer())->hasKey(key); + } + else + { + typeWarning("dictionary", + "returning false for a key containment request"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary false for hasKey"); + return false; + } } QPDFObjectHandle QPDFObjectHandle::getKey(std::string const& key) { - assertDictionary(); - return dynamic_cast(m->obj.getPointer())->getKey(key); + QPDFObjectHandle result; + if (isDictionary()) + { + result = dynamic_cast( + m->obj.getPointer())->getKey(key); + } + else + { + typeWarning( + "dictionary", "returning null for attempted key retrieval"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary null for getKey"); + result = newNull(); + QPDF* qpdf = 0; + std::string description; + if (this->m->obj->getDescription(qpdf, description)) + { + result.setObjectDescription( + qpdf, + description + + " -> null returned from getting key " + + key + " from non-Dictionary"); + } + } + return result; } std::set QPDFObjectHandle::getKeys() { - assertDictionary(); - return dynamic_cast(m->obj.getPointer())->getKeys(); + std::set result; + if (isDictionary()) + { + result = dynamic_cast(m->obj.getPointer())->getKeys(); + } + else + { + typeWarning("dictionary", "treating as empty"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary empty set for getKeys"); + } + return result; } std::map QPDFObjectHandle::getDictAsMap() { - assertDictionary(); - return dynamic_cast(m->obj.getPointer())->getAsMap(); + std::map result; + if (isDictionary()) + { + result = dynamic_cast( + m->obj.getPointer())->getAsMap(); + } + else + { + typeWarning("dictionary", "treating as empty"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary empty map for asMap"); + } + return result; } // Array and Name accessors @@ -551,27 +765,48 @@ QPDFObjectHandle::getOwningQPDF() void QPDFObjectHandle::replaceKey(std::string const& key, - QPDFObjectHandle const& value) + QPDFObjectHandle value) { - assertDictionary(); - return dynamic_cast( - m->obj.getPointer())->replaceKey(key, value); + if (isDictionary()) + { + dynamic_cast( + m->obj.getPointer())->replaceKey(key, value); + } + else + { + typeWarning("dictionary", "ignoring key replacement request"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring replaceKey"); + } } void QPDFObjectHandle::removeKey(std::string const& key) { - assertDictionary(); - return dynamic_cast(m->obj.getPointer())->removeKey(key); + if (isDictionary()) + { + dynamic_cast(m->obj.getPointer())->removeKey(key); + } + else + { + typeWarning("dictionary", "ignoring key removal request"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removeKey"); + } } void QPDFObjectHandle::replaceOrRemoveKey(std::string const& key, QPDFObjectHandle value) { - assertDictionary(); - return dynamic_cast( - m->obj.getPointer())->replaceOrRemoveKey(key, value); + if (isDictionary()) + { + dynamic_cast( + m->obj.getPointer())->replaceOrRemoveKey(key, value); + } + else + { + typeWarning("dictionary", "ignoring key removal/replacement request"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removereplace"); + } } // Stream accessors @@ -1173,35 +1408,45 @@ QPDFObjectHandle::parseInternal(PointerHolder input, std::vector state_stack; state_stack.push_back(st_top); std::vector offset_stack; - offset_stack.push_back(input->tell()); + qpdf_offset_t offset = input->tell(); + offset_stack.push_back(offset); bool done = false; while (! done) { std::vector& olist = olist_stack.back(); parser_state_e state = state_stack.back(); - qpdf_offset_t offset = offset_stack.back(); + offset = offset_stack.back(); object = QPDFObjectHandle(); QPDFTokenizer::Token token = - tokenizer.readToken(input, object_description); + tokenizer.readToken(input, object_description, true); switch (token.getType()) { case QPDFTokenizer::tt_eof: - if (content_stream) + if (! content_stream) { - state = st_eof; - } - else - { - // When not in content stream mode, EOF is tt_bad and - // throws an exception before we get here. - throw std::logic_error( - "EOF received while not in content stream mode"); + QTC::TC("qpdf", "QPDFObjectHandle eof in parseInternal"); + warn(context, + QPDFExc(qpdf_e_damaged_pdf, input->getName(), + object_description, + input->getLastOffset(), + "unexpected EOF")); } + state = st_eof; break; + case QPDFTokenizer::tt_bad: + QTC::TC("qpdf", "QPDFObjectHandle bad token in parse"); + warn(context, + QPDFExc(qpdf_e_damaged_pdf, input->getName(), + object_description, + input->getLastOffset(), + token.getErrorMessage())); + object = newNull(); + break; + case QPDFTokenizer::tt_brace_open: case QPDFTokenizer::tt_brace_close: QTC::TC("qpdf", "QPDFObjectHandle bad brace"); @@ -1375,11 +1620,19 @@ QPDFObjectHandle::parseInternal(PointerHolder input, "parse error while reading object")); } done = true; - // Leave object uninitialized to indicate EOF + // In content stream mode, leave object uninitialized to + // indicate EOF + if (! content_stream) + { + object = newNull(); + } break; case st_dictionary: case st_array: + setObjectDescriptionFromInput( + object, context, object_description, input, + input->getLastOffset()); olist.push_back(object); break; @@ -1402,6 +1655,8 @@ QPDFObjectHandle::parseInternal(PointerHolder input, if (old_state == st_array) { object = newArray(olist); + setObjectDescriptionFromInput( + object, context, object_description, input, offset); } else if (old_state == st_dictionary) { @@ -1458,6 +1713,8 @@ QPDFObjectHandle::parseInternal(PointerHolder input, "dictionary ended prematurely; " "using null as value for last key")); val = newNull(); + setObjectDescriptionFromInput( + val, context, object_description, input, offset); } else { @@ -1466,6 +1723,8 @@ QPDFObjectHandle::parseInternal(PointerHolder input, dict[key_obj.getName()] = val; } object = newDictionary(dict); + setObjectDescriptionFromInput( + object, context, object_description, input, offset); } olist_stack.pop_back(); offset_stack.pop_back(); @@ -1480,6 +1739,8 @@ QPDFObjectHandle::parseInternal(PointerHolder input, } } + setObjectDescriptionFromInput( + object, context, object_description, input, offset); return object; } @@ -1635,6 +1896,26 @@ QPDFObjectHandle::newReserved(QPDF* qpdf) return result; } +void +QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, + std::string const& object_description) +{ + if (isInitialized() && this->m->obj.getPointer()) + { + this->m->obj->setDescription(owning_qpdf, object_description); + } +} + +bool +QPDFObjectHandle::hasObjectDescription() +{ + if (isInitialized() && this->m->obj.getPointer()) + { + return this->m->obj->hasDescription(); + } + return false; +} + QPDFObjectHandle QPDFObjectHandle::shallowCopy() { @@ -1793,85 +2074,127 @@ QPDFObjectHandle::assertInitialized() const } void -QPDFObjectHandle::assertType(char const* type_name, bool istype) const +QPDFObjectHandle::typeWarning(char const* expected_type, + std::string const& warning) +{ + QPDF* context = 0; + std::string description; + if (this->m->obj->getDescription(context, description)) + { + warn(context, + QPDFExc( + qpdf_e_damaged_pdf, + "", description, 0, + std::string("operation for ") + expected_type + + " attempted on object of type " + + getTypeName() + ": " + warning)); + } + else + { + assertType(expected_type, false); + } +} + +void +QPDFObjectHandle::objectWarning(std::string const& warning) +{ + QPDF* context = 0; + std::string description; + if (this->m->obj->getDescription(context, description)) + { + warn(context, + QPDFExc( + qpdf_e_damaged_pdf, + "", description, 0, + warning)); + } + else + { + throw std::logic_error(warning); + } +} + +void +QPDFObjectHandle::assertType(char const* type_name, bool istype) { if (! istype) { throw std::logic_error(std::string("operation for ") + type_name + - " object attempted on object of wrong type"); + " attempted on object of type " + + getTypeName()); } } void QPDFObjectHandle::assertNull() { - assertType("Null", isNull()); + assertType("null", isNull()); } void QPDFObjectHandle::assertBool() { - assertType("Boolean", isBool()); + assertType("boolean", isBool()); } void QPDFObjectHandle::assertInteger() { - assertType("Integer", isInteger()); + assertType("integer", isInteger()); } void QPDFObjectHandle::assertReal() { - assertType("Real", isReal()); + assertType("real", isReal()); } void QPDFObjectHandle::assertName() { - assertType("Name", isName()); + assertType("name", isName()); } void QPDFObjectHandle::assertString() { - assertType("String", isString()); + assertType("string", isString()); } void QPDFObjectHandle::assertOperator() { - assertType("Operator", isOperator()); + assertType("operator", isOperator()); } void QPDFObjectHandle::assertInlineImage() { - assertType("InlineImage", isInlineImage()); + assertType("inlineimage", isInlineImage()); } void QPDFObjectHandle::assertArray() { - assertType("Array", isArray()); + assertType("array", isArray()); } void QPDFObjectHandle::assertDictionary() { - assertType("Dictionary", isDictionary()); + assertType("dictionary", isDictionary()); } void QPDFObjectHandle::assertStream() { - assertType("Stream", isStream()); + assertType("stream", isStream()); } void QPDFObjectHandle::assertReserved() { - assertType("Reserved", isReserved()); + assertType("reserved", isReserved()); } void @@ -1887,13 +2210,13 @@ QPDFObjectHandle::assertIndirect() void QPDFObjectHandle::assertScalar() { - assertType("Scalar", isScalar()); + assertType("scalar", isScalar()); } void QPDFObjectHandle::assertNumber() { - assertType("Number", isNumber()); + assertType("number", isNumber()); } bool @@ -1928,7 +2251,8 @@ QPDFObjectHandle::dereference() this->m->qpdf, this->m->objid, this->m->generation); if (obj.getPointer() == 0) { - QTC::TC("qpdf", "QPDFObjectHandle indirect to unknown"); + // QPDF::resolve never returns an uninitialized object, but + // check just in case. this->m->obj = new QPDF_Null(); } else if (dynamic_cast(obj.getPointer())) diff --git a/libqpdf/QPDFTokenizer.cc b/libqpdf/QPDFTokenizer.cc index c3a017d0..95551e7c 100644 --- a/libqpdf/QPDFTokenizer.cc +++ b/libqpdf/QPDFTokenizer.cc @@ -640,7 +640,9 @@ QPDFTokenizer::readToken(PointerHolder input, presented_eof = true; if ((this->m->type == tt_eof) && (! this->m->allow_eof)) { - QTC::TC("qpdf", "QPDFTokenizer EOF when not allowed"); + // Nothing in the qpdf library calls readToken + // without allowEOF anymore, so this case is not + // exercised. this->m->type = tt_bad; this->m->error_message = "unexpected EOF"; offset = input->getLastOffset(); @@ -677,7 +679,10 @@ QPDFTokenizer::readToken(PointerHolder input, input->unreadCh(char_to_unread); } - input->setLastOffset(offset); + if (token.getType() != tt_eof) + { + input->setLastOffset(offset); + } if (token.getType() == tt_bad) { diff --git a/libqpdf/QPDF_Array.cc b/libqpdf/QPDF_Array.cc index c526174f..1a4ba61d 100644 --- a/libqpdf/QPDF_Array.cc +++ b/libqpdf/QPDF_Array.cc @@ -1,4 +1,5 @@ #include +#include #include QPDF_Array::QPDF_Array(std::vector const& items) : @@ -46,6 +47,12 @@ QPDF_Array::getTypeName() const return "array"; } +void +QPDF_Array::setDescription(QPDF* qpdf, std::string const& description) +{ + this->QPDFObject::setDescription(qpdf, description); +} + int QPDF_Array::getNItems() const { diff --git a/libqpdf/QPDF_Dictionary.cc b/libqpdf/QPDF_Dictionary.cc index 0af2f4bf..df640354 100644 --- a/libqpdf/QPDF_Dictionary.cc +++ b/libqpdf/QPDF_Dictionary.cc @@ -51,6 +51,12 @@ QPDF_Dictionary::getTypeName() const return "dictionary"; } +void +QPDF_Dictionary::setDescription(QPDF* qpdf, std::string const& description) +{ + this->QPDFObject::setDescription(qpdf, description); +} + bool QPDF_Dictionary::hasKey(std::string const& key) { @@ -70,7 +76,15 @@ QPDF_Dictionary::getKey(std::string const& key) } else { - return QPDFObjectHandle::newNull(); + QPDFObjectHandle null = QPDFObjectHandle::newNull(); + QPDF* qpdf = 0; + std::string description; + if (getDescription(qpdf, description)) + { + null.setObjectDescription( + qpdf, description + " -> dictionary key " + key); + } + return null; } } @@ -93,13 +107,12 @@ QPDF_Dictionary::getKeys() std::map const& QPDF_Dictionary::getAsMap() const { - return this->items; } void QPDF_Dictionary::replaceKey(std::string const& key, - QPDFObjectHandle const& value) + QPDFObjectHandle value) { // add or replace value this->items[key] = value; diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc index 7b84d10c..384652e2 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -39,6 +39,7 @@ QPDF_Stream::QPDF_Stream(QPDF* qpdf, int objid, int generation, "stream object instantiated with non-dictionary " "object for dictionary"); } + setStreamDescription(); } QPDF_Stream::~QPDF_Stream() @@ -85,6 +86,35 @@ QPDF_Stream::getTypeName() const return "stream"; } +void +QPDF_Stream::setDescription(QPDF* qpdf, std::string const& description) +{ + this->QPDFObject::setDescription(qpdf, description); + setDictDescription(); +} + +void +QPDF_Stream::setStreamDescription() +{ + setDescription( + this->qpdf, + "stream object " + QUtil::int_to_string(this->objid) + " " + + QUtil::int_to_string(this->generation)); +} + +void +QPDF_Stream::setDictDescription() +{ + QPDF* qpdf = 0; + std::string description; + if ((! this->stream_dict.hasObjectDescription()) && + getDescription(qpdf, description)) + { + this->stream_dict.setObjectDescription( + qpdf, description + " -> stream dictionary"); + } +} + QPDFObjectHandle QPDF_Stream::getDict() const { @@ -688,6 +718,7 @@ void QPDF_Stream::replaceDict(QPDFObjectHandle new_dict) { this->stream_dict = new_dict; + setDictDescription(); QPDFObjectHandle length_obj = new_dict.getKey("/Length"); if (length_obj.isInteger()) { diff --git a/libqpdf/QPDF_linearization.cc b/libqpdf/QPDF_linearization.cc index 3d04ab90..ecf81bee 100644 --- a/libqpdf/QPDF_linearization.cc +++ b/libqpdf/QPDF_linearization.cc @@ -121,10 +121,10 @@ QPDF::isLinearized() ++p; } - QPDFTokenizer::Token t1 = readToken(this->m->file, true); - QPDFTokenizer::Token t2 = readToken(this->m->file, true); - QPDFTokenizer::Token t3 = readToken(this->m->file, true); - QPDFTokenizer::Token t4 = readToken(this->m->file, true); + QPDFTokenizer::Token t1 = readToken(this->m->file); + QPDFTokenizer::Token t2 = readToken(this->m->file); + QPDFTokenizer::Token t3 = readToken(this->m->file); + QPDFTokenizer::Token t4 = readToken(this->m->file); if ((t1.getType() == QPDFTokenizer::tt_integer) && (t2.getType() == QPDFTokenizer::tt_integer) && (t3 == QPDFTokenizer::Token(QPDFTokenizer::tt_word, "obj")) && diff --git a/libqpdf/qpdf/QPDF_Array.hh b/libqpdf/qpdf/QPDF_Array.hh index e81f8664..8a23da35 100644 --- a/libqpdf/qpdf/QPDF_Array.hh +++ b/libqpdf/qpdf/QPDF_Array.hh @@ -14,6 +14,7 @@ class QPDF_Array: public QPDFObject virtual std::string unparse(); virtual QPDFObject::object_type_e getTypeCode() const; virtual char const* getTypeName() const; + virtual void setDescription(QPDF*, std::string const&); int getNItems() const; QPDFObjectHandle getItem(int n) const; diff --git a/libqpdf/qpdf/QPDF_Dictionary.hh b/libqpdf/qpdf/QPDF_Dictionary.hh index 5b5630cf..cea63835 100644 --- a/libqpdf/qpdf/QPDF_Dictionary.hh +++ b/libqpdf/qpdf/QPDF_Dictionary.hh @@ -16,6 +16,7 @@ class QPDF_Dictionary: public QPDFObject virtual std::string unparse(); virtual QPDFObject::object_type_e getTypeCode() const; virtual char const* getTypeName() const; + virtual void setDescription(QPDF*, std::string const&); // hasKey() and getKeys() treat keys with null values as if they // aren't there. getKey() returns null for the value of a @@ -26,7 +27,7 @@ class QPDF_Dictionary: public QPDFObject std::map const& getAsMap() const; // Replace value of key, adding it if it does not exist - void replaceKey(std::string const& key, QPDFObjectHandle const&); + void replaceKey(std::string const& key, QPDFObjectHandle); // Remove key, doing nothing if key does not exist void removeKey(std::string const& key); // If object is null, replace key; otherwise, remove key diff --git a/libqpdf/qpdf/QPDF_Stream.hh b/libqpdf/qpdf/QPDF_Stream.hh index 86b796cf..98b8c11f 100644 --- a/libqpdf/qpdf/QPDF_Stream.hh +++ b/libqpdf/qpdf/QPDF_Stream.hh @@ -19,6 +19,7 @@ class QPDF_Stream: public QPDFObject virtual std::string unparse(); virtual QPDFObject::object_type_e getTypeCode() const; virtual char const* getTypeName() const; + virtual void setDescription(QPDF*, std::string const&); QPDFObjectHandle getDict() const; bool isDataModified() const; @@ -66,6 +67,8 @@ class QPDF_Stream: public QPDFObject int& colors, int& bits_per_component, bool& early_code_change); void warn(QPDFExc const& e); + void setDictDescription(); + void setStreamDescription(); QPDF* qpdf; int objid; diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 2c51867f..67cbfe7f 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -105,7 +105,6 @@ QPDF reconstructed xref table 0 QPDF recovered in readObjectAtOffset 0 QPDF recovered stream length 0 QPDF found wrong endstream in recovery 0 -QPDFObjectHandle indirect to unknown 0 QPDF_Stream pipeStreamData with null pipeline 0 QPDFWriter not recompressing /FlateDecode 0 QPDF_encryption xref stream from encrypted file 0 @@ -300,10 +299,37 @@ qpdf-c called qpdf_set_compress_streams 0 qpdf-c called qpdf_set_preserve_unreferenced_objects 0 qpdf-c called qpdf_set_newline_before_endstream 0 QPDF_Stream TIFF predictor 0 -QPDFTokenizer EOF when not allowed 0 QPDFTokenizer inline image at EOF 0 Pl_QPDFTokenizer found ID 0 QPDFObjectHandle non-stream in stream array 0 QPDFObjectHandle coalesce called on stream 0 QPDFObjectHandle coalesce provide stream data 0 QPDF_Stream bad token at end during normalize 0 +QPDFObjectHandle bad token in parse 0 +QPDFObjectHandle eof in parseInternal 0 +QPDFObjectHandle array bounds 0 +QPDFObjectHandle boolean returning false 0 +QPDFObjectHandle integer returning 0 0 +QPDFObjectHandle real returning 0.0 0 +QPDFObjectHandle name returning dummy name 0 +QPDFObjectHandle string returning empty string 0 +QPDFObjectHandle string returning empty utf8 0 +QPDFObjectHandle operator returning fake value 0 +QPDFObjectHandle inlineimage returning empty data 0 +QPDFObjectHandle array treating as empty 0 +QPDFObjectHandle array null for non-array 0 +QPDFObjectHandle array treating as empty vector 0 +QPDFObjectHandle array ignoring set item 0 +QPDFObjectHandle array ignoring replace items 0 +QPDFObjectHandle array ignoring insert item 0 +QPDFObjectHandle array ignoring append item 0 +QPDFObjectHandle array ignoring erase item 0 +QPDFObjectHandle dictionary false for hasKey 0 +QPDFObjectHandle dictionary null for getKey 0 +QPDFObjectHandle dictionary empty set for getKeys 0 +QPDFObjectHandle dictionary empty map for asMap 0 +QPDFObjectHandle dictionary ignoring replaceKey 0 +QPDFObjectHandle dictionary ignoring removeKey 0 +QPDFObjectHandle dictionary ignoring removereplace 0 +QPDFObjectHandle numeric non-numeric 0 +QPDFObjectHandle erase array bounds 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 45c750fd..b5939703 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -212,7 +212,7 @@ my @bug_tests = ( ["99", "object 0", 2], ["99b", "object 0", 2], ["100", "xref reconstruction loop", 2], - ["101", "resolve for exception text", 2], + ["101", "resolve for exception text", 3], ["117", "other infinite loop", 2], ["118", "other infinite loop", 2], ["119", "other infinite loop", 3], @@ -735,6 +735,33 @@ $td->runtest("stream with tiff predictor", $td->NORMALIZE_NEWLINES); show_ntests(); +# ---------- +$td->notify("--- Type checks ---"); +$n_tests += 4; +# Whenever object-types.pdf is edited, object-types-os.pdf should be +# regenerated. +$td->runtest("ensure object-types-os is up-to-date", + {$td->COMMAND => + "qpdf" . + " --object-streams=generate" . + " --deterministic-id" . + " --stream-data=uncompress" . + " object-types.pdf a.pdf"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}); +$td->runtest("check file", + {$td->FILE => "a.pdf"}, + {$td->FILE => "object-types-os.pdf"}); +$td->runtest("type checks", + {$td->COMMAND => "test_driver 42 object-types.pdf"}, + {$td->FILE => "object-types.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("type checks with object streams", + {$td->COMMAND => "test_driver 42 object-types-os.pdf"}, + {$td->FILE => "object-types-os.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + # ---------- $td->notify("--- Coalesce contents ---"); $n_tests += 6; @@ -1200,7 +1227,7 @@ $n_tests += @badfiles + 3; # have error conditions that used to be fatal but are now considered # non-fatal. my %badtest_overrides = (); -for(6, 12..15, 17, 22..28, 30..32, 34, 36) +for(6, 12..15, 17, 18..32, 34, 36) { $badtest_overrides{$_} = 0; } @@ -1243,7 +1270,7 @@ $n_tests += @badfiles + 6; # though in some cases it may. Acrobat Reader would not be able to # recover any of these files any better. my %recover_failures = (); -for (1, 7, 16, 18..21, 29, 35) +for (1, 7, 16, 35) { $recover_failures{$_} = 1; } diff --git a/qpdf/qtest/qpdf/bad16-recover.out b/qpdf/qtest/qpdf/bad16-recover.out index 5ed231d8..adddb4f7 100644 --- a/qpdf/qtest/qpdf/bad16-recover.out +++ b/qpdf/qtest/qpdf/bad16-recover.out @@ -1,10 +1,14 @@ WARNING: bad16.pdf (trailer, offset 753): unexpected dictionary close token WARNING: bad16.pdf (trailer, offset 756): unexpected dictionary close token WARNING: bad16.pdf (trailer, offset 759): unknown token while reading object; treating as string -WARNING: bad16.pdf: file is damaged WARNING: bad16.pdf (trailer, offset 779): unexpected EOF +WARNING: bad16.pdf (trailer, offset 779): parse error while reading object +WARNING: bad16.pdf: file is damaged +WARNING: bad16.pdf (offset 712): expected trailer dictionary WARNING: bad16.pdf: Attempting to reconstruct cross-reference table WARNING: bad16.pdf (trailer, offset 753): unexpected dictionary close token WARNING: bad16.pdf (trailer, offset 756): unexpected dictionary close token WARNING: bad16.pdf (trailer, offset 759): unknown token while reading object; treating as string -bad16.pdf (trailer, offset 779): unexpected EOF +WARNING: bad16.pdf (trailer, offset 779): unexpected EOF +WARNING: bad16.pdf (trailer, offset 779): parse error while reading object +bad16.pdf: unable to find trailer dictionary while recovering damaged file diff --git a/qpdf/qtest/qpdf/bad16.out b/qpdf/qtest/qpdf/bad16.out index 1018bd7b..bcc37f35 100644 --- a/qpdf/qtest/qpdf/bad16.out +++ b/qpdf/qtest/qpdf/bad16.out @@ -1,4 +1,6 @@ WARNING: bad16.pdf (trailer, offset 753): unexpected dictionary close token WARNING: bad16.pdf (trailer, offset 756): unexpected dictionary close token WARNING: bad16.pdf (trailer, offset 759): unknown token while reading object; treating as string -bad16.pdf (trailer, offset 779): unexpected EOF +WARNING: bad16.pdf (trailer, offset 779): unexpected EOF +WARNING: bad16.pdf (trailer, offset 779): parse error while reading object +bad16.pdf (offset 712): expected trailer dictionary diff --git a/qpdf/qtest/qpdf/bad18-recover.out b/qpdf/qtest/qpdf/bad18-recover.out index c14bc1f3..7814c8ac 100644 --- a/qpdf/qtest/qpdf/bad18-recover.out +++ b/qpdf/qtest/qpdf/bad18-recover.out @@ -1,4 +1,7 @@ -WARNING: bad18.pdf: file is damaged WARNING: bad18.pdf (trailer, offset 753): unexpected ) -WARNING: bad18.pdf: Attempting to reconstruct cross-reference table -bad18.pdf (trailer, offset 753): unexpected ) +/QTest is implicit +/QTest is direct and has type null (2) +/QTest is null +unparse: null +unparseResolved: null +test 1 done diff --git a/qpdf/qtest/qpdf/bad18.out b/qpdf/qtest/qpdf/bad18.out index b6bce222..53d64cb1 100644 --- a/qpdf/qtest/qpdf/bad18.out +++ b/qpdf/qtest/qpdf/bad18.out @@ -1 +1,7 @@ -bad18.pdf (trailer, offset 753): unexpected ) +WARNING: bad18.pdf (trailer, offset 753): unexpected ) +/QTest is implicit +/QTest is direct and has type null (2) +/QTest is null +unparse: null +unparseResolved: null +test 0 done diff --git a/qpdf/qtest/qpdf/bad19-recover.out b/qpdf/qtest/qpdf/bad19-recover.out index ced8f51a..a828e0ed 100644 --- a/qpdf/qtest/qpdf/bad19-recover.out +++ b/qpdf/qtest/qpdf/bad19-recover.out @@ -1,4 +1,7 @@ -WARNING: bad19.pdf: file is damaged WARNING: bad19.pdf (trailer, offset 753): unexpected > -WARNING: bad19.pdf: Attempting to reconstruct cross-reference table -bad19.pdf (trailer, offset 753): unexpected > +/QTest is implicit +/QTest is direct and has type null (2) +/QTest is null +unparse: null +unparseResolved: null +test 1 done diff --git a/qpdf/qtest/qpdf/bad19.out b/qpdf/qtest/qpdf/bad19.out index 36eda04f..eafe3d88 100644 --- a/qpdf/qtest/qpdf/bad19.out +++ b/qpdf/qtest/qpdf/bad19.out @@ -1 +1,7 @@ -bad19.pdf (trailer, offset 753): unexpected > +WARNING: bad19.pdf (trailer, offset 753): unexpected > +/QTest is implicit +/QTest is direct and has type null (2) +/QTest is null +unparse: null +unparseResolved: null +test 0 done diff --git a/qpdf/qtest/qpdf/bad20-recover.out b/qpdf/qtest/qpdf/bad20-recover.out index 8411d5a9..a9507671 100644 --- a/qpdf/qtest/qpdf/bad20-recover.out +++ b/qpdf/qtest/qpdf/bad20-recover.out @@ -1,4 +1,11 @@ -WARNING: bad20.pdf: file is damaged WARNING: bad20.pdf (trailer, offset 753): invalid character (q) in hexstring -WARNING: bad20.pdf: Attempting to reconstruct cross-reference table -bad20.pdf (trailer, offset 753): invalid character (q) in hexstring +WARNING: bad20.pdf (trailer, offset 757): unknown token while reading object; treating as string +WARNING: bad20.pdf (trailer, offset 758): unexpected > +WARNING: bad20.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1 +WARNING: bad20.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2 +/QTest is implicit +/QTest is direct and has type null (2) +/QTest is null +unparse: null +unparseResolved: null +test 1 done diff --git a/qpdf/qtest/qpdf/bad20.out b/qpdf/qtest/qpdf/bad20.out index 8cdad1f1..7253932b 100644 --- a/qpdf/qtest/qpdf/bad20.out +++ b/qpdf/qtest/qpdf/bad20.out @@ -1 +1,11 @@ -bad20.pdf (trailer, offset 753): invalid character (q) in hexstring +WARNING: bad20.pdf (trailer, offset 753): invalid character (q) in hexstring +WARNING: bad20.pdf (trailer, offset 757): unknown token while reading object; treating as string +WARNING: bad20.pdf (trailer, offset 758): unexpected > +WARNING: bad20.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1 +WARNING: bad20.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2 +/QTest is implicit +/QTest is direct and has type null (2) +/QTest is null +unparse: null +unparseResolved: null +test 0 done diff --git a/qpdf/qtest/qpdf/bad21-recover.out b/qpdf/qtest/qpdf/bad21-recover.out index ff483eff..cbf55baf 100644 --- a/qpdf/qtest/qpdf/bad21-recover.out +++ b/qpdf/qtest/qpdf/bad21-recover.out @@ -1,4 +1,9 @@ -WARNING: bad21.pdf: file is damaged WARNING: bad21.pdf (trailer, offset 742): invalid name token -WARNING: bad21.pdf: Attempting to reconstruct cross-reference table -bad21.pdf (trailer, offset 742): invalid name token +WARNING: bad21.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1 +WARNING: bad21.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2 +/QTest is implicit +/QTest is direct and has type null (2) +/QTest is null +unparse: null +unparseResolved: null +test 1 done diff --git a/qpdf/qtest/qpdf/bad21.out b/qpdf/qtest/qpdf/bad21.out index b1a57fef..eab6f636 100644 --- a/qpdf/qtest/qpdf/bad21.out +++ b/qpdf/qtest/qpdf/bad21.out @@ -1 +1,9 @@ -bad21.pdf (trailer, offset 742): invalid name token +WARNING: bad21.pdf (trailer, offset 742): invalid name token +WARNING: bad21.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1 +WARNING: bad21.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2 +/QTest is implicit +/QTest is direct and has type null (2) +/QTest is null +unparse: null +unparseResolved: null +test 0 done diff --git a/qpdf/qtest/qpdf/bad29-recover.out b/qpdf/qtest/qpdf/bad29-recover.out index bc6c9b09..2f78b306 100644 --- a/qpdf/qtest/qpdf/bad29-recover.out +++ b/qpdf/qtest/qpdf/bad29-recover.out @@ -1,4 +1,9 @@ -WARNING: bad29.pdf: file is damaged WARNING: bad29.pdf (trailer, offset 742): null character not allowed in name token -WARNING: bad29.pdf: Attempting to reconstruct cross-reference table -bad29.pdf (trailer, offset 742): null character not allowed in name token +WARNING: bad29.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1 +WARNING: bad29.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2 +/QTest is implicit +/QTest is direct and has type null (2) +/QTest is null +unparse: null +unparseResolved: null +test 1 done diff --git a/qpdf/qtest/qpdf/bad29.out b/qpdf/qtest/qpdf/bad29.out index e9ded462..f1e8aa38 100644 --- a/qpdf/qtest/qpdf/bad29.out +++ b/qpdf/qtest/qpdf/bad29.out @@ -1 +1,9 @@ -bad29.pdf (trailer, offset 742): null character not allowed in name token +WARNING: bad29.pdf (trailer, offset 742): null character not allowed in name token +WARNING: bad29.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1 +WARNING: bad29.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2 +/QTest is implicit +/QTest is direct and has type null (2) +/QTest is null +unparse: null +unparseResolved: null +test 0 done diff --git a/qpdf/qtest/qpdf/issue-100.out b/qpdf/qtest/qpdf/issue-100.out index e5007541..da286551 100644 --- a/qpdf/qtest/qpdf/issue-100.out +++ b/qpdf/qtest/qpdf/issue-100.out @@ -8,6 +8,9 @@ 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 string WARNING: issue-100.pdf (object 5 0, offset 304): unknown token while reading object; treating as string WARNING: issue-100.pdf (object 5 0, offset 308): unexpected ) +WARNING: issue-100.pdf (object 5 0, offset 316): treating unexpected array close token as null +WARNING: issue-100.pdf (object 5 0, offset 227): expected dictionary key but found non-name object; inserting key /QPDFFake1 +WARNING: issue-100.pdf (object 5 0, offset 321): expected endobj WARNING: issue-100.pdf (object 5 0, offset 418): /Length key in stream dictionary is not an integer WARNING: issue-100.pdf (object 5 0, offset 489): attempting to recover stream length WARNING: issue-100.pdf (object 5 0, offset 489): recovered stream length: 12 diff --git a/qpdf/qtest/qpdf/issue-101.out b/qpdf/qtest/qpdf/issue-101.out index 29ccbfb7..f1e4d03a 100644 --- a/qpdf/qtest/qpdf/issue-101.out +++ b/qpdf/qtest/qpdf/issue-101.out @@ -56,4 +56,72 @@ WARNING: issue-101.pdf (object 11 0, offset 811): unknown token while reading ob WARNING: issue-101.pdf (object 11 0, offset 819): unknown token while reading object; treating as string WARNING: issue-101.pdf (object 11 0, offset 832): unknown token while reading object; treating as string WARNING: issue-101.pdf (object 11 0, offset 856): unexpected > -issue-101.pdf (offset 856): unable to find /Root dictionary +WARNING: issue-101.pdf (object 11 0, offset 857): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 868): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 887): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 897): unexpected ) +WARNING: issue-101.pdf (object 11 0, offset 898): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 909): invalid character (¤) in hexstring +WARNING: issue-101.pdf (object 11 0, offset 911): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 929): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 930): invalid character (²) in hexstring +WARNING: issue-101.pdf (object 11 0, offset 932): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 944): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 947): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 970): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1046): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1067): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1075): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1080): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1084): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1102): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1112): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1124): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1133): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1145): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1148): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1150): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1151): unexpected ) +WARNING: issue-101.pdf (object 11 0, offset 1153): unexpected dictionary close token +WARNING: issue-101.pdf (object 11 0, offset 1156): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1163): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1168): unexpected > +WARNING: issue-101.pdf (object 11 0, offset 1170): invalid character (I) in hexstring +WARNING: issue-101.pdf (object 11 0, offset 1167): expected dictionary key but found non-name object; inserting key /QPDFFake1 +WARNING: issue-101.pdf (object 11 0, offset 1167): expected dictionary key but found non-name object; inserting key /QPDFFake2 +WARNING: issue-101.pdf (object 11 0, offset 1167): expected dictionary key but found non-name object; inserting key /QPDFFake3 +WARNING: issue-101.pdf (object 11 0, offset 1176): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1180): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1184): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1190): unexpected > +WARNING: issue-101.pdf (object 11 0, offset 1192): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1195): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1205): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1217): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1224): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1236): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1242): expected dictionary key but found non-name object; inserting key /QPDFFake1 +WARNING: issue-101.pdf (object 11 0, offset 1242): dictionary ended prematurely; using null as value for last key +WARNING: issue-101.pdf (object 11 0, offset 1275): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1287): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1291): unexpected dictionary close token +WARNING: issue-101.pdf (object 11 0, offset 1294): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1306): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1322): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1325): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1329): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1341): treating unexpected array close token as null +WARNING: issue-101.pdf (object 11 0, offset 1312): expected dictionary key but found non-name object; inserting key /QPDFFake1 +WARNING: issue-101.pdf (object 11 0, offset 1312): expected dictionary key but found non-name object; inserting key /QPDFFake2 +WARNING: issue-101.pdf (object 11 0, offset 1312): expected dictionary key but found non-name object; inserting key /QPDFFake3 +WARNING: issue-101.pdf (object 11 0, offset 1312): expected dictionary key but found non-name object; inserting key /QPDFFake4 +WARNING: issue-101.pdf (object 11 0, offset 1312): dictionary ended prematurely; using null as value for last key +WARNING: issue-101.pdf (object 11 0, offset 1349): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1353): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1357): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1359): unknown token while reading object; treating as string +WARNING: issue-101.pdf (object 11 0, offset 1368): unexpected ) +WARNING: issue-101.pdf (object 11 0, offset 1373): 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 +qpdf: operation succeeded with warnings; resulting file may have some problems diff --git a/qpdf/qtest/qpdf/issue-146.out b/qpdf/qtest/qpdf/issue-146.out index fc92ab65..79bb8118 100644 --- a/qpdf/qtest/qpdf/issue-146.out +++ b/qpdf/qtest/qpdf/issue-146.out @@ -2,4 +2,6 @@ WARNING: issue-146.pdf: file is damaged WARNING: issue-146.pdf: can't find startxref WARNING: issue-146.pdf: Attempting to reconstruct cross-reference table WARNING: issue-146.pdf (trailer, offset 20728): unknown token while reading object; treating as string -issue-146.pdf (trailer, offset 20732): unexpected EOF +WARNING: issue-146.pdf (trailer, offset 20732): unexpected EOF +WARNING: issue-146.pdf (trailer, offset 20732): parse error while reading object +issue-146.pdf: unable to find trailer dictionary while recovering damaged file diff --git a/qpdf/qtest/qpdf/issue-51.out b/qpdf/qtest/qpdf/issue-51.out index 7873886b..692c0984 100644 --- a/qpdf/qtest/qpdf/issue-51.out +++ b/qpdf/qtest/qpdf/issue-51.out @@ -6,5 +6,6 @@ WARNING: issue-51.pdf (offset 70): loop detected resolving object 2 0 WARNING: issue-51.pdf (object 2 0, offset 26): /Length key in stream dictionary is not an integer WARNING: issue-51.pdf (object 2 0, offset 71): attempting to recover stream length WARNING: issue-51.pdf (object 2 0, offset 71): unable to recover stream data; treating stream as empty -WARNING: issue-51.pdf (object 2 0, offset 977): unexpected EOF +WARNING: issue-51.pdf (object 2 0, offset 977): expected endobj +WARNING: issue-51.pdf (object 2 0, offset 977): EOF after endobj qpdf: operation succeeded with warnings; resulting file may have some problems diff --git a/qpdf/qtest/qpdf/linearization-bounds-1.out b/qpdf/qtest/qpdf/linearization-bounds-1.out index 066dc883..d92c51c5 100644 --- a/qpdf/qtest/qpdf/linearization-bounds-1.out +++ b/qpdf/qtest/qpdf/linearization-bounds-1.out @@ -2,7 +2,7 @@ checking linearization-bounds-1.pdf PDF Version: 1.3 File is not encrypted File is linearized -WARNING: linearization-bounds-1.pdf (linearization hint stream: object 62 0, offset 12302): unexpected EOF +WARNING: linearization-bounds-1.pdf (linearization hint stream: object 62 0, offset 12302): expected endstream WARNING: linearization-bounds-1.pdf (linearization hint stream: object 62 0, offset 1183): attempting to recover stream length WARNING: linearization-bounds-1.pdf (linearization hint stream: object 62 0, offset 1183): recovered stream length: 106 linearization-bounds-1.pdf (linearization hint table, offset 1183): /S (shared object) offset is out of bounds diff --git a/qpdf/qtest/qpdf/object-types-os.out b/qpdf/qtest/qpdf/object-types-os.out new file mode 100644 index 00000000..26fcf369 --- /dev/null +++ b/qpdf/qtest/qpdf/object-types-os.out @@ -0,0 +1,41 @@ +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 347: operation for string attempted on object of type dictionary: returning empty string +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: returning null for out of bounds array access +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: returning null for out of bounds array access +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: returning null +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: ignoring attempt to append item +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to erase out of bounds array item +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to erase out of bounds array item +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: ignoring attempt to erase item +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: ignoring attempt to insert item +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: ignoring attempt to replace items +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: ignoring attempt to set item +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: treating as empty +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: treating as empty +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for boolean attempted on object of type integer: returning false +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for dictionary attempted on object of type integer: treating as empty +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for dictionary attempted on object of type integer: treating as empty +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for dictionary attempted on object of type integer: returning false for a key containment request +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for dictionary attempted on object of type integer: ignoring key removal request +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for dictionary attempted on object of type integer: ignoring key removal/replacement request +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for dictionary attempted on object of type integer: ignoring key removal/replacement request +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for dictionary attempted on object of type integer: ignoring key replacement request +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for dictionary attempted on object of type integer: returning null for attempted key retrieval +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for inlineimage attempted on object of type integer: returning empty data +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 362: operation for integer attempted on object of type dictionary: returning 0 +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for name attempted on object of type integer: returning dummy name +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for operator attempted on object of type integer: returning fake value +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 362: operation for real attempted on object of type dictionary: returning 0.0 +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for string attempted on object of type integer: returning empty string +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for string attempted on object of type integer: returning empty string +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 362: operation for number attempted on object of type dictionary: returning 0 +One error +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 385: operation for string attempted on object of type name: returning empty string +One error +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 362 -> dictionary key /Quack: operation for string attempted on object of type null: returning empty string +Two errors +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: returning null for out of bounds array access +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384 -> null returned from invalid array access: operation for string attempted on object of type null: returning empty string +One error +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 400: operation for string attempted on object of type name: returning empty string +WARNING: object-types-os.pdf, object 8 0 at offset 538 -> dictionary key /Potato: operation for name attempted on object of type null: returning dummy name +test 42 done diff --git a/qpdf/qtest/qpdf/object-types-os.pdf b/qpdf/qtest/qpdf/object-types-os.pdf new file mode 100644 index 0000000000000000000000000000000000000000..652ed35b2f054eba2123fa4796f5a1f820caf991 GIT binary patch literal 865 zcmZWnO^?$s5N!)wa)F;Pmr8Je*iM=csj5V`jaV)0mNr_Dsy#T)w4`d|RpO$SpTi&F zXK_P}lMRcsttJ}VZ{Ey%_HZ#v-tu=NXZY*)&p!?a20Gh0<1x7D`5wW&$#yGq0QV!7 zTk{Qk&jJ`8ia3H0Jnl8D3%SLbPz;t` ziwkMwUT-at?R%k*r2!7^Rgu@QCNhh?>(DL1KWu5RG&Mvm;U~-sIn^gBWAqQWfG`%e zcp)nSc8!-<>tm&865BD+bkZc8rncQC6RN1RTA?8Y-A?O>9iW?aDr;=2-4D3`j;2r& z+VUX)vKzZB*BvMwGsuH}I|O-S78n3=|4OG@jOyWt{jYid7BXctcg?iyQWg))UM zWp!>wy}~n9-OByZR?|w5X7|$Ifwkv$c}vQz6uWI*XmWCFixml_K3L`tcSo7McL z0J~o6r#KOjZcd50ZgXZ0=iXRA^;FmL4k4LC7KT|4*eI{~`BWFrr;Gv2}j3G|O zxCz2652O-8j6^0v>BVv2@rdQQiefRb)^rkk^y~Lt_6GE&=u-?Ro>4rfc+nrcqMbwU VslM9C%A6|PIDz2Ka5%q7oWF|Q<{kh5 literal 0 HcmV?d00001 diff --git a/qpdf/qtest/qpdf/object-types.out b/qpdf/qtest/qpdf/object-types.out new file mode 100644 index 00000000..b707a957 --- /dev/null +++ b/qpdf/qtest/qpdf/object-types.out @@ -0,0 +1,41 @@ +WARNING: object-types.pdf, object 8 0 at offset 657: operation for string attempted on object of type dictionary: returning empty string +WARNING: object-types.pdf, object 8 0 at offset 717: returning null for out of bounds array access +WARNING: object-types.pdf, object 8 0 at offset 717: returning null for out of bounds array access +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: returning null +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to append item +WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to erase out of bounds array item +WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to erase out of bounds array item +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to erase item +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to insert item +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to replace items +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to set item +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: treating as empty +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: treating as empty +WARNING: object-types.pdf, object 8 0 at offset 669: operation for boolean attempted on object of type integer: returning false +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: treating as empty +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: treating as empty +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: returning false for a key containment request +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: ignoring key removal request +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: ignoring key removal/replacement request +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: ignoring key removal/replacement request +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: ignoring key replacement request +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: returning null for attempted key retrieval +WARNING: object-types.pdf, object 8 0 at offset 669: operation for inlineimage attempted on object of type integer: returning empty data +WARNING: object-types.pdf, object 8 0 at offset 687: operation for integer attempted on object of type dictionary: returning 0 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for name attempted on object of type integer: returning dummy name +WARNING: object-types.pdf, object 8 0 at offset 669: operation for operator attempted on object of type integer: returning fake value +WARNING: object-types.pdf, object 8 0 at offset 687: operation for real attempted on object of type dictionary: returning 0.0 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for string attempted on object of type integer: returning empty string +WARNING: object-types.pdf, object 8 0 at offset 669: operation for string attempted on object of type integer: returning empty string +WARNING: object-types.pdf, object 8 0 at offset 687: operation for number attempted on object of type dictionary: returning 0 +One error +WARNING: object-types.pdf, object 8 0 at offset 724: operation for string attempted on object of type name: returning empty string +One error +WARNING: object-types.pdf, object 8 0 at offset 687 -> dictionary key /Quack: operation for string attempted on object of type null: returning empty string +Two errors +WARNING: object-types.pdf, object 8 0 at offset 717: returning null for out of bounds array access +WARNING: object-types.pdf, object 8 0 at offset 717 -> null returned from invalid array access: operation for string attempted on object of type null: returning empty string +One error +WARNING: object-types.pdf, object 8 0 at offset 745: operation for string attempted on object of type name: returning empty string +WARNING: object-types.pdf, object 4 0 at offset 386 -> dictionary key /Potato: operation for name attempted on object of type null: returning dummy name +test 42 done diff --git a/qpdf/qtest/qpdf/object-types.pdf b/qpdf/qtest/qpdf/object-types.pdf new file mode 100644 index 00000000..a283d129 --- /dev/null +++ b/qpdf/qtest/qpdf/object-types.pdf @@ -0,0 +1,111 @@ +%PDF-1.3 +%¿÷¢þ +%QDF-1.0 + +1 0 obj +<< + /Pages 2 0 R + /Type /Catalog +>> +endobj + +2 0 obj +<< + /Count 1 + /Kids [ + 3 0 R + ] + /Type /Pages +>> +endobj + +%% Page 1 +3 0 obj +<< + /Contents 4 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 6 0 R + >> + /ProcSet 7 0 R + >> + /Type /Page +>> +endobj + +%% Contents for page 1 +4 0 obj +<< + /Length 5 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato) Tj +ET +endstream +endobj + +5 0 obj +44 +endobj + +6 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +7 0 obj +[ + /PDF + /Text +] +endobj + +8 0 obj +<< + /Integer 5 + /Dictionary << + /Key1 /Value1 + /Key2 [ + /Item0 + << /K [ /V ] >> + /Item2 + ] + >> +>> +endobj + +xref +0 9 +0000000000 65535 f +0000000025 00000 n +0000000079 00000 n +0000000161 00000 n +0000000376 00000 n +0000000475 00000 n +0000000494 00000 n +0000000612 00000 n +0000000647 00000 n +trailer << + /Root 1 0 R + /Size 9 + /ID [<5ecb4bcc69402d31e10c2e63ec8500ee><5ecb4bcc69402d31e10c2e63ec8500ee>] + /QTest 8 0 R +>> +startxref +788 +%%EOF diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index c03e0250..3ec20dd8 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -1390,6 +1390,66 @@ void runtest(int n, char const* filename1, char const* arg2) w.setStaticID(true); w.write(); } + else if (n == 42) + { + // Access objects as wrong type. This test case is crafted to + // work with object-types.pdf. + QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest"); + QPDFObjectHandle array = qtest.getKey("/Dictionary").getKey("/Key2"); + QPDFObjectHandle dictionary = qtest.getKey("/Dictionary"); + QPDFObjectHandle integer = qtest.getKey("/Integer"); + QPDFObjectHandle null = QPDFObjectHandle::newNull(); + assert(array.isArray()); + assert(dictionary.isDictionary()); + assert("" == qtest.getStringValue()); + array.getArrayItem(-1).assertNull(); + array.getArrayItem(16059).assertNull(); + integer.getArrayItem(0).assertNull(); + integer.appendItem(null); + array.eraseItem(-1); + array.eraseItem(16059); + integer.eraseItem(0); + integer.insertItem(0, null); + integer.setArrayFromVector(std::vector()); + integer.setArrayItem(0, null); + assert(0 == integer.getArrayNItems()); + assert(integer.getArrayAsVector().empty()); + assert(false == integer.getBoolValue()); + assert(integer.getDictAsMap().empty()); + assert(integer.getKeys().empty()); + assert(false == integer.hasKey("/Potato")); + integer.removeKey("/Potato"); + integer.replaceOrRemoveKey("/Potato", null); + integer.replaceOrRemoveKey("/Potato", QPDFObjectHandle::newInteger(1)); + integer.replaceKey("/Potato", QPDFObjectHandle::newInteger(1)); + qtest.getKey("/Integer").getKey("/Potato"); + assert(integer.getInlineImageValue().empty()); + assert(0 == dictionary.getIntValue()); + assert("/QPDFFakeName" == integer.getName()); + assert("QPDFFAKE" == integer.getOperatorValue()); + assert("0.0" == dictionary.getRealValue()); + assert(integer.getStringValue().empty()); + assert(integer.getUTF8Value().empty()); + assert(0.0 == dictionary.getNumericValue()); + // Make sure error messages are okay for nested values + std::cerr << "One error\n"; + assert(array.getArrayItem(0).getStringValue().empty()); + std::cerr << "One error\n"; + assert(dictionary.getKey("/Quack").getStringValue().empty()); + assert(array.getArrayItem(1).isDictionary()); + assert(array.getArrayItem(1).getKey("/K").isArray()); + assert(array.getArrayItem(1).getKey("/K").getArrayItem(0).isName()); + assert("/V" == + array.getArrayItem(1).getKey("/K").getArrayItem(0).getName()); + std::cerr << "Two errors\n"; + assert(array.getArrayItem(16059).getStringValue().empty()); + std::cerr << "One error\n"; + array.getArrayItem(1).getKey("/K").getArrayItem(0).getStringValue(); + // Stream dictionary + QPDFObjectHandle page = pdf.getAllPages()[0]; + assert("/QPDFFakeName" == + page.getKey("/Contents").getDict().getKey("/Potato").getName()); + } else { throw std::runtime_error(std::string("invalid test ") +