Commit d0e99f195a987c483bbb6c5449cf39bee34e08a1

Authored by Jay Berkenbilt
1 parent c2e16827

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
1 1 2018-02-17 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Major enhancements to handling of type errors within the qpdf
  4 + library. This fix is intended to eliminate those annoying cases
  5 + where qpdf would exit with a message like "operation for
  6 + dictionary object attemped on object of wrong type" without
  7 + providing any context. Now qpdf keeps enough context to be able to
  8 + issue a proper warning and to handle such conditions in a sensible
  9 + way. This should greatly increase the number of bad files that
  10 + qpdf can recover, and it should make it much easier to figure out
  11 + what's broken when a file contains errors.
  12 +
3 13 * Error message fix: replace "file position" with "offset" in
4 14 error messages that report lexical or parsing errors. Sometimes
5 15 it's an offset in an object stream or a content stream rather than
... ...
1 1 Soon
2 2 ====
3 3  
4   - * Consider whether there should be a mode in which QPDFObjectHandle
5   - returns nulls for operations on the wrong type instead of asserting
6   - the type. The way things are wired up now, this would have to be a
7   - global flag. Probably it makes sense to make that be the default
8   - behavior and to add a static method in QPDFObjectHandle and
9   - command-line flag that enables the stricter behavior globally for
10   - easier debugging. For cases where we have enough information to do
11   - so, we could still warn when not in strict mode.
12   -
13 4 * Add method to push inheritable resources to a single page by
14 5 walking up and copying without overwrite. Above logic will also be
15 6 sufficient to fix the limitation in
... ...
include/qpdf/QPDF.hh
... ... @@ -703,7 +703,6 @@ class QPDF
703 703 PointerHolder<InputSource> input, int objid, int generation,
704 704 qpdf_offset_t stream_offset);
705 705 QPDFTokenizer::Token readToken(PointerHolder<InputSource>,
706   - bool allow_bad = false,
707 706 size_t max_len = 0);
708 707  
709 708 QPDFObjectHandle readObjectAtOffset(
... ...
include/qpdf/QPDFObject.hh
... ... @@ -23,6 +23,7 @@
23 23 #define __QPDFOBJECT_HH__
24 24  
25 25 #include <qpdf/DLL.h>
  26 +#include <qpdf/PointerHolder.hh>
26 27  
27 28 #include <string>
28 29  
... ... @@ -32,6 +33,7 @@ class QPDFObjectHandle;
32 33 class QPDFObject
33 34 {
34 35 public:
  36 + QPDFObject();
35 37  
36 38 // Objects derived from QPDFObject are accessible through
37 39 // QPDFObjectHandle. Each object returns a unique type code that
... ... @@ -84,8 +86,27 @@ class QPDFObject
84 86 };
85 87 friend class ObjAccessor;
86 88  
  89 + virtual void setDescription(QPDF*, std::string const&);
  90 + bool getDescription(QPDF*&, std::string&);
  91 + bool hasDescription();
  92 +
87 93 protected:
88 94 virtual void releaseResolved() {}
  95 +
  96 + private:
  97 + QPDFObject(QPDFObject const&);
  98 + QPDFObject& operator=(QPDFObject const&);
  99 + class Members
  100 + {
  101 + friend class QPDFObject;
  102 + public:
  103 + ~Members();
  104 + private:
  105 + Members();
  106 + QPDF* owning_qpdf;
  107 + std::string object_description;
  108 + };
  109 + PointerHolder<Members> m;
89 110 };
90 111  
91 112 #endif // __QPDFOBJECT_HH__
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -398,6 +398,21 @@ class QPDFObjectHandle
398 398 QPDF_DLL
399 399 static QPDFObjectHandle newReserved(QPDF* qpdf);
400 400  
  401 + // Provide an owning qpdf and object description. The library does
  402 + // this automatically with objects that are read from from the
  403 + // input PDF and with objects that are created programmatically
  404 + // and inserted into the QPDF by adding them to an array or a
  405 + // dictionary or creating a new indirect object. Most end user
  406 + // code will not need to call this. If an object has an owning
  407 + // qpdf and object description, it enables qpdf to give warnings
  408 + // with proper context in some cases where it would otherwise
  409 + // raise exceptions.
  410 + QPDF_DLL
  411 + void setObjectDescription(QPDF* owning_qpdf,
  412 + std::string const& object_description);
  413 + QPDF_DLL
  414 + bool hasObjectDescription();
  415 +
401 416 // Accessor methods. If an accessor method that is valid for only
402 417 // a particular object type is called on an object of the wrong
403 418 // type, an exception is thrown.
... ... @@ -498,7 +513,7 @@ class QPDFObjectHandle
498 513  
499 514 // Replace value of key, adding it if it does not exist
500 515 QPDF_DLL
501   - void replaceKey(std::string const& key, QPDFObjectHandle const&);
  516 + void replaceKey(std::string const& key, QPDFObjectHandle);
502 517 // Remove key, doing nothing if key does not exist
503 518 QPDF_DLL
504 519 void removeKey(std::string const& key);
... ... @@ -769,7 +784,10 @@ class QPDFObjectHandle
769 784 };
770 785 friend class ReleaseResolver;
771 786  
772   - // Convenience routine: Throws if the assumption is violated.
  787 + // Convenience routine: Throws if the assumption is violated. Your
  788 + // code will be better if you call one of the isType methods and
  789 + // handle the case of the type being wrong, but these can be
  790 + // convenient if you have already verified the type.
773 791 QPDF_DLL
774 792 void assertInitialized() const;
775 793  
... ... @@ -832,10 +850,16 @@ class QPDFObjectHandle
832 850 QPDF* qpdf, int objid, int generation,
833 851 QPDFObjectHandle stream_dict, qpdf_offset_t offset, size_t length);
834 852  
835   - void assertType(char const* type_name, bool istype) const;
  853 + void typeWarning(char const* expected_type,
  854 + std::string const& warning);
  855 + void objectWarning(std::string const& warning);
  856 + void assertType(char const* type_name, bool istype);
836 857 void dereference();
837 858 void makeDirectInternal(std::set<int>& visited);
838 859 void releaseResolved();
  860 + static void setObjectDescriptionFromInput(
  861 + QPDFObjectHandle, QPDF*, std::string const&,
  862 + PointerHolder<InputSource>, qpdf_offset_t);
839 863 static QPDFObjectHandle parseInternal(
840 864 PointerHolder<InputSource> input,
841 865 std::string const& object_description,
... ... @@ -868,7 +892,7 @@ class QPDFObjectHandle
868 892  
869 893 bool initialized;
870 894  
871   - QPDF* qpdf; // 0 for direct object
  895 + QPDF* qpdf;
872 896 int objid; // 0 for direct object
873 897 int generation;
874 898 PointerHolder<QPDFObject> obj;
... ...
ispell-words
... ... @@ -23,9 +23,11 @@ activatePipelineStack
23 23 ActiveState
24 24 acyclic
25 25 adbe
  26 +addContentTokenFilter
26 27 addPage
27 28 addPageAt
28 29 addPageContents
  30 +addTokenFilter
29 31 addToTable
30 32 adjustAESStreamLength
31 33 admon
... ... @@ -54,6 +56,7 @@ allowPoundAnywhereInName
54 56 allowPrintHighRes
55 57 allowPrintLowRes
56 58 antivirus
  59 +anyBadTokens
57 60 aobjid
58 61 apexcovantage
59 62 api
... ... @@ -71,11 +74,13 @@ argv
71 74 arko
72 75 arko's
73 76 Arora
  77 +arrayOrStreamToStreamArray
74 78 ArtBox
75 79 ascii
76 80 asciiHex
77 81 ASCIIHexDecode
78 82 ASCIIHexDecoder
  83 +asMap
79 84 assertArray
80 85 assertBool
81 86 assertDictionary
... ... @@ -214,12 +219,15 @@ closeObject
214 219 cmath
215 220 cmd
216 221 cmyk
  222 +coalesceContentStreams
  223 +CoalesceProvider
217 224 codepage
218 225 codepoint
219 226 col
220 227 Coldwind
221 228 ColorSpace
222 229 colorspace
  230 +ColorToGray
223 231 com
224 232 compareVersions
225 233 compatbility
... ... @@ -230,6 +238,7 @@ Cond
230 238 config
231 239 conftest
232 240 const
  241 +ContentNormalizer
233 242 contrib
234 243 CopiedStreamDataProvider
235 244 copyEncryptionParameters
... ... @@ -300,6 +309,7 @@ deflateEnd
300 309 deflateInit
301 310 defq
302 311 delphi
  312 +deque
303 313 dereference
304 314 dereferenced
305 315 dest
... ... @@ -411,6 +421,7 @@ esize
411 421 exc
412 422 exe
413 423 exp
  424 +expectInlineImage
414 425 ExtensionLevel
415 426 extern
416 427 fb
... ... @@ -439,6 +450,7 @@ Filespec
439 450 FILETIME
440 451 filetrailer
441 452 filterCompressedObjects
  453 +filterPageContents
442 454 findAndSkipNextEOL
443 455 findAttachmentStreams
444 456 findEndstream
... ... @@ -509,6 +521,7 @@ getCompressibleObjects
509 521 getCompressibleObjGens
510 522 getCount
511 523 getDataChecksum
  524 +getDescription
512 525 getDict
513 526 getDictAsMap
514 527 getEncryptionKey
... ... @@ -562,6 +575,7 @@ getOE
562 575 getOffset
563 576 getOffsetLength
564 577 getOperatorValue
  578 +getOriginalID
565 579 getOwningQPDF
566 580 getP
567 581 getPaddedUserPassword
... ... @@ -626,7 +640,10 @@ handleCode
626 640 handleData
627 641 handleEOF
628 642 handleObject
  643 +handleToken
  644 +hasDescription
629 645 hasKey
  646 +hasObjectDescription
630 647 hb
631 648 hbp
632 649 HCRYPTPROV
... ... @@ -668,6 +685,7 @@ ifeq
668 685 iff
669 686 ifndef
670 687 ifstream
  688 +ignorable
671 689 ijg
672 690 Im
673 691 ImageC
... ... @@ -676,6 +694,7 @@ ImageInverter
676 694 ImageMask
677 695 ImageProvider
678 696 inbuf
  697 +includeIgnorable
679 698 INDOC
680 699 indx
681 700 inf
... ... @@ -720,9 +739,12 @@ iostream
720 739 irdp
721 740 isArray
722 741 isBool
  742 +isDataModified
  743 +isDelimiter
723 744 isDictionary
724 745 isdigit
725 746 isEncrypted
  747 +isIgnorable
726 748 isIndirect
727 749 isInitialized
728 750 isInlineImage
... ... @@ -731,6 +753,7 @@ isLinearized
731 753 isName
732 754 isNull
733 755 isNumber
  756 +isNumeric
734 757 iso
735 758 isOperator
736 759 isOrHasName
... ... @@ -741,10 +764,12 @@ isReal
741 764 isReserved
742 765 isScalar
743 766 isspace
  767 +isSpace
744 768 isStream
745 769 isString
746 770 istream
747 771 istype
  772 +isType
748 773 italicseq
749 774 itemizedlist
750 775 iter
... ... @@ -772,6 +797,7 @@ keyset
772 797 LARGEFILE
773 798 lastnum
774 799 lastreleased
  800 +lastTokenWasBad
775 801 latin
776 802 lbuf
777 803 lc
... ... @@ -839,6 +865,7 @@ malloc
839 865 manualFinish
840 866 Mateusz
841 867 maxEnd
  868 +maxlen
842 869 maxval
843 870 md
844 871 mdash
... ... @@ -933,6 +960,7 @@ NTE
933 960 ntotal
934 961 NUL
935 962 num
  963 +numericValue
936 964 numrange
937 965 nval
938 966 nwalsh
... ... @@ -1007,6 +1035,7 @@ parms
1007 1035 parsecontent
1008 1036 parseContentStream
1009 1037 parseInternal
  1038 +parsePageContents
1010 1039 ParserCallbacks
1011 1040 parseVersion
1012 1041 partLen
... ... @@ -1033,6 +1062,9 @@ persistAcrossFinish
1033 1062 ph
1034 1063 phe
1035 1064 php
  1065 +pipeContentStreams
  1066 +PipelineAccessor
  1067 +pipePageContents
1036 1068 pipeStreamData
1037 1069 pipeStringAndFinish
1038 1070 Pkey
... ... @@ -1072,6 +1104,7 @@ procset
1072 1104 ProcSet
1073 1105 procsets
1074 1106 programlisting
  1107 +programmatically
1075 1108 Projet
1076 1109 prov
1077 1110 provideRandomData
... ... @@ -1106,6 +1139,7 @@ QPDF&#39;s
1106 1139 QPDFCONSTANTS
1107 1140 QPDFExc
1108 1141 QPDFFake
  1142 +QPDFFakeName
1109 1143 QPDFObject
1110 1144 QPDFObjectHandle
1111 1145 QPDFObjectHandle's
... ... @@ -1170,6 +1204,7 @@ ReleaseResolver
1170 1204 remotesensing
1171 1205 removeKey
1172 1206 removePage
  1207 +removereplace
1173 1208 repl
1174 1209 replaceDict
1175 1210 replaceFilterData
... ... @@ -1191,6 +1226,7 @@ retested
1191 1226 reverseResolved
1192 1227 rf
1193 1228 rfont
  1229 +rg
1194 1230 rgb
1195 1231 rhs
1196 1232 rijndael
... ... @@ -1240,7 +1276,9 @@ setCompressStreams
1240 1276 setContentNormalization
1241 1277 setDataKey
1242 1278 setDecodeLevel
  1279 +setDescription
1243 1280 setDeterministicID
  1281 +setDictDescription
1244 1282 setEncryptionParameters
1245 1283 setEncryptionParametersInternal
1246 1284 setExtraHeaderText
... ... @@ -1254,11 +1292,14 @@ setjmp
1254 1292 setLastObjectDescription
1255 1293 setLastOffset
1256 1294 setLinearization
  1295 +setLinearizationPass
1257 1296 setLineBuf
1258 1297 setMinimumPDFVersion
1259 1298 setmode
1260 1299 setNewlineBeforeEndstream
1261 1300 setO
  1301 +setObjectDescription
  1302 +setObjectDescriptionFromInput
1262 1303 setObjectStreamMode
1263 1304 setObjGen
1264 1305 setOutputFile
... ... @@ -1268,6 +1309,7 @@ setOutputPipeline
1268 1309 setOutputStreams
1269 1310 setPasswordIsHexKey
1270 1311 setPCLm
  1312 +setPipeline
1271 1313 setprecision
1272 1314 setPreserveEncryption
1273 1315 setPreserveUnreferencedObjects
... ... @@ -1277,6 +1319,7 @@ setRandomDataProvider
1277 1319 setStaticAesIV
1278 1320 setStaticID
1279 1321 setStreamDataMode
  1322 +setStreamDescription
1280 1323 setSuppressOriginalObjectIDs
1281 1324 setSuppressWarnings
1282 1325 setTrailer
... ... @@ -1329,8 +1372,10 @@ StreamDataProvider
1329 1372 strerror
1330 1373 StrF
1331 1374 stricmp
  1375 +StringCounter
1332 1376 StringDecrypter
1333 1377 stringprep
  1378 +StringReverser
1334 1379 stripesize
1335 1380 strlen
1336 1381 strncmp
... ... @@ -1385,10 +1430,13 @@ tobj
1385 1430 tobjid
1386 1431 TODO
1387 1432 toffset
  1433 +TokenFilter
  1434 +TokenFilters
1388 1435 tokenize
1389 1436 tokenized
1390 1437 tokenizer
1391 1438 tokenizing
  1439 +tokenTypeName
1392 1440 toolchain
1393 1441 Toolchains
1394 1442 toupper
... ... @@ -1400,6 +1448,7 @@ trimTrailerForWrite
1400 1448 tt
1401 1449 turbo
1402 1450 txt
  1451 +typeWarning
1403 1452 uc
1404 1453 udata
1405 1454 UE
... ... @@ -1428,6 +1477,7 @@ unparse
1428 1477 unparseChild
1429 1478 unparseObject
1430 1479 unparseResolved
  1480 +unparsing
1431 1481 unreadCh
1432 1482 unreferenced
1433 1483 unresolvable
... ...
libqpdf/QPDF.cc
... ... @@ -106,6 +106,7 @@ QPDF::Members::~Members()
106 106 QPDF::QPDF() :
107 107 m(new Members())
108 108 {
  109 + m->tokenizer.allowEOF();
109 110 }
110 111  
111 112 QPDF::~QPDF()
... ... @@ -272,10 +273,10 @@ QPDF::findHeader()
272 273 bool
273 274 QPDF::findStartxref()
274 275 {
275   - QPDFTokenizer::Token t = readToken(this->m->file, true);
  276 + QPDFTokenizer::Token t = readToken(this->m->file);
276 277 if (t == QPDFTokenizer::Token(QPDFTokenizer::tt_word, "startxref"))
277 278 {
278   - t = readToken(this->m->file, true);
  279 + t = readToken(this->m->file);
279 280 if (t.getType() == QPDFTokenizer::tt_integer)
280 281 {
281 282 // Position in front of offset token
... ... @@ -421,7 +422,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e)
421 422 this->m->file->findAndSkipNextEOL();
422 423 qpdf_offset_t next_line_start = this->m->file->tell();
423 424 this->m->file->seek(line_start, SEEK_SET);
424   - QPDFTokenizer::Token t1 = readToken(this->m->file, true, MAX_LEN);
  425 + QPDFTokenizer::Token t1 = readToken(this->m->file, MAX_LEN);
425 426 qpdf_offset_t token_start =
426 427 this->m->file->tell() - t1.getValue().length();
427 428 if (token_start >= next_line_start)
... ... @@ -440,9 +441,9 @@ QPDF::reconstruct_xref(QPDFExc&amp; e)
440 441 if (t1.getType() == QPDFTokenizer::tt_integer)
441 442 {
442 443 QPDFTokenizer::Token t2 =
443   - readToken(this->m->file, true, MAX_LEN);
  444 + readToken(this->m->file, MAX_LEN);
444 445 QPDFTokenizer::Token t3 =
445   - readToken(this->m->file, true, MAX_LEN);
  446 + readToken(this->m->file, MAX_LEN);
446 447 if ((t2.getType() == QPDFTokenizer::tt_integer) &&
447 448 (t3 == QPDFTokenizer::Token(QPDFTokenizer::tt_word, "obj")))
448 449 {
... ... @@ -1429,7 +1430,7 @@ bool
1429 1430 QPDF::findEndstream()
1430 1431 {
1431 1432 // Find endstream or endobj. Position the input at that token.
1432   - QPDFTokenizer::Token t = readToken(this->m->file, true, 20);
  1433 + QPDFTokenizer::Token t = readToken(this->m->file, 20);
1433 1434 if ((t.getType() == QPDFTokenizer::tt_word) &&
1434 1435 ((t.getValue() == "endobj") ||
1435 1436 (t.getValue() == "endstream")))
... ... @@ -1522,11 +1523,10 @@ QPDF::recoverStreamLength(PointerHolder&lt;InputSource&gt; input,
1522 1523 }
1523 1524  
1524 1525 QPDFTokenizer::Token
1525   -QPDF::readToken(PointerHolder<InputSource> input,
1526   - bool allow_bad, size_t max_len)
  1526 +QPDF::readToken(PointerHolder<InputSource> input, size_t max_len)
1527 1527 {
1528 1528 return this->m->tokenizer.readToken(
1529   - input, this->m->last_object_description, allow_bad, max_len);
  1529 + input, this->m->last_object_description, true, max_len);
1530 1530 }
1531 1531  
1532 1532 QPDFObjectHandle
... ... @@ -1730,16 +1730,10 @@ QPDF::resolve(int objid, int generation)
1730 1730 }
1731 1731 ResolveRecorder rr(this, og);
1732 1732  
1733   - if (! this->m->obj_cache.count(og))
  1733 + // PDF spec says unknown objects resolve to the null object.
  1734 + if ((! this->m->obj_cache.count(og)) && this->m->xref_table.count(og))
1734 1735 {
1735   - if (! this->m->xref_table.count(og))
1736   - {
1737   - // PDF spec says unknown objects resolve to the null object.
1738   - return new QPDF_Null;
1739   - }
1740   -
1741 1736 QPDFXRefEntry const& entry = this->m->xref_table[og];
1742   - bool success = false;
1743 1737 try
1744 1738 {
1745 1739 switch (entry.getType())
... ... @@ -1768,7 +1762,6 @@ QPDF::resolve(int objid, int generation)
1768 1762 QUtil::int_to_string(generation) +
1769 1763 " has unexpected xref entry type");
1770 1764 }
1771   - success = true;
1772 1765 }
1773 1766 catch (QPDFExc& e)
1774 1767 {
... ... @@ -1782,16 +1775,24 @@ QPDF::resolve(int objid, int generation)
1782 1775 QUtil::int_to_string(generation) +
1783 1776 ": error reading object: " + e.what()));
1784 1777 }
1785   - if (! success)
1786   - {
1787   - QTC::TC("qpdf", "QPDF resolve failure to null");
1788   - QPDFObjectHandle oh = QPDFObjectHandle::newNull();
1789   - this->m->obj_cache[og] =
1790   - ObjCache(QPDFObjectHandle::ObjAccessor::getObject(oh), -1, -1);
1791   - }
  1778 + }
  1779 + if (this->m->obj_cache.count(og) == 0)
  1780 + {
  1781 + QTC::TC("qpdf", "QPDF resolve failure to null");
  1782 + QPDFObjectHandle oh = QPDFObjectHandle::newNull();
  1783 + this->m->obj_cache[og] =
  1784 + ObjCache(QPDFObjectHandle::ObjAccessor::getObject(oh), -1, -1);
1792 1785 }
1793 1786  
1794   - return this->m->obj_cache[og].object;
  1787 + PointerHolder<QPDFObject> result(this->m->obj_cache[og].object);
  1788 + if (! result->hasDescription())
  1789 + {
  1790 + result->setDescription(
  1791 + this,
  1792 + "object " + QUtil::int_to_string(objid) + " " +
  1793 + QUtil::int_to_string(generation));
  1794 + }
  1795 + return result;
1795 1796 }
1796 1797  
1797 1798 void
... ...
libqpdf/QPDFExc.cc
... ... @@ -32,7 +32,10 @@ QPDFExc::createWhat(std::string const&amp; filename,
32 32 }
33 33 if (! (object.empty() && offset == 0))
34 34 {
35   - result += " (";
  35 + if (! filename.empty())
  36 + {
  37 + result += " (";
  38 + }
36 39 if (! object.empty())
37 40 {
38 41 result += object;
... ... @@ -45,7 +48,10 @@ QPDFExc::createWhat(std::string const&amp; filename,
45 48 {
46 49 result += "offset " + QUtil::int_to_string(offset);
47 50 }
48   - result += ")";
  51 + if (! filename.empty())
  52 + {
  53 + result += ")";
  54 + }
49 55 }
50 56 if (! result.empty())
51 57 {
... ...
libqpdf/QPDFObject.cc
1 1 #include <qpdf/QPDFObject.hh>
  2 +
  3 +QPDFObject::Members::Members() :
  4 + owning_qpdf(0)
  5 +{
  6 +}
  7 +
  8 +QPDFObject::Members::~Members()
  9 +{
  10 +}
  11 +
  12 +QPDFObject::QPDFObject() :
  13 + m(new Members)
  14 +{
  15 +}
  16 +
  17 +void
  18 +QPDFObject::setDescription(QPDF* qpdf, std::string const& description)
  19 +{
  20 + this->m->owning_qpdf = qpdf;
  21 + this->m->object_description = description;
  22 +}
  23 +
  24 +bool
  25 +QPDFObject::getDescription(QPDF*& qpdf, std::string& description)
  26 +{
  27 + qpdf = this->m->owning_qpdf;
  28 + description = this->m->object_description;
  29 + return this->m->owning_qpdf;
  30 +}
  31 +
  32 +bool
  33 +QPDFObject::hasDescription()
  34 +{
  35 + return this->m->owning_qpdf;
  36 +}
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -190,6 +190,18 @@ QPDFObjectHandle::releaseResolved()
190 190 }
191 191 }
192 192  
  193 +void
  194 +QPDFObjectHandle::setObjectDescriptionFromInput(
  195 + QPDFObjectHandle object, QPDF* context,
  196 + std::string const& description, PointerHolder<InputSource> input,
  197 + qpdf_offset_t offset)
  198 +{
  199 + object.setObjectDescription(
  200 + context,
  201 + input->getName() + ", " + description +
  202 + " at offset " + QUtil::int_to_string(offset));
  203 +}
  204 +
193 205 bool
194 206 QPDFObjectHandle::isInitialized() const
195 207 {
... ... @@ -282,7 +294,8 @@ QPDFObjectHandle::getNumericValue()
282 294 }
283 295 else
284 296 {
285   - throw std::logic_error("getNumericValue called for non-numeric object");
  297 + typeWarning("number", "returning 0");
  298 + QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
286 299 }
287 300 return result;
288 301 }
... ... @@ -363,8 +376,16 @@ QPDFObjectHandle::isScalar()
363 376 bool
364 377 QPDFObjectHandle::getBoolValue()
365 378 {
366   - assertBool();
367   - return dynamic_cast<QPDF_Bool*>(m->obj.getPointer())->getVal();
  379 + if (isBool())
  380 + {
  381 + return dynamic_cast<QPDF_Bool*>(m->obj.getPointer())->getVal();
  382 + }
  383 + else
  384 + {
  385 + typeWarning("boolean", "returning false");
  386 + QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
  387 + return false;
  388 + }
368 389 }
369 390  
370 391 // Integer accessors
... ... @@ -372,8 +393,16 @@ QPDFObjectHandle::getBoolValue()
372 393 long long
373 394 QPDFObjectHandle::getIntValue()
374 395 {
375   - assertInteger();
376   - return dynamic_cast<QPDF_Integer*>(m->obj.getPointer())->getVal();
  396 + if (isInteger())
  397 + {
  398 + return dynamic_cast<QPDF_Integer*>(m->obj.getPointer())->getVal();
  399 + }
  400 + else
  401 + {
  402 + typeWarning("integer", "returning 0");
  403 + QTC::TC("qpdf", "QPDFObjectHandle integer returning 0");
  404 + return 0;
  405 + }
377 406 }
378 407  
379 408 // Real accessors
... ... @@ -381,8 +410,16 @@ QPDFObjectHandle::getIntValue()
381 410 std::string
382 411 QPDFObjectHandle::getRealValue()
383 412 {
384   - assertReal();
385   - return dynamic_cast<QPDF_Real*>(m->obj.getPointer())->getVal();
  413 + if (isReal())
  414 + {
  415 + return dynamic_cast<QPDF_Real*>(m->obj.getPointer())->getVal();
  416 + }
  417 + else
  418 + {
  419 + typeWarning("real", "returning 0.0");
  420 + QTC::TC("qpdf", "QPDFObjectHandle real returning 0.0");
  421 + return "0.0";
  422 + }
386 423 }
387 424  
388 425 // Name accessors
... ... @@ -390,8 +427,16 @@ QPDFObjectHandle::getRealValue()
390 427 std::string
391 428 QPDFObjectHandle::getName()
392 429 {
393   - assertName();
394   - return dynamic_cast<QPDF_Name*>(m->obj.getPointer())->getName();
  430 + if (isName())
  431 + {
  432 + return dynamic_cast<QPDF_Name*>(m->obj.getPointer())->getName();
  433 + }
  434 + else
  435 + {
  436 + typeWarning("name", "returning dummy name");
  437 + QTC::TC("qpdf", "QPDFObjectHandle name returning dummy name");
  438 + return "/QPDFFakeName";
  439 + }
395 440 }
396 441  
397 442 // String accessors
... ... @@ -399,15 +444,31 @@ QPDFObjectHandle::getName()
399 444 std::string
400 445 QPDFObjectHandle::getStringValue()
401 446 {
402   - assertString();
403   - return dynamic_cast<QPDF_String*>(m->obj.getPointer())->getVal();
  447 + if (isString())
  448 + {
  449 + return dynamic_cast<QPDF_String*>(m->obj.getPointer())->getVal();
  450 + }
  451 + else
  452 + {
  453 + typeWarning("string", "returning empty string");
  454 + QTC::TC("qpdf", "QPDFObjectHandle string returning empty string");
  455 + return "";
  456 + }
404 457 }
405 458  
406 459 std::string
407 460 QPDFObjectHandle::getUTF8Value()
408 461 {
409   - assertString();
410   - return dynamic_cast<QPDF_String*>(m->obj.getPointer())->getUTF8Val();
  462 + if (isString())
  463 + {
  464 + return dynamic_cast<QPDF_String*>(m->obj.getPointer())->getUTF8Val();
  465 + }
  466 + else
  467 + {
  468 + typeWarning("string", "returning empty string");
  469 + QTC::TC("qpdf", "QPDFObjectHandle string returning empty utf8");
  470 + return "";
  471 + }
411 472 }
412 473  
413 474 // Operator and Inline Image accessors
... ... @@ -415,15 +476,31 @@ QPDFObjectHandle::getUTF8Value()
415 476 std::string
416 477 QPDFObjectHandle::getOperatorValue()
417 478 {
418   - assertOperator();
419   - return dynamic_cast<QPDF_Operator*>(m->obj.getPointer())->getVal();
  479 + if (isOperator())
  480 + {
  481 + return dynamic_cast<QPDF_Operator*>(m->obj.getPointer())->getVal();
  482 + }
  483 + else
  484 + {
  485 + typeWarning("operator", "returning fake value");
  486 + QTC::TC("qpdf", "QPDFObjectHandle operator returning fake value");
  487 + return "QPDFFAKE";
  488 + }
420 489 }
421 490  
422 491 std::string
423 492 QPDFObjectHandle::getInlineImageValue()
424 493 {
425   - assertInlineImage();
426   - return dynamic_cast<QPDF_InlineImage*>(m->obj.getPointer())->getVal();
  494 + if (isInlineImage())
  495 + {
  496 + return dynamic_cast<QPDF_InlineImage*>(m->obj.getPointer())->getVal();
  497 + }
  498 + else
  499 + {
  500 + typeWarning("inlineimage", "returning empty data");
  501 + QTC::TC("qpdf", "QPDFObjectHandle inlineimage returning empty data");
  502 + return "";
  503 + }
427 504 }
428 505  
429 506 // Array accessors
... ... @@ -431,22 +508,66 @@ QPDFObjectHandle::getInlineImageValue()
431 508 int
432 509 QPDFObjectHandle::getArrayNItems()
433 510 {
434   - assertArray();
435   - return dynamic_cast<QPDF_Array*>(m->obj.getPointer())->getNItems();
  511 + if (isArray())
  512 + {
  513 + return dynamic_cast<QPDF_Array*>(m->obj.getPointer())->getNItems();
  514 + }
  515 + else
  516 + {
  517 + typeWarning("array", "treating as empty");
  518 + QTC::TC("qpdf", "QPDFObjectHandle array treating as empty");
  519 + return 0;
  520 + }
436 521 }
437 522  
438 523 QPDFObjectHandle
439 524 QPDFObjectHandle::getArrayItem(int n)
440 525 {
441   - assertArray();
442   - return dynamic_cast<QPDF_Array*>(m->obj.getPointer())->getItem(n);
  526 + QPDFObjectHandle result;
  527 + if (isArray() && (n < getArrayNItems()) && (n >= 0))
  528 + {
  529 + result = dynamic_cast<QPDF_Array*>(m->obj.getPointer())->getItem(n);
  530 + }
  531 + else
  532 + {
  533 + result = newNull();
  534 + if (isArray())
  535 + {
  536 + objectWarning("returning null for out of bounds array access");
  537 + QTC::TC("qpdf", "QPDFObjectHandle array bounds");
  538 + }
  539 + else
  540 + {
  541 + typeWarning("array", "returning null");
  542 + QTC::TC("qpdf", "QPDFObjectHandle array null for non-array");
  543 + }
  544 + QPDF* context = 0;
  545 + std::string description;
  546 + if (this->m->obj->getDescription(context, description))
  547 + {
  548 + result.setObjectDescription(
  549 + context,
  550 + description +
  551 + " -> null returned from invalid array access");
  552 + }
  553 + }
  554 + return result;
443 555 }
444 556  
445 557 std::vector<QPDFObjectHandle>
446 558 QPDFObjectHandle::getArrayAsVector()
447 559 {
448   - assertArray();
449   - return dynamic_cast<QPDF_Array*>(m->obj.getPointer())->getAsVector();
  560 + std::vector<QPDFObjectHandle> result;
  561 + if (isArray())
  562 + {
  563 + result = dynamic_cast<QPDF_Array*>(m->obj.getPointer())->getAsVector();
  564 + }
  565 + else
  566 + {
  567 + typeWarning("array", "treating as empty");
  568 + QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector");
  569 + }
  570 + return result;
450 571 }
451 572  
452 573 // Array mutators
... ... @@ -454,36 +575,79 @@ QPDFObjectHandle::getArrayAsVector()
454 575 void
455 576 QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item)
456 577 {
457   - assertArray();
458   - return dynamic_cast<QPDF_Array*>(m->obj.getPointer())->setItem(n, item);
  578 + if (isArray())
  579 + {
  580 + dynamic_cast<QPDF_Array*>(m->obj.getPointer())->setItem(n, item);
  581 + }
  582 + else
  583 + {
  584 + typeWarning("array", "ignoring attempt to set item");
  585 + QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item");
  586 + }
459 587 }
460 588  
461 589 void
462 590 QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items)
463 591 {
464   - assertArray();
465   - return dynamic_cast<QPDF_Array*>(m->obj.getPointer())->setFromVector(items);
  592 + if (isArray())
  593 + {
  594 + dynamic_cast<QPDF_Array*>(m->obj.getPointer())->setFromVector(items);
  595 + }
  596 + else
  597 + {
  598 + typeWarning("array", "ignoring attempt to replace items");
  599 + QTC::TC("qpdf", "QPDFObjectHandle array ignoring replace items");
  600 + }
466 601 }
467 602  
468 603 void
469 604 QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item)
470 605 {
471   - assertArray();
472   - return dynamic_cast<QPDF_Array*>(m->obj.getPointer())->insertItem(at, item);
  606 + if (isArray())
  607 + {
  608 + dynamic_cast<QPDF_Array*>(m->obj.getPointer())->insertItem(at, item);
  609 + }
  610 + else
  611 + {
  612 + typeWarning("array", "ignoring attempt to insert item");
  613 + QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item");
  614 + }
473 615 }
474 616  
475 617 void
476 618 QPDFObjectHandle::appendItem(QPDFObjectHandle const& item)
477 619 {
478   - assertArray();
479   - return dynamic_cast<QPDF_Array*>(m->obj.getPointer())->appendItem(item);
  620 + if (isArray())
  621 + {
  622 + dynamic_cast<QPDF_Array*>(m->obj.getPointer())->appendItem(item);
  623 + }
  624 + else
  625 + {
  626 + typeWarning("array", "ignoring attempt to append item");
  627 + QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item");
  628 + }
480 629 }
481 630  
482 631 void
483 632 QPDFObjectHandle::eraseItem(int at)
484 633 {
485   - assertArray();
486   - return dynamic_cast<QPDF_Array*>(m->obj.getPointer())->eraseItem(at);
  634 + if (isArray() && (at < getArrayNItems()) && (at >= 0))
  635 + {
  636 + dynamic_cast<QPDF_Array*>(m->obj.getPointer())->eraseItem(at);
  637 + }
  638 + else
  639 + {
  640 + if (isArray())
  641 + {
  642 + objectWarning("ignoring attempt to erase out of bounds array item");
  643 + QTC::TC("qpdf", "QPDFObjectHandle erase array bounds");
  644 + }
  645 + else
  646 + {
  647 + typeWarning("array", "ignoring attempt to erase item");
  648 + QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item");
  649 + }
  650 + }
487 651 }
488 652  
489 653 // Dictionary accessors
... ... @@ -491,29 +655,79 @@ QPDFObjectHandle::eraseItem(int at)
491 655 bool
492 656 QPDFObjectHandle::hasKey(std::string const& key)
493 657 {
494   - assertDictionary();
495   - return dynamic_cast<QPDF_Dictionary*>(m->obj.getPointer())->hasKey(key);
  658 + if (isDictionary())
  659 + {
  660 + return dynamic_cast<QPDF_Dictionary*>(m->obj.getPointer())->hasKey(key);
  661 + }
  662 + else
  663 + {
  664 + typeWarning("dictionary",
  665 + "returning false for a key containment request");
  666 + QTC::TC("qpdf", "QPDFObjectHandle dictionary false for hasKey");
  667 + return false;
  668 + }
496 669 }
497 670  
498 671 QPDFObjectHandle
499 672 QPDFObjectHandle::getKey(std::string const& key)
500 673 {
501   - assertDictionary();
502   - return dynamic_cast<QPDF_Dictionary*>(m->obj.getPointer())->getKey(key);
  674 + QPDFObjectHandle result;
  675 + if (isDictionary())
  676 + {
  677 + result = dynamic_cast<QPDF_Dictionary*>(
  678 + m->obj.getPointer())->getKey(key);
  679 + }
  680 + else
  681 + {
  682 + typeWarning(
  683 + "dictionary", "returning null for attempted key retrieval");
  684 + QTC::TC("qpdf", "QPDFObjectHandle dictionary null for getKey");
  685 + result = newNull();
  686 + QPDF* qpdf = 0;
  687 + std::string description;
  688 + if (this->m->obj->getDescription(qpdf, description))
  689 + {
  690 + result.setObjectDescription(
  691 + qpdf,
  692 + description +
  693 + " -> null returned from getting key " +
  694 + key + " from non-Dictionary");
  695 + }
  696 + }
  697 + return result;
503 698 }
504 699  
505 700 std::set<std::string>
506 701 QPDFObjectHandle::getKeys()
507 702 {
508   - assertDictionary();
509   - return dynamic_cast<QPDF_Dictionary*>(m->obj.getPointer())->getKeys();
  703 + std::set<std::string> result;
  704 + if (isDictionary())
  705 + {
  706 + result = dynamic_cast<QPDF_Dictionary*>(m->obj.getPointer())->getKeys();
  707 + }
  708 + else
  709 + {
  710 + typeWarning("dictionary", "treating as empty");
  711 + QTC::TC("qpdf", "QPDFObjectHandle dictionary empty set for getKeys");
  712 + }
  713 + return result;
510 714 }
511 715  
512 716 std::map<std::string, QPDFObjectHandle>
513 717 QPDFObjectHandle::getDictAsMap()
514 718 {
515   - assertDictionary();
516   - return dynamic_cast<QPDF_Dictionary*>(m->obj.getPointer())->getAsMap();
  719 + std::map<std::string, QPDFObjectHandle> result;
  720 + if (isDictionary())
  721 + {
  722 + result = dynamic_cast<QPDF_Dictionary*>(
  723 + m->obj.getPointer())->getAsMap();
  724 + }
  725 + else
  726 + {
  727 + typeWarning("dictionary", "treating as empty");
  728 + QTC::TC("qpdf", "QPDFObjectHandle dictionary empty map for asMap");
  729 + }
  730 + return result;
517 731 }
518 732  
519 733 // Array and Name accessors
... ... @@ -551,27 +765,48 @@ QPDFObjectHandle::getOwningQPDF()
551 765  
552 766 void
553 767 QPDFObjectHandle::replaceKey(std::string const& key,
554   - QPDFObjectHandle const& value)
  768 + QPDFObjectHandle value)
555 769 {
556   - assertDictionary();
557   - return dynamic_cast<QPDF_Dictionary*>(
558   - m->obj.getPointer())->replaceKey(key, value);
  770 + if (isDictionary())
  771 + {
  772 + dynamic_cast<QPDF_Dictionary*>(
  773 + m->obj.getPointer())->replaceKey(key, value);
  774 + }
  775 + else
  776 + {
  777 + typeWarning("dictionary", "ignoring key replacement request");
  778 + QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring replaceKey");
  779 + }
559 780 }
560 781  
561 782 void
562 783 QPDFObjectHandle::removeKey(std::string const& key)
563 784 {
564   - assertDictionary();
565   - return dynamic_cast<QPDF_Dictionary*>(m->obj.getPointer())->removeKey(key);
  785 + if (isDictionary())
  786 + {
  787 + dynamic_cast<QPDF_Dictionary*>(m->obj.getPointer())->removeKey(key);
  788 + }
  789 + else
  790 + {
  791 + typeWarning("dictionary", "ignoring key removal request");
  792 + QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removeKey");
  793 + }
566 794 }
567 795  
568 796 void
569 797 QPDFObjectHandle::replaceOrRemoveKey(std::string const& key,
570 798 QPDFObjectHandle value)
571 799 {
572   - assertDictionary();
573   - return dynamic_cast<QPDF_Dictionary*>(
574   - m->obj.getPointer())->replaceOrRemoveKey(key, value);
  800 + if (isDictionary())
  801 + {
  802 + dynamic_cast<QPDF_Dictionary*>(
  803 + m->obj.getPointer())->replaceOrRemoveKey(key, value);
  804 + }
  805 + else
  806 + {
  807 + typeWarning("dictionary", "ignoring key removal/replacement request");
  808 + QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removereplace");
  809 + }
575 810 }
576 811  
577 812 // Stream accessors
... ... @@ -1173,35 +1408,45 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
1173 1408 std::vector<parser_state_e> state_stack;
1174 1409 state_stack.push_back(st_top);
1175 1410 std::vector<qpdf_offset_t> offset_stack;
1176   - offset_stack.push_back(input->tell());
  1411 + qpdf_offset_t offset = input->tell();
  1412 + offset_stack.push_back(offset);
1177 1413 bool done = false;
1178 1414 while (! done)
1179 1415 {
1180 1416 std::vector<QPDFObjectHandle>& olist = olist_stack.back();
1181 1417 parser_state_e state = state_stack.back();
1182   - qpdf_offset_t offset = offset_stack.back();
  1418 + offset = offset_stack.back();
1183 1419  
1184 1420 object = QPDFObjectHandle();
1185 1421  
1186 1422 QPDFTokenizer::Token token =
1187   - tokenizer.readToken(input, object_description);
  1423 + tokenizer.readToken(input, object_description, true);
1188 1424  
1189 1425 switch (token.getType())
1190 1426 {
1191 1427 case QPDFTokenizer::tt_eof:
1192   - if (content_stream)
  1428 + if (! content_stream)
1193 1429 {
1194   - state = st_eof;
1195   - }
1196   - else
1197   - {
1198   - // When not in content stream mode, EOF is tt_bad and
1199   - // throws an exception before we get here.
1200   - throw std::logic_error(
1201   - "EOF received while not in content stream mode");
  1430 + QTC::TC("qpdf", "QPDFObjectHandle eof in parseInternal");
  1431 + warn(context,
  1432 + QPDFExc(qpdf_e_damaged_pdf, input->getName(),
  1433 + object_description,
  1434 + input->getLastOffset(),
  1435 + "unexpected EOF"));
1202 1436 }
  1437 + state = st_eof;
1203 1438 break;
1204 1439  
  1440 + case QPDFTokenizer::tt_bad:
  1441 + QTC::TC("qpdf", "QPDFObjectHandle bad token in parse");
  1442 + warn(context,
  1443 + QPDFExc(qpdf_e_damaged_pdf, input->getName(),
  1444 + object_description,
  1445 + input->getLastOffset(),
  1446 + token.getErrorMessage()));
  1447 + object = newNull();
  1448 + break;
  1449 +
1205 1450 case QPDFTokenizer::tt_brace_open:
1206 1451 case QPDFTokenizer::tt_brace_close:
1207 1452 QTC::TC("qpdf", "QPDFObjectHandle bad brace");
... ... @@ -1375,11 +1620,19 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
1375 1620 "parse error while reading object"));
1376 1621 }
1377 1622 done = true;
1378   - // Leave object uninitialized to indicate EOF
  1623 + // In content stream mode, leave object uninitialized to
  1624 + // indicate EOF
  1625 + if (! content_stream)
  1626 + {
  1627 + object = newNull();
  1628 + }
1379 1629 break;
1380 1630  
1381 1631 case st_dictionary:
1382 1632 case st_array:
  1633 + setObjectDescriptionFromInput(
  1634 + object, context, object_description, input,
  1635 + input->getLastOffset());
1383 1636 olist.push_back(object);
1384 1637 break;
1385 1638  
... ... @@ -1402,6 +1655,8 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
1402 1655 if (old_state == st_array)
1403 1656 {
1404 1657 object = newArray(olist);
  1658 + setObjectDescriptionFromInput(
  1659 + object, context, object_description, input, offset);
1405 1660 }
1406 1661 else if (old_state == st_dictionary)
1407 1662 {
... ... @@ -1458,6 +1713,8 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
1458 1713 "dictionary ended prematurely; "
1459 1714 "using null as value for last key"));
1460 1715 val = newNull();
  1716 + setObjectDescriptionFromInput(
  1717 + val, context, object_description, input, offset);
1461 1718 }
1462 1719 else
1463 1720 {
... ... @@ -1466,6 +1723,8 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
1466 1723 dict[key_obj.getName()] = val;
1467 1724 }
1468 1725 object = newDictionary(dict);
  1726 + setObjectDescriptionFromInput(
  1727 + object, context, object_description, input, offset);
1469 1728 }
1470 1729 olist_stack.pop_back();
1471 1730 offset_stack.pop_back();
... ... @@ -1480,6 +1739,8 @@ QPDFObjectHandle::parseInternal(PointerHolder&lt;InputSource&gt; input,
1480 1739 }
1481 1740 }
1482 1741  
  1742 + setObjectDescriptionFromInput(
  1743 + object, context, object_description, input, offset);
1483 1744 return object;
1484 1745 }
1485 1746  
... ... @@ -1635,6 +1896,26 @@ QPDFObjectHandle::newReserved(QPDF* qpdf)
1635 1896 return result;
1636 1897 }
1637 1898  
  1899 +void
  1900 +QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf,
  1901 + std::string const& object_description)
  1902 +{
  1903 + if (isInitialized() && this->m->obj.getPointer())
  1904 + {
  1905 + this->m->obj->setDescription(owning_qpdf, object_description);
  1906 + }
  1907 +}
  1908 +
  1909 +bool
  1910 +QPDFObjectHandle::hasObjectDescription()
  1911 +{
  1912 + if (isInitialized() && this->m->obj.getPointer())
  1913 + {
  1914 + return this->m->obj->hasDescription();
  1915 + }
  1916 + return false;
  1917 +}
  1918 +
1638 1919 QPDFObjectHandle
1639 1920 QPDFObjectHandle::shallowCopy()
1640 1921 {
... ... @@ -1793,85 +2074,127 @@ QPDFObjectHandle::assertInitialized() const
1793 2074 }
1794 2075  
1795 2076 void
1796   -QPDFObjectHandle::assertType(char const* type_name, bool istype) const
  2077 +QPDFObjectHandle::typeWarning(char const* expected_type,
  2078 + std::string const& warning)
  2079 +{
  2080 + QPDF* context = 0;
  2081 + std::string description;
  2082 + if (this->m->obj->getDescription(context, description))
  2083 + {
  2084 + warn(context,
  2085 + QPDFExc(
  2086 + qpdf_e_damaged_pdf,
  2087 + "", description, 0,
  2088 + std::string("operation for ") + expected_type +
  2089 + " attempted on object of type " +
  2090 + getTypeName() + ": " + warning));
  2091 + }
  2092 + else
  2093 + {
  2094 + assertType(expected_type, false);
  2095 + }
  2096 +}
  2097 +
  2098 +void
  2099 +QPDFObjectHandle::objectWarning(std::string const& warning)
  2100 +{
  2101 + QPDF* context = 0;
  2102 + std::string description;
  2103 + if (this->m->obj->getDescription(context, description))
  2104 + {
  2105 + warn(context,
  2106 + QPDFExc(
  2107 + qpdf_e_damaged_pdf,
  2108 + "", description, 0,
  2109 + warning));
  2110 + }
  2111 + else
  2112 + {
  2113 + throw std::logic_error(warning);
  2114 + }
  2115 +}
  2116 +
  2117 +void
  2118 +QPDFObjectHandle::assertType(char const* type_name, bool istype)
1797 2119 {
1798 2120 if (! istype)
1799 2121 {
1800 2122 throw std::logic_error(std::string("operation for ") + type_name +
1801   - " object attempted on object of wrong type");
  2123 + " attempted on object of type " +
  2124 + getTypeName());
1802 2125 }
1803 2126 }
1804 2127  
1805 2128 void
1806 2129 QPDFObjectHandle::assertNull()
1807 2130 {
1808   - assertType("Null", isNull());
  2131 + assertType("null", isNull());
1809 2132 }
1810 2133  
1811 2134 void
1812 2135 QPDFObjectHandle::assertBool()
1813 2136 {
1814   - assertType("Boolean", isBool());
  2137 + assertType("boolean", isBool());
1815 2138 }
1816 2139  
1817 2140 void
1818 2141 QPDFObjectHandle::assertInteger()
1819 2142 {
1820   - assertType("Integer", isInteger());
  2143 + assertType("integer", isInteger());
1821 2144 }
1822 2145  
1823 2146 void
1824 2147 QPDFObjectHandle::assertReal()
1825 2148 {
1826   - assertType("Real", isReal());
  2149 + assertType("real", isReal());
1827 2150 }
1828 2151  
1829 2152 void
1830 2153 QPDFObjectHandle::assertName()
1831 2154 {
1832   - assertType("Name", isName());
  2155 + assertType("name", isName());
1833 2156 }
1834 2157  
1835 2158 void
1836 2159 QPDFObjectHandle::assertString()
1837 2160 {
1838   - assertType("String", isString());
  2161 + assertType("string", isString());
1839 2162 }
1840 2163  
1841 2164 void
1842 2165 QPDFObjectHandle::assertOperator()
1843 2166 {
1844   - assertType("Operator", isOperator());
  2167 + assertType("operator", isOperator());
1845 2168 }
1846 2169  
1847 2170 void
1848 2171 QPDFObjectHandle::assertInlineImage()
1849 2172 {
1850   - assertType("InlineImage", isInlineImage());
  2173 + assertType("inlineimage", isInlineImage());
1851 2174 }
1852 2175  
1853 2176 void
1854 2177 QPDFObjectHandle::assertArray()
1855 2178 {
1856   - assertType("Array", isArray());
  2179 + assertType("array", isArray());
1857 2180 }
1858 2181  
1859 2182 void
1860 2183 QPDFObjectHandle::assertDictionary()
1861 2184 {
1862   - assertType("Dictionary", isDictionary());
  2185 + assertType("dictionary", isDictionary());
1863 2186 }
1864 2187  
1865 2188 void
1866 2189 QPDFObjectHandle::assertStream()
1867 2190 {
1868   - assertType("Stream", isStream());
  2191 + assertType("stream", isStream());
1869 2192 }
1870 2193  
1871 2194 void
1872 2195 QPDFObjectHandle::assertReserved()
1873 2196 {
1874   - assertType("Reserved", isReserved());
  2197 + assertType("reserved", isReserved());
1875 2198 }
1876 2199  
1877 2200 void
... ... @@ -1887,13 +2210,13 @@ QPDFObjectHandle::assertIndirect()
1887 2210 void
1888 2211 QPDFObjectHandle::assertScalar()
1889 2212 {
1890   - assertType("Scalar", isScalar());
  2213 + assertType("scalar", isScalar());
1891 2214 }
1892 2215  
1893 2216 void
1894 2217 QPDFObjectHandle::assertNumber()
1895 2218 {
1896   - assertType("Number", isNumber());
  2219 + assertType("number", isNumber());
1897 2220 }
1898 2221  
1899 2222 bool
... ... @@ -1928,7 +2251,8 @@ QPDFObjectHandle::dereference()
1928 2251 this->m->qpdf, this->m->objid, this->m->generation);
1929 2252 if (obj.getPointer() == 0)
1930 2253 {
1931   - QTC::TC("qpdf", "QPDFObjectHandle indirect to unknown");
  2254 + // QPDF::resolve never returns an uninitialized object, but
  2255 + // check just in case.
1932 2256 this->m->obj = new QPDF_Null();
1933 2257 }
1934 2258 else if (dynamic_cast<QPDF_Reserved*>(obj.getPointer()))
... ...
libqpdf/QPDFTokenizer.cc
... ... @@ -640,7 +640,9 @@ QPDFTokenizer::readToken(PointerHolder&lt;InputSource&gt; input,
640 640 presented_eof = true;
641 641 if ((this->m->type == tt_eof) && (! this->m->allow_eof))
642 642 {
643   - QTC::TC("qpdf", "QPDFTokenizer EOF when not allowed");
  643 + // Nothing in the qpdf library calls readToken
  644 + // without allowEOF anymore, so this case is not
  645 + // exercised.
644 646 this->m->type = tt_bad;
645 647 this->m->error_message = "unexpected EOF";
646 648 offset = input->getLastOffset();
... ... @@ -677,7 +679,10 @@ QPDFTokenizer::readToken(PointerHolder&lt;InputSource&gt; input,
677 679 input->unreadCh(char_to_unread);
678 680 }
679 681  
680   - input->setLastOffset(offset);
  682 + if (token.getType() != tt_eof)
  683 + {
  684 + input->setLastOffset(offset);
  685 + }
681 686  
682 687 if (token.getType() == tt_bad)
683 688 {
... ...
libqpdf/QPDF_Array.cc
1 1 #include <qpdf/QPDF_Array.hh>
  2 +#include <qpdf/QUtil.hh>
2 3 #include <stdexcept>
3 4  
4 5 QPDF_Array::QPDF_Array(std::vector<QPDFObjectHandle> const& items) :
... ... @@ -46,6 +47,12 @@ QPDF_Array::getTypeName() const
46 47 return "array";
47 48 }
48 49  
  50 +void
  51 +QPDF_Array::setDescription(QPDF* qpdf, std::string const& description)
  52 +{
  53 + this->QPDFObject::setDescription(qpdf, description);
  54 +}
  55 +
49 56 int
50 57 QPDF_Array::getNItems() const
51 58 {
... ...
libqpdf/QPDF_Dictionary.cc
... ... @@ -51,6 +51,12 @@ QPDF_Dictionary::getTypeName() const
51 51 return "dictionary";
52 52 }
53 53  
  54 +void
  55 +QPDF_Dictionary::setDescription(QPDF* qpdf, std::string const& description)
  56 +{
  57 + this->QPDFObject::setDescription(qpdf, description);
  58 +}
  59 +
54 60 bool
55 61 QPDF_Dictionary::hasKey(std::string const& key)
56 62 {
... ... @@ -70,7 +76,15 @@ QPDF_Dictionary::getKey(std::string const&amp; key)
70 76 }
71 77 else
72 78 {
73   - return QPDFObjectHandle::newNull();
  79 + QPDFObjectHandle null = QPDFObjectHandle::newNull();
  80 + QPDF* qpdf = 0;
  81 + std::string description;
  82 + if (getDescription(qpdf, description))
  83 + {
  84 + null.setObjectDescription(
  85 + qpdf, description + " -> dictionary key " + key);
  86 + }
  87 + return null;
74 88 }
75 89 }
76 90  
... ... @@ -93,13 +107,12 @@ QPDF_Dictionary::getKeys()
93 107 std::map<std::string, QPDFObjectHandle> const&
94 108 QPDF_Dictionary::getAsMap() const
95 109 {
96   -
97 110 return this->items;
98 111 }
99 112  
100 113 void
101 114 QPDF_Dictionary::replaceKey(std::string const& key,
102   - QPDFObjectHandle const& value)
  115 + QPDFObjectHandle value)
103 116 {
104 117 // add or replace value
105 118 this->items[key] = value;
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -39,6 +39,7 @@ QPDF_Stream::QPDF_Stream(QPDF* qpdf, int objid, int generation,
39 39 "stream object instantiated with non-dictionary "
40 40 "object for dictionary");
41 41 }
  42 + setStreamDescription();
42 43 }
43 44  
44 45 QPDF_Stream::~QPDF_Stream()
... ... @@ -85,6 +86,35 @@ QPDF_Stream::getTypeName() const
85 86 return "stream";
86 87 }
87 88  
  89 +void
  90 +QPDF_Stream::setDescription(QPDF* qpdf, std::string const& description)
  91 +{
  92 + this->QPDFObject::setDescription(qpdf, description);
  93 + setDictDescription();
  94 +}
  95 +
  96 +void
  97 +QPDF_Stream::setStreamDescription()
  98 +{
  99 + setDescription(
  100 + this->qpdf,
  101 + "stream object " + QUtil::int_to_string(this->objid) + " " +
  102 + QUtil::int_to_string(this->generation));
  103 +}
  104 +
  105 +void
  106 +QPDF_Stream::setDictDescription()
  107 +{
  108 + QPDF* qpdf = 0;
  109 + std::string description;
  110 + if ((! this->stream_dict.hasObjectDescription()) &&
  111 + getDescription(qpdf, description))
  112 + {
  113 + this->stream_dict.setObjectDescription(
  114 + qpdf, description + " -> stream dictionary");
  115 + }
  116 +}
  117 +
88 118 QPDFObjectHandle
89 119 QPDF_Stream::getDict() const
90 120 {
... ... @@ -688,6 +718,7 @@ void
688 718 QPDF_Stream::replaceDict(QPDFObjectHandle new_dict)
689 719 {
690 720 this->stream_dict = new_dict;
  721 + setDictDescription();
691 722 QPDFObjectHandle length_obj = new_dict.getKey("/Length");
692 723 if (length_obj.isInteger())
693 724 {
... ...
libqpdf/QPDF_linearization.cc
... ... @@ -121,10 +121,10 @@ QPDF::isLinearized()
121 121 ++p;
122 122 }
123 123  
124   - QPDFTokenizer::Token t1 = readToken(this->m->file, true);
125   - QPDFTokenizer::Token t2 = readToken(this->m->file, true);
126   - QPDFTokenizer::Token t3 = readToken(this->m->file, true);
127   - QPDFTokenizer::Token t4 = readToken(this->m->file, true);
  124 + QPDFTokenizer::Token t1 = readToken(this->m->file);
  125 + QPDFTokenizer::Token t2 = readToken(this->m->file);
  126 + QPDFTokenizer::Token t3 = readToken(this->m->file);
  127 + QPDFTokenizer::Token t4 = readToken(this->m->file);
128 128 if ((t1.getType() == QPDFTokenizer::tt_integer) &&
129 129 (t2.getType() == QPDFTokenizer::tt_integer) &&
130 130 (t3 == QPDFTokenizer::Token(QPDFTokenizer::tt_word, "obj")) &&
... ...
libqpdf/qpdf/QPDF_Array.hh
... ... @@ -14,6 +14,7 @@ class QPDF_Array: public QPDFObject
14 14 virtual std::string unparse();
15 15 virtual QPDFObject::object_type_e getTypeCode() const;
16 16 virtual char const* getTypeName() const;
  17 + virtual void setDescription(QPDF*, std::string const&);
17 18  
18 19 int getNItems() const;
19 20 QPDFObjectHandle getItem(int n) const;
... ...
libqpdf/qpdf/QPDF_Dictionary.hh
... ... @@ -16,6 +16,7 @@ class QPDF_Dictionary: public QPDFObject
16 16 virtual std::string unparse();
17 17 virtual QPDFObject::object_type_e getTypeCode() const;
18 18 virtual char const* getTypeName() const;
  19 + virtual void setDescription(QPDF*, std::string const&);
19 20  
20 21 // hasKey() and getKeys() treat keys with null values as if they
21 22 // aren't there. getKey() returns null for the value of a
... ... @@ -26,7 +27,7 @@ class QPDF_Dictionary: public QPDFObject
26 27 std::map<std::string, QPDFObjectHandle> const& getAsMap() const;
27 28  
28 29 // Replace value of key, adding it if it does not exist
29   - void replaceKey(std::string const& key, QPDFObjectHandle const&);
  30 + void replaceKey(std::string const& key, QPDFObjectHandle);
30 31 // Remove key, doing nothing if key does not exist
31 32 void removeKey(std::string const& key);
32 33 // If object is null, replace key; otherwise, remove key
... ...
libqpdf/qpdf/QPDF_Stream.hh
... ... @@ -19,6 +19,7 @@ class QPDF_Stream: public QPDFObject
19 19 virtual std::string unparse();
20 20 virtual QPDFObject::object_type_e getTypeCode() const;
21 21 virtual char const* getTypeName() const;
  22 + virtual void setDescription(QPDF*, std::string const&);
22 23 QPDFObjectHandle getDict() const;
23 24 bool isDataModified() const;
24 25  
... ... @@ -66,6 +67,8 @@ class QPDF_Stream: public QPDFObject
66 67 int& colors, int& bits_per_component,
67 68 bool& early_code_change);
68 69 void warn(QPDFExc const& e);
  70 + void setDictDescription();
  71 + void setStreamDescription();
69 72  
70 73 QPDF* qpdf;
71 74 int objid;
... ...
qpdf/qpdf.testcov
... ... @@ -105,7 +105,6 @@ QPDF reconstructed xref table 0
105 105 QPDF recovered in readObjectAtOffset 0
106 106 QPDF recovered stream length 0
107 107 QPDF found wrong endstream in recovery 0
108   -QPDFObjectHandle indirect to unknown 0
109 108 QPDF_Stream pipeStreamData with null pipeline 0
110 109 QPDFWriter not recompressing /FlateDecode 0
111 110 QPDF_encryption xref stream from encrypted file 0
... ... @@ -300,10 +299,37 @@ qpdf-c called qpdf_set_compress_streams 0
300 299 qpdf-c called qpdf_set_preserve_unreferenced_objects 0
301 300 qpdf-c called qpdf_set_newline_before_endstream 0
302 301 QPDF_Stream TIFF predictor 0
303   -QPDFTokenizer EOF when not allowed 0
304 302 QPDFTokenizer inline image at EOF 0
305 303 Pl_QPDFTokenizer found ID 0
306 304 QPDFObjectHandle non-stream in stream array 0
307 305 QPDFObjectHandle coalesce called on stream 0
308 306 QPDFObjectHandle coalesce provide stream data 0
309 307 QPDF_Stream bad token at end during normalize 0
  308 +QPDFObjectHandle bad token in parse 0
  309 +QPDFObjectHandle eof in parseInternal 0
  310 +QPDFObjectHandle array bounds 0
  311 +QPDFObjectHandle boolean returning false 0
  312 +QPDFObjectHandle integer returning 0 0
  313 +QPDFObjectHandle real returning 0.0 0
  314 +QPDFObjectHandle name returning dummy name 0
  315 +QPDFObjectHandle string returning empty string 0
  316 +QPDFObjectHandle string returning empty utf8 0
  317 +QPDFObjectHandle operator returning fake value 0
  318 +QPDFObjectHandle inlineimage returning empty data 0
  319 +QPDFObjectHandle array treating as empty 0
  320 +QPDFObjectHandle array null for non-array 0
  321 +QPDFObjectHandle array treating as empty vector 0
  322 +QPDFObjectHandle array ignoring set item 0
  323 +QPDFObjectHandle array ignoring replace items 0
  324 +QPDFObjectHandle array ignoring insert item 0
  325 +QPDFObjectHandle array ignoring append item 0
  326 +QPDFObjectHandle array ignoring erase item 0
  327 +QPDFObjectHandle dictionary false for hasKey 0
  328 +QPDFObjectHandle dictionary null for getKey 0
  329 +QPDFObjectHandle dictionary empty set for getKeys 0
  330 +QPDFObjectHandle dictionary empty map for asMap 0
  331 +QPDFObjectHandle dictionary ignoring replaceKey 0
  332 +QPDFObjectHandle dictionary ignoring removeKey 0
  333 +QPDFObjectHandle dictionary ignoring removereplace 0
  334 +QPDFObjectHandle numeric non-numeric 0
  335 +QPDFObjectHandle erase array bounds 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -212,7 +212,7 @@ my @bug_tests = (
212 212 ["99", "object 0", 2],
213 213 ["99b", "object 0", 2],
214 214 ["100", "xref reconstruction loop", 2],
215   - ["101", "resolve for exception text", 2],
  215 + ["101", "resolve for exception text", 3],
216 216 ["117", "other infinite loop", 2],
217 217 ["118", "other infinite loop", 2],
218 218 ["119", "other infinite loop", 3],
... ... @@ -736,6 +736,33 @@ $td-&gt;runtest(&quot;stream with tiff predictor&quot;,
736 736  
737 737 show_ntests();
738 738 # ----------
  739 +$td->notify("--- Type checks ---");
  740 +$n_tests += 4;
  741 +# Whenever object-types.pdf is edited, object-types-os.pdf should be
  742 +# regenerated.
  743 +$td->runtest("ensure object-types-os is up-to-date",
  744 + {$td->COMMAND =>
  745 + "qpdf" .
  746 + " --object-streams=generate" .
  747 + " --deterministic-id" .
  748 + " --stream-data=uncompress" .
  749 + " object-types.pdf a.pdf"},
  750 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  751 +$td->runtest("check file",
  752 + {$td->FILE => "a.pdf"},
  753 + {$td->FILE => "object-types-os.pdf"});
  754 +$td->runtest("type checks",
  755 + {$td->COMMAND => "test_driver 42 object-types.pdf"},
  756 + {$td->FILE => "object-types.out",
  757 + $td->EXIT_STATUS => 0},
  758 + $td->NORMALIZE_NEWLINES);
  759 +$td->runtest("type checks with object streams",
  760 + {$td->COMMAND => "test_driver 42 object-types-os.pdf"},
  761 + {$td->FILE => "object-types-os.out",
  762 + $td->EXIT_STATUS => 0},
  763 + $td->NORMALIZE_NEWLINES);
  764 +
  765 +# ----------
739 766 $td->notify("--- Coalesce contents ---");
740 767 $n_tests += 6;
741 768  
... ... @@ -1200,7 +1227,7 @@ $n_tests += @badfiles + 3;
1200 1227 # have error conditions that used to be fatal but are now considered
1201 1228 # non-fatal.
1202 1229 my %badtest_overrides = ();
1203   -for(6, 12..15, 17, 22..28, 30..32, 34, 36)
  1230 +for(6, 12..15, 17, 18..32, 34, 36)
1204 1231 {
1205 1232 $badtest_overrides{$_} = 0;
1206 1233 }
... ... @@ -1243,7 +1270,7 @@ $n_tests += @badfiles + 6;
1243 1270 # though in some cases it may. Acrobat Reader would not be able to
1244 1271 # recover any of these files any better.
1245 1272 my %recover_failures = ();
1246   -for (1, 7, 16, 18..21, 29, 35)
  1273 +for (1, 7, 16, 35)
1247 1274 {
1248 1275 $recover_failures{$_} = 1;
1249 1276 }
... ...
qpdf/qtest/qpdf/bad16-recover.out
1 1 WARNING: bad16.pdf (trailer, offset 753): unexpected dictionary close token
2 2 WARNING: bad16.pdf (trailer, offset 756): unexpected dictionary close token
3 3 WARNING: bad16.pdf (trailer, offset 759): unknown token while reading object; treating as string
4   -WARNING: bad16.pdf: file is damaged
5 4 WARNING: bad16.pdf (trailer, offset 779): unexpected EOF
  5 +WARNING: bad16.pdf (trailer, offset 779): parse error while reading object
  6 +WARNING: bad16.pdf: file is damaged
  7 +WARNING: bad16.pdf (offset 712): expected trailer dictionary
6 8 WARNING: bad16.pdf: Attempting to reconstruct cross-reference table
7 9 WARNING: bad16.pdf (trailer, offset 753): unexpected dictionary close token
8 10 WARNING: bad16.pdf (trailer, offset 756): unexpected dictionary close token
9 11 WARNING: bad16.pdf (trailer, offset 759): unknown token while reading object; treating as string
10   -bad16.pdf (trailer, offset 779): unexpected EOF
  12 +WARNING: bad16.pdf (trailer, offset 779): unexpected EOF
  13 +WARNING: bad16.pdf (trailer, offset 779): parse error while reading object
  14 +bad16.pdf: unable to find trailer dictionary while recovering damaged file
... ...
qpdf/qtest/qpdf/bad16.out
1 1 WARNING: bad16.pdf (trailer, offset 753): unexpected dictionary close token
2 2 WARNING: bad16.pdf (trailer, offset 756): unexpected dictionary close token
3 3 WARNING: bad16.pdf (trailer, offset 759): unknown token while reading object; treating as string
4   -bad16.pdf (trailer, offset 779): unexpected EOF
  4 +WARNING: bad16.pdf (trailer, offset 779): unexpected EOF
  5 +WARNING: bad16.pdf (trailer, offset 779): parse error while reading object
  6 +bad16.pdf (offset 712): expected trailer dictionary
... ...
qpdf/qtest/qpdf/bad18-recover.out
1   -WARNING: bad18.pdf: file is damaged
2 1 WARNING: bad18.pdf (trailer, offset 753): unexpected )
3   -WARNING: bad18.pdf: Attempting to reconstruct cross-reference table
4   -bad18.pdf (trailer, offset 753): unexpected )
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 1 done
... ...
qpdf/qtest/qpdf/bad18.out
1   -bad18.pdf (trailer, offset 753): unexpected )
  1 +WARNING: bad18.pdf (trailer, offset 753): unexpected )
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 0 done
... ...
qpdf/qtest/qpdf/bad19-recover.out
1   -WARNING: bad19.pdf: file is damaged
2 1 WARNING: bad19.pdf (trailer, offset 753): unexpected >
3   -WARNING: bad19.pdf: Attempting to reconstruct cross-reference table
4   -bad19.pdf (trailer, offset 753): unexpected >
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 1 done
... ...
qpdf/qtest/qpdf/bad19.out
1   -bad19.pdf (trailer, offset 753): unexpected >
  1 +WARNING: bad19.pdf (trailer, offset 753): unexpected >
  2 +/QTest is implicit
  3 +/QTest is direct and has type null (2)
  4 +/QTest is null
  5 +unparse: null
  6 +unparseResolved: null
  7 +test 0 done
... ...
qpdf/qtest/qpdf/bad20-recover.out
1   -WARNING: bad20.pdf: file is damaged
2 1 WARNING: bad20.pdf (trailer, offset 753): invalid character (q) in hexstring
3   -WARNING: bad20.pdf: Attempting to reconstruct cross-reference table
4   -bad20.pdf (trailer, offset 753): invalid character (q) in hexstring
  2 +WARNING: bad20.pdf (trailer, offset 757): unknown token while reading object; treating as string
  3 +WARNING: bad20.pdf (trailer, offset 758): unexpected >
  4 +WARNING: bad20.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1
  5 +WARNING: bad20.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2
  6 +/QTest is implicit
  7 +/QTest is direct and has type null (2)
  8 +/QTest is null
  9 +unparse: null
  10 +unparseResolved: null
  11 +test 1 done
... ...
qpdf/qtest/qpdf/bad20.out
1   -bad20.pdf (trailer, offset 753): invalid character (q) in hexstring
  1 +WARNING: bad20.pdf (trailer, offset 753): invalid character (q) in hexstring
  2 +WARNING: bad20.pdf (trailer, offset 757): unknown token while reading object; treating as string
  3 +WARNING: bad20.pdf (trailer, offset 758): unexpected >
  4 +WARNING: bad20.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1
  5 +WARNING: bad20.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2
  6 +/QTest is implicit
  7 +/QTest is direct and has type null (2)
  8 +/QTest is null
  9 +unparse: null
  10 +unparseResolved: null
  11 +test 0 done
... ...
qpdf/qtest/qpdf/bad21-recover.out
1   -WARNING: bad21.pdf: file is damaged
2 1 WARNING: bad21.pdf (trailer, offset 742): invalid name token
3   -WARNING: bad21.pdf: Attempting to reconstruct cross-reference table
4   -bad21.pdf (trailer, offset 742): invalid name token
  2 +WARNING: bad21.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1
  3 +WARNING: bad21.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2
  4 +/QTest is implicit
  5 +/QTest is direct and has type null (2)
  6 +/QTest is null
  7 +unparse: null
  8 +unparseResolved: null
  9 +test 1 done
... ...
qpdf/qtest/qpdf/bad21.out
1   -bad21.pdf (trailer, offset 742): invalid name token
  1 +WARNING: bad21.pdf (trailer, offset 742): invalid name token
  2 +WARNING: bad21.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1
  3 +WARNING: bad21.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2
  4 +/QTest is implicit
  5 +/QTest is direct and has type null (2)
  6 +/QTest is null
  7 +unparse: null
  8 +unparseResolved: null
  9 +test 0 done
... ...
qpdf/qtest/qpdf/bad29-recover.out
1   -WARNING: bad29.pdf: file is damaged
2 1 WARNING: bad29.pdf (trailer, offset 742): null character not allowed in name token
3   -WARNING: bad29.pdf: Attempting to reconstruct cross-reference table
4   -bad29.pdf (trailer, offset 742): null character not allowed in name token
  2 +WARNING: bad29.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1
  3 +WARNING: bad29.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2
  4 +/QTest is implicit
  5 +/QTest is direct and has type null (2)
  6 +/QTest is null
  7 +unparse: null
  8 +unparseResolved: null
  9 +test 1 done
... ...
qpdf/qtest/qpdf/bad29.out
1   -bad29.pdf (trailer, offset 742): null character not allowed in name token
  1 +WARNING: bad29.pdf (trailer, offset 742): null character not allowed in name token
  2 +WARNING: bad29.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake1
  3 +WARNING: bad29.pdf (trailer, offset 715): expected dictionary key but found non-name object; inserting key /QPDFFake2
  4 +/QTest is implicit
  5 +/QTest is direct and has type null (2)
  6 +/QTest is null
  7 +unparse: null
  8 +unparseResolved: null
  9 +test 0 done
... ...
qpdf/qtest/qpdf/issue-100.out
... ... @@ -8,6 +8,9 @@ WARNING: issue-100.pdf (object 5 0, offset 294): unknown token while reading obj
8 8 WARNING: issue-100.pdf (object 5 0, offset 297): unknown token while reading object; treating as string
9 9 WARNING: issue-100.pdf (object 5 0, offset 304): unknown token while reading object; treating as string
10 10 WARNING: issue-100.pdf (object 5 0, offset 308): unexpected )
  11 +WARNING: issue-100.pdf (object 5 0, offset 316): treating unexpected array close token as null
  12 +WARNING: issue-100.pdf (object 5 0, offset 227): expected dictionary key but found non-name object; inserting key /QPDFFake1
  13 +WARNING: issue-100.pdf (object 5 0, offset 321): expected endobj
11 14 WARNING: issue-100.pdf (object 5 0, offset 418): /Length key in stream dictionary is not an integer
12 15 WARNING: issue-100.pdf (object 5 0, offset 489): attempting to recover stream length
13 16 WARNING: issue-100.pdf (object 5 0, offset 489): recovered stream length: 12
... ...
qpdf/qtest/qpdf/issue-101.out
... ... @@ -56,4 +56,72 @@ WARNING: issue-101.pdf (object 11 0, offset 811): unknown token while reading ob
56 56 WARNING: issue-101.pdf (object 11 0, offset 819): unknown token while reading object; treating as string
57 57 WARNING: issue-101.pdf (object 11 0, offset 832): unknown token while reading object; treating as string
58 58 WARNING: issue-101.pdf (object 11 0, offset 856): unexpected >
59   -issue-101.pdf (offset 856): unable to find /Root dictionary
  59 +WARNING: issue-101.pdf (object 11 0, offset 857): unknown token while reading object; treating as string
  60 +WARNING: issue-101.pdf (object 11 0, offset 868): unknown token while reading object; treating as string
  61 +WARNING: issue-101.pdf (object 11 0, offset 887): unknown token while reading object; treating as string
  62 +WARNING: issue-101.pdf (object 11 0, offset 897): unexpected )
  63 +WARNING: issue-101.pdf (object 11 0, offset 898): unknown token while reading object; treating as string
  64 +WARNING: issue-101.pdf (object 11 0, offset 909): invalid character (ยค) in hexstring
  65 +WARNING: issue-101.pdf (object 11 0, offset 911): unknown token while reading object; treating as string
  66 +WARNING: issue-101.pdf (object 11 0, offset 929): unknown token while reading object; treating as string
  67 +WARNING: issue-101.pdf (object 11 0, offset 930): invalid character (ยฒ) in hexstring
  68 +WARNING: issue-101.pdf (object 11 0, offset 932): unknown token while reading object; treating as string
  69 +WARNING: issue-101.pdf (object 11 0, offset 944): unknown token while reading object; treating as string
  70 +WARNING: issue-101.pdf (object 11 0, offset 947): unknown token while reading object; treating as string
  71 +WARNING: issue-101.pdf (object 11 0, offset 970): unknown token while reading object; treating as string
  72 +WARNING: issue-101.pdf (object 11 0, offset 1046): unknown token while reading object; treating as string
  73 +WARNING: issue-101.pdf (object 11 0, offset 1067): unknown token while reading object; treating as string
  74 +WARNING: issue-101.pdf (object 11 0, offset 1075): unknown token while reading object; treating as string
  75 +WARNING: issue-101.pdf (object 11 0, offset 1080): unknown token while reading object; treating as string
  76 +WARNING: issue-101.pdf (object 11 0, offset 1084): unknown token while reading object; treating as string
  77 +WARNING: issue-101.pdf (object 11 0, offset 1102): unknown token while reading object; treating as string
  78 +WARNING: issue-101.pdf (object 11 0, offset 1112): unknown token while reading object; treating as string
  79 +WARNING: issue-101.pdf (object 11 0, offset 1124): unknown token while reading object; treating as string
  80 +WARNING: issue-101.pdf (object 11 0, offset 1133): unknown token while reading object; treating as string
  81 +WARNING: issue-101.pdf (object 11 0, offset 1145): unknown token while reading object; treating as string
  82 +WARNING: issue-101.pdf (object 11 0, offset 1148): unknown token while reading object; treating as string
  83 +WARNING: issue-101.pdf (object 11 0, offset 1150): unknown token while reading object; treating as string
  84 +WARNING: issue-101.pdf (object 11 0, offset 1151): unexpected )
  85 +WARNING: issue-101.pdf (object 11 0, offset 1153): unexpected dictionary close token
  86 +WARNING: issue-101.pdf (object 11 0, offset 1156): unknown token while reading object; treating as string
  87 +WARNING: issue-101.pdf (object 11 0, offset 1163): unknown token while reading object; treating as string
  88 +WARNING: issue-101.pdf (object 11 0, offset 1168): unexpected >
  89 +WARNING: issue-101.pdf (object 11 0, offset 1170): invalid character (I) in hexstring
  90 +WARNING: issue-101.pdf (object 11 0, offset 1167): expected dictionary key but found non-name object; inserting key /QPDFFake1
  91 +WARNING: issue-101.pdf (object 11 0, offset 1167): expected dictionary key but found non-name object; inserting key /QPDFFake2
  92 +WARNING: issue-101.pdf (object 11 0, offset 1167): expected dictionary key but found non-name object; inserting key /QPDFFake3
  93 +WARNING: issue-101.pdf (object 11 0, offset 1176): unknown token while reading object; treating as string
  94 +WARNING: issue-101.pdf (object 11 0, offset 1180): unknown token while reading object; treating as string
  95 +WARNING: issue-101.pdf (object 11 0, offset 1184): unknown token while reading object; treating as string
  96 +WARNING: issue-101.pdf (object 11 0, offset 1190): unexpected >
  97 +WARNING: issue-101.pdf (object 11 0, offset 1192): unknown token while reading object; treating as string
  98 +WARNING: issue-101.pdf (object 11 0, offset 1195): unknown token while reading object; treating as string
  99 +WARNING: issue-101.pdf (object 11 0, offset 1205): unknown token while reading object; treating as string
  100 +WARNING: issue-101.pdf (object 11 0, offset 1217): unknown token while reading object; treating as string
  101 +WARNING: issue-101.pdf (object 11 0, offset 1224): unknown token while reading object; treating as string
  102 +WARNING: issue-101.pdf (object 11 0, offset 1236): unknown token while reading object; treating as string
  103 +WARNING: issue-101.pdf (object 11 0, offset 1242): expected dictionary key but found non-name object; inserting key /QPDFFake1
  104 +WARNING: issue-101.pdf (object 11 0, offset 1242): dictionary ended prematurely; using null as value for last key
  105 +WARNING: issue-101.pdf (object 11 0, offset 1275): unknown token while reading object; treating as string
  106 +WARNING: issue-101.pdf (object 11 0, offset 1287): unknown token while reading object; treating as string
  107 +WARNING: issue-101.pdf (object 11 0, offset 1291): unexpected dictionary close token
  108 +WARNING: issue-101.pdf (object 11 0, offset 1294): unknown token while reading object; treating as string
  109 +WARNING: issue-101.pdf (object 11 0, offset 1306): unknown token while reading object; treating as string
  110 +WARNING: issue-101.pdf (object 11 0, offset 1322): unknown token while reading object; treating as string
  111 +WARNING: issue-101.pdf (object 11 0, offset 1325): unknown token while reading object; treating as string
  112 +WARNING: issue-101.pdf (object 11 0, offset 1329): unknown token while reading object; treating as string
  113 +WARNING: issue-101.pdf (object 11 0, offset 1341): treating unexpected array close token as null
  114 +WARNING: issue-101.pdf (object 11 0, offset 1312): expected dictionary key but found non-name object; inserting key /QPDFFake1
  115 +WARNING: issue-101.pdf (object 11 0, offset 1312): expected dictionary key but found non-name object; inserting key /QPDFFake2
  116 +WARNING: issue-101.pdf (object 11 0, offset 1312): expected dictionary key but found non-name object; inserting key /QPDFFake3
  117 +WARNING: issue-101.pdf (object 11 0, offset 1312): expected dictionary key but found non-name object; inserting key /QPDFFake4
  118 +WARNING: issue-101.pdf (object 11 0, offset 1312): dictionary ended prematurely; using null as value for last key
  119 +WARNING: issue-101.pdf (object 11 0, offset 1349): unknown token while reading object; treating as string
  120 +WARNING: issue-101.pdf (object 11 0, offset 1353): unknown token while reading object; treating as string
  121 +WARNING: issue-101.pdf (object 11 0, offset 1357): unknown token while reading object; treating as string
  122 +WARNING: issue-101.pdf (object 11 0, offset 1359): unknown token while reading object; treating as string
  123 +WARNING: issue-101.pdf (object 11 0, offset 1368): unexpected )
  124 +WARNING: issue-101.pdf (object 11 0, offset 1373): expected endobj
  125 +WARNING: issue-101.pdf (object 8 0, offset 4067): invalid character ()) in hexstring
  126 +WARNING: issue-101.pdf (object 8 0, offset 4069): expected endobj
  127 +qpdf: operation succeeded with warnings; resulting file may have some problems
... ...
qpdf/qtest/qpdf/issue-146.out
... ... @@ -2,4 +2,6 @@ WARNING: issue-146.pdf: file is damaged
2 2 WARNING: issue-146.pdf: can't find startxref
3 3 WARNING: issue-146.pdf: Attempting to reconstruct cross-reference table
4 4 WARNING: issue-146.pdf (trailer, offset 20728): unknown token while reading object; treating as string
5   -issue-146.pdf (trailer, offset 20732): unexpected EOF
  5 +WARNING: issue-146.pdf (trailer, offset 20732): unexpected EOF
  6 +WARNING: issue-146.pdf (trailer, offset 20732): parse error while reading object
  7 +issue-146.pdf: unable to find trailer dictionary while recovering damaged file
... ...
qpdf/qtest/qpdf/issue-51.out
... ... @@ -6,5 +6,6 @@ WARNING: issue-51.pdf (offset 70): loop detected resolving object 2 0
6 6 WARNING: issue-51.pdf (object 2 0, offset 26): /Length key in stream dictionary is not an integer
7 7 WARNING: issue-51.pdf (object 2 0, offset 71): attempting to recover stream length
8 8 WARNING: issue-51.pdf (object 2 0, offset 71): unable to recover stream data; treating stream as empty
9   -WARNING: issue-51.pdf (object 2 0, offset 977): unexpected EOF
  9 +WARNING: issue-51.pdf (object 2 0, offset 977): expected endobj
  10 +WARNING: issue-51.pdf (object 2 0, offset 977): EOF after endobj
10 11 qpdf: operation succeeded with warnings; resulting file may have some problems
... ...
qpdf/qtest/qpdf/linearization-bounds-1.out
... ... @@ -2,7 +2,7 @@ checking linearization-bounds-1.pdf
2 2 PDF Version: 1.3
3 3 File is not encrypted
4 4 File is linearized
5   -WARNING: linearization-bounds-1.pdf (linearization hint stream: object 62 0, offset 12302): unexpected EOF
  5 +WARNING: linearization-bounds-1.pdf (linearization hint stream: object 62 0, offset 12302): expected endstream
6 6 WARNING: linearization-bounds-1.pdf (linearization hint stream: object 62 0, offset 1183): attempting to recover stream length
7 7 WARNING: linearization-bounds-1.pdf (linearization hint stream: object 62 0, offset 1183): recovered stream length: 106
8 8 linearization-bounds-1.pdf (linearization hint table, offset 1183): /S (shared object) offset is out of bounds
... ...
qpdf/qtest/qpdf/object-types-os.out 0 โ†’ 100644
  1 +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
  2 +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: returning null for out of bounds array access
  3 +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: returning null for out of bounds array access
  4 +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
  5 +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
  6 +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to erase out of bounds array item
  7 +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to erase out of bounds array item
  8 +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
  9 +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
  10 +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
  11 +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
  12 +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
  13 +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
  14 +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
  15 +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
  16 +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
  17 +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
  18 +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
  19 +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
  20 +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
  21 +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
  22 +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
  23 +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
  24 +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
  25 +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
  26 +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
  27 +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
  28 +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
  29 +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
  30 +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
  31 +One error
  32 +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
  33 +One error
  34 +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
  35 +Two errors
  36 +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: returning null for out of bounds array access
  37 +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
  38 +One error
  39 +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
  40 +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
  41 +test 42 done
... ...
qpdf/qtest/qpdf/object-types-os.pdf 0 โ†’ 100644
No preview for this file type
qpdf/qtest/qpdf/object-types.out 0 โ†’ 100644
  1 +WARNING: object-types.pdf, object 8 0 at offset 657: operation for string attempted on object of type dictionary: returning empty string
  2 +WARNING: object-types.pdf, object 8 0 at offset 717: returning null for out of bounds array access
  3 +WARNING: object-types.pdf, object 8 0 at offset 717: returning null for out of bounds array access
  4 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: returning null
  5 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to append item
  6 +WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to erase out of bounds array item
  7 +WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to erase out of bounds array item
  8 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to erase item
  9 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to insert item
  10 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to replace items
  11 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to set item
  12 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: treating as empty
  13 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: treating as empty
  14 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for boolean attempted on object of type integer: returning false
  15 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: treating as empty
  16 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: treating as empty
  17 +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
  18 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: ignoring key removal request
  19 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: ignoring key removal/replacement request
  20 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: ignoring key removal/replacement request
  21 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for dictionary attempted on object of type integer: ignoring key replacement request
  22 +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
  23 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for inlineimage attempted on object of type integer: returning empty data
  24 +WARNING: object-types.pdf, object 8 0 at offset 687: operation for integer attempted on object of type dictionary: returning 0
  25 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for name attempted on object of type integer: returning dummy name
  26 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for operator attempted on object of type integer: returning fake value
  27 +WARNING: object-types.pdf, object 8 0 at offset 687: operation for real attempted on object of type dictionary: returning 0.0
  28 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for string attempted on object of type integer: returning empty string
  29 +WARNING: object-types.pdf, object 8 0 at offset 669: operation for string attempted on object of type integer: returning empty string
  30 +WARNING: object-types.pdf, object 8 0 at offset 687: operation for number attempted on object of type dictionary: returning 0
  31 +One error
  32 +WARNING: object-types.pdf, object 8 0 at offset 724: operation for string attempted on object of type name: returning empty string
  33 +One error
  34 +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
  35 +Two errors
  36 +WARNING: object-types.pdf, object 8 0 at offset 717: returning null for out of bounds array access
  37 +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
  38 +One error
  39 +WARNING: object-types.pdf, object 8 0 at offset 745: operation for string attempted on object of type name: returning empty string
  40 +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
  41 +test 42 done
... ...
qpdf/qtest/qpdf/object-types.pdf 0 โ†’ 100644
  1 +%PDF-1.3
  2 +%ยฟรทยขรพ
  3 +%QDF-1.0
  4 +
  5 +1 0 obj
  6 +<<
  7 + /Pages 2 0 R
  8 + /Type /Catalog
  9 +>>
  10 +endobj
  11 +
  12 +2 0 obj
  13 +<<
  14 + /Count 1
  15 + /Kids [
  16 + 3 0 R
  17 + ]
  18 + /Type /Pages
  19 +>>
  20 +endobj
  21 +
  22 +%% Page 1
  23 +3 0 obj
  24 +<<
  25 + /Contents 4 0 R
  26 + /MediaBox [
  27 + 0
  28 + 0
  29 + 612
  30 + 792
  31 + ]
  32 + /Parent 2 0 R
  33 + /Resources <<
  34 + /Font <<
  35 + /F1 6 0 R
  36 + >>
  37 + /ProcSet 7 0 R
  38 + >>
  39 + /Type /Page
  40 +>>
  41 +endobj
  42 +
  43 +%% Contents for page 1
  44 +4 0 obj
  45 +<<
  46 + /Length 5 0 R
  47 +>>
  48 +stream
  49 +BT
  50 + /F1 24 Tf
  51 + 72 720 Td
  52 + (Potato) Tj
  53 +ET
  54 +endstream
  55 +endobj
  56 +
  57 +5 0 obj
  58 +44
  59 +endobj
  60 +
  61 +6 0 obj
  62 +<<
  63 + /BaseFont /Helvetica
  64 + /Encoding /WinAnsiEncoding
  65 + /Name /F1
  66 + /Subtype /Type1
  67 + /Type /Font
  68 +>>
  69 +endobj
  70 +
  71 +7 0 obj
  72 +[
  73 + /PDF
  74 + /Text
  75 +]
  76 +endobj
  77 +
  78 +8 0 obj
  79 +<<
  80 + /Integer 5
  81 + /Dictionary <<
  82 + /Key1 /Value1
  83 + /Key2 [
  84 + /Item0
  85 + << /K [ /V ] >>
  86 + /Item2
  87 + ]
  88 + >>
  89 +>>
  90 +endobj
  91 +
  92 +xref
  93 +0 9
  94 +0000000000 65535 f
  95 +0000000025 00000 n
  96 +0000000079 00000 n
  97 +0000000161 00000 n
  98 +0000000376 00000 n
  99 +0000000475 00000 n
  100 +0000000494 00000 n
  101 +0000000612 00000 n
  102 +0000000647 00000 n
  103 +trailer <<
  104 + /Root 1 0 R
  105 + /Size 9
  106 + /ID [<5ecb4bcc69402d31e10c2e63ec8500ee><5ecb4bcc69402d31e10c2e63ec8500ee>]
  107 + /QTest 8 0 R
  108 +>>
  109 +startxref
  110 +788
  111 +%%EOF
... ...
qpdf/test_driver.cc
... ... @@ -1390,6 +1390,66 @@ void runtest(int n, char const* filename1, char const* arg2)
1390 1390 w.setStaticID(true);
1391 1391 w.write();
1392 1392 }
  1393 + else if (n == 42)
  1394 + {
  1395 + // Access objects as wrong type. This test case is crafted to
  1396 + // work with object-types.pdf.
  1397 + QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
  1398 + QPDFObjectHandle array = qtest.getKey("/Dictionary").getKey("/Key2");
  1399 + QPDFObjectHandle dictionary = qtest.getKey("/Dictionary");
  1400 + QPDFObjectHandle integer = qtest.getKey("/Integer");
  1401 + QPDFObjectHandle null = QPDFObjectHandle::newNull();
  1402 + assert(array.isArray());
  1403 + assert(dictionary.isDictionary());
  1404 + assert("" == qtest.getStringValue());
  1405 + array.getArrayItem(-1).assertNull();
  1406 + array.getArrayItem(16059).assertNull();
  1407 + integer.getArrayItem(0).assertNull();
  1408 + integer.appendItem(null);
  1409 + array.eraseItem(-1);
  1410 + array.eraseItem(16059);
  1411 + integer.eraseItem(0);
  1412 + integer.insertItem(0, null);
  1413 + integer.setArrayFromVector(std::vector<QPDFObjectHandle>());
  1414 + integer.setArrayItem(0, null);
  1415 + assert(0 == integer.getArrayNItems());
  1416 + assert(integer.getArrayAsVector().empty());
  1417 + assert(false == integer.getBoolValue());
  1418 + assert(integer.getDictAsMap().empty());
  1419 + assert(integer.getKeys().empty());
  1420 + assert(false == integer.hasKey("/Potato"));
  1421 + integer.removeKey("/Potato");
  1422 + integer.replaceOrRemoveKey("/Potato", null);
  1423 + integer.replaceOrRemoveKey("/Potato", QPDFObjectHandle::newInteger(1));
  1424 + integer.replaceKey("/Potato", QPDFObjectHandle::newInteger(1));
  1425 + qtest.getKey("/Integer").getKey("/Potato");
  1426 + assert(integer.getInlineImageValue().empty());
  1427 + assert(0 == dictionary.getIntValue());
  1428 + assert("/QPDFFakeName" == integer.getName());
  1429 + assert("QPDFFAKE" == integer.getOperatorValue());
  1430 + assert("0.0" == dictionary.getRealValue());
  1431 + assert(integer.getStringValue().empty());
  1432 + assert(integer.getUTF8Value().empty());
  1433 + assert(0.0 == dictionary.getNumericValue());
  1434 + // Make sure error messages are okay for nested values
  1435 + std::cerr << "One error\n";
  1436 + assert(array.getArrayItem(0).getStringValue().empty());
  1437 + std::cerr << "One error\n";
  1438 + assert(dictionary.getKey("/Quack").getStringValue().empty());
  1439 + assert(array.getArrayItem(1).isDictionary());
  1440 + assert(array.getArrayItem(1).getKey("/K").isArray());
  1441 + assert(array.getArrayItem(1).getKey("/K").getArrayItem(0).isName());
  1442 + assert("/V" ==
  1443 + array.getArrayItem(1).getKey("/K").getArrayItem(0).getName());
  1444 + std::cerr << "Two errors\n";
  1445 + assert(array.getArrayItem(16059).getStringValue().empty());
  1446 + std::cerr << "One error\n";
  1447 + array.getArrayItem(1).getKey("/K").getArrayItem(0).getStringValue();
  1448 + // Stream dictionary
  1449 + QPDFObjectHandle page = pdf.getAllPages()[0];
  1450 + assert("/QPDFFakeName" ==
  1451 + page.getKey("/Contents").getDict().getKey("/Potato").getName());
  1452 + }
1393 1453 else
1394 1454 {
1395 1455 throw std::runtime_error(std::string("invalid test ") +
... ...