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 00000000..652ed35b Binary files /dev/null and b/qpdf/qtest/qpdf/object-types-os.pdf differ 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 ") +