diff options
Diffstat (limited to 'Build/source/libs/poppler/poppler-0.24.1/poppler/XRef.cc')
-rw-r--r-- | Build/source/libs/poppler/poppler-0.24.1/poppler/XRef.cc | 1739 |
1 files changed, 1739 insertions, 0 deletions
diff --git a/Build/source/libs/poppler/poppler-0.24.1/poppler/XRef.cc b/Build/source/libs/poppler/poppler-0.24.1/poppler/XRef.cc new file mode 100644 index 00000000000..5495b62f7ba --- /dev/null +++ b/Build/source/libs/poppler/poppler-0.24.1/poppler/XRef.cc @@ -0,0 +1,1739 @@ +//======================================================================== +// +// XRef.cc +// +// Copyright 1996-2003 Glyph & Cog, LLC +// +//======================================================================== + +//======================================================================== +// +// Modified under the Poppler project - http://poppler.freedesktop.org +// +// All changes made under the Poppler project to this file are licensed +// under GPL version 2 or later +// +// Copyright (C) 2005 Dan Sheridan <dan.sheridan@postman.org.uk> +// Copyright (C) 2005 Brad Hards <bradh@frogmouth.net> +// Copyright (C) 2006, 2008, 2010, 2012, 2013 Albert Astals Cid <aacid@kde.org> +// Copyright (C) 2007-2008 Julien Rebetez <julienr@svn.gnome.org> +// Copyright (C) 2007 Carlos Garcia Campos <carlosgc@gnome.org> +// Copyright (C) 2009, 2010 Ilya Gorenbein <igorenbein@finjan.com> +// Copyright (C) 2010 Hib Eris <hib@hiberis.nl> +// Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@kabelmail.de> +// Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it> +// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com> +// Copyright (C) 2013 Pino Toscano <pino@kde.org> +// +// To see a description of the changes please see the Changelog file that +// came with your tarball or type make ChangeLog if you are building from git +// +//======================================================================== + +#include <config.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <math.h> +#include <ctype.h> +#include <limits.h> +#include <float.h> +#include "goo/gfile.h" +#include "goo/gmem.h" +#include "Object.h" +#include "Stream.h" +#include "Lexer.h" +#include "Parser.h" +#include "Dict.h" +#include "Error.h" +#include "ErrorCodes.h" +#include "XRef.h" +#include "PopplerCache.h" + +//------------------------------------------------------------------------ +// Permission bits +// Note that the PDF spec uses 1 base (eg bit 3 is 1<<2) +//------------------------------------------------------------------------ + +#define permPrint (1<<2) // bit 3 +#define permChange (1<<3) // bit 4 +#define permCopy (1<<4) // bit 5 +#define permNotes (1<<5) // bit 6 +#define permFillForm (1<<8) // bit 9 +#define permAccessibility (1<<9) // bit 10 +#define permAssemble (1<<10) // bit 11 +#define permHighResPrint (1<<11) // bit 12 +#define defPermFlags 0xfffc + +#if MULTITHREADED +# define xrefLocker() MutexLocker locker(&mutex) +# define xrefCondLocker(X) MutexLocker locker(&mutex, (X)) +#else +# define xrefLocker() +# define xrefCondLocker(X) +#endif + +//------------------------------------------------------------------------ +// ObjectStream +//------------------------------------------------------------------------ + +class ObjectStream { +public: + + // Create an object stream, using object number <objStrNum>, + // generation 0. + ObjectStream(XRef *xref, int objStrNumA, int recursion = 0); + + GBool isOk() { return ok; } + + ~ObjectStream(); + + // Return the object number of this object stream. + int getObjStrNum() { return objStrNum; } + + // Get the <objIdx>th object from this stream, which should be + // object number <objNum>, generation 0. + Object *getObject(int objIdx, int objNum, Object *obj); + +private: + + int objStrNum; // object number of the object stream + int nObjects; // number of objects in the stream + Object *objs; // the objects (length = nObjects) + int *objNums; // the object numbers (length = nObjects) + GBool ok; +}; + +class ObjectStreamKey : public PopplerCacheKey +{ + public: + ObjectStreamKey(int num) : objStrNum(num) + { + } + + bool operator==(const PopplerCacheKey &key) const + { + const ObjectStreamKey *k = static_cast<const ObjectStreamKey*>(&key); + return objStrNum == k->objStrNum; + } + + const int objStrNum; +}; + +class ObjectStreamItem : public PopplerCacheItem +{ + public: + ObjectStreamItem(ObjectStream *objStr) : objStream(objStr) + { + } + + ~ObjectStreamItem() + { + delete objStream; + } + + ObjectStream *objStream; +}; + +ObjectStream::ObjectStream(XRef *xref, int objStrNumA, int recursion) { + Stream *str; + Parser *parser; + Goffset *offsets; + Object objStr, obj1, obj2; + Goffset first; + int i; + + objStrNum = objStrNumA; + nObjects = 0; + objs = NULL; + objNums = NULL; + ok = gFalse; + + if (!xref->fetch(objStrNum, 0, &objStr, recursion)->isStream()) { + goto err1; + } + + if (!objStr.streamGetDict()->lookup("N", &obj1, recursion)->isInt()) { + obj1.free(); + goto err1; + } + nObjects = obj1.getInt(); + obj1.free(); + if (nObjects <= 0) { + goto err1; + } + + objStr.streamGetDict()->lookup("First", &obj1, recursion); + if (!obj1.isInt() && !obj1.isInt64()) { + obj1.free(); + goto err1; + } + if (obj1.isInt()) + first = obj1.getInt(); + else + first = obj1.getInt64(); + obj1.free(); + if (first < 0) { + goto err1; + } + + // this is an arbitrary limit to avoid integer overflow problems + // in the 'new Object[nObjects]' call (Acrobat apparently limits + // object streams to 100-200 objects) + if (nObjects > 1000000) { + error(errSyntaxError, -1, "Too many objects in an object stream"); + goto err1; + } + objs = new Object[nObjects]; + objNums = (int *)gmallocn(nObjects, sizeof(int)); + offsets = (Goffset *)gmallocn(nObjects, sizeof(Goffset)); + + // parse the header: object numbers and offsets + objStr.streamReset(); + obj1.initNull(); + str = new EmbedStream(objStr.getStream(), &obj1, gTrue, first); + parser = new Parser(xref, new Lexer(xref, str), gFalse); + for (i = 0; i < nObjects; ++i) { + parser->getObj(&obj1); + parser->getObj(&obj2); + if (!obj1.isInt() || !(obj2.isInt() || obj2.isInt64())) { + obj1.free(); + obj2.free(); + delete parser; + gfree(offsets); + goto err1; + } + objNums[i] = obj1.getInt(); + if (obj2.isInt()) + offsets[i] = obj2.getInt(); + else + offsets[i] = obj2.getInt64(); + obj1.free(); + obj2.free(); + if (objNums[i] < 0 || offsets[i] < 0 || + (i > 0 && offsets[i] < offsets[i-1])) { + delete parser; + gfree(offsets); + goto err1; + } + } + while (str->getChar() != EOF) ; + delete parser; + + // skip to the first object - this shouldn't be necessary because + // the First key is supposed to be equal to offsets[0], but just in + // case... + for (Goffset pos = first; pos < offsets[0]; ++pos) { + objStr.getStream()->getChar(); + } + + // parse the objects + for (i = 0; i < nObjects; ++i) { + obj1.initNull(); + if (i == nObjects - 1) { + str = new EmbedStream(objStr.getStream(), &obj1, gFalse, 0); + } else { + str = new EmbedStream(objStr.getStream(), &obj1, gTrue, + offsets[i+1] - offsets[i]); + } + parser = new Parser(xref, new Lexer(xref, str), gFalse); + parser->getObj(&objs[i]); + while (str->getChar() != EOF) ; + delete parser; + } + + gfree(offsets); + ok = gTrue; + + err1: + objStr.free(); +} + +ObjectStream::~ObjectStream() { + int i; + + if (objs) { + for (i = 0; i < nObjects; ++i) { + objs[i].free(); + } + delete[] objs; + } + gfree(objNums); +} + +Object *ObjectStream::getObject(int objIdx, int objNum, Object *obj) { + if (objIdx < 0 || objIdx >= nObjects || objNum != objNums[objIdx]) { + return obj->initNull(); + } + return objs[objIdx].copy(obj); +} + +//------------------------------------------------------------------------ +// XRef +//------------------------------------------------------------------------ + +void XRef::init() { +#if MULTITHREADED + gInitMutex(&mutex); +#endif + ok = gTrue; + errCode = errNone; + entries = NULL; + capacity = 0; + size = 0; + streamEnds = NULL; + streamEndsLen = 0; + objStrs = new PopplerCache(5); + mainXRefEntriesOffset = 0; + xRefStream = gFalse; + scannedSpecialFlags = gFalse; + encrypted = gFalse; + permFlags = defPermFlags; + ownerPasswordOk = gFalse; + rootNum = -1; + strOwner = gFalse; +} + +XRef::XRef() { + init(); +} + +XRef::XRef(Object *trailerDictA) { + init(); + + if (trailerDictA->isDict()) + trailerDict.initDict(trailerDictA->getDict()); +} + +XRef::XRef(BaseStream *strA, Goffset pos, Goffset mainXRefEntriesOffsetA, GBool *wasReconstructed, GBool reconstruct) { + Object obj; + + init(); + mainXRefEntriesOffset = mainXRefEntriesOffsetA; + + // read the trailer + str = strA; + start = str->getStart(); + prevXRefOffset = mainXRefOffset = pos; + + if (reconstruct && !(ok = constructXRef(wasReconstructed))) + { + errCode = errDamaged; + return; + } + else + { + // if there was a problem with the 'startxref' position, try to + // reconstruct the xref table + if (prevXRefOffset == 0) { + if (!(ok = constructXRef(wasReconstructed))) { + errCode = errDamaged; + return; + } + + // read the xref table + } else { + std::vector<Goffset> followedXRefStm; + readXRef(&prevXRefOffset, &followedXRefStm, NULL); + + // if there was a problem with the xref table, + // try to reconstruct it + if (!ok) { + if (!(ok = constructXRef(wasReconstructed))) { + errCode = errDamaged; + return; + } + } + } + + // set size to (at least) the size specified in trailer dict + trailerDict.dictLookupNF("Size", &obj); + if (!obj.isInt()) { + error(errSyntaxWarning, -1, "No valid XRef size in trailer"); + } else { + if (obj.getInt() > size) { + if (resize(obj.getInt()) != obj.getInt()) { + if (!(ok = constructXRef(wasReconstructed))) { + obj.free(); + errCode = errDamaged; + return; + } + } + } + } + obj.free(); + + // get the root dictionary (catalog) object + trailerDict.dictLookupNF("Root", &obj); + if (obj.isRef()) { + rootNum = obj.getRefNum(); + rootGen = obj.getRefGen(); + obj.free(); + } else { + obj.free(); + if (!(ok = constructXRef(wasReconstructed))) { + errCode = errDamaged; + return; + } + } + } + // now set the trailer dictionary's xref pointer so we can fetch + // indirect objects from it + trailerDict.getDict()->setXRef(this); +} + +XRef::~XRef() { + for(int i=0; i<size; i++) { + entries[i].obj.free (); + } + gfree(entries); + + trailerDict.free(); + if (streamEnds) { + gfree(streamEnds); + } + if (objStrs) { + delete objStrs; + } + if (strOwner) { + delete str; + } +#if MULTITHREADED + gDestroyMutex(&mutex); +#endif +} + +XRef *XRef::copy() { + XRef *xref = new XRef(); + xref->str = str->copy(); + xref->strOwner = gTrue; + xref->encrypted = encrypted; + xref->permFlags = permFlags; + xref->ownerPasswordOk = ownerPasswordOk; + xref->rootGen = rootGen; + xref->rootNum = rootNum; + + xref->start = start; + xref->prevXRefOffset = prevXRefOffset; + xref->mainXRefEntriesOffset = mainXRefEntriesOffset; + xref->xRefStream = xRefStream; + trailerDict.copy(&xref->trailerDict); + xref->encAlgorithm = encAlgorithm; + xref->encRevision = encRevision; + xref->encVersion = encVersion; + xref->permFlags = permFlags; + xref->keyLength = keyLength; + xref->permFlags = permFlags; + for (int i = 0; i < 32; i++) { + xref->fileKey[i] = fileKey[i]; + } + + if (xref->reserve(size) == 0) { + error(errSyntaxError, -1, "unable to allocate {0:d} entries", size); + return NULL; + } + xref->size = size; + for (int i = 0; i < size; ++i) { + xref->entries[i].offset = entries[i].offset; + xref->entries[i].type = entries[i].type; + xref->entries[i].obj.initNull (); + xref->entries[i].flags = entries[i].flags; + xref->entries[i].gen = entries[i].gen; + } + xref->streamEndsLen = streamEndsLen; + if (streamEndsLen != 0) { + xref->streamEnds = (Goffset *)gmalloc(streamEndsLen * sizeof(Goffset)); + for (int i = 0; i < streamEndsLen; i++) { + xref->streamEnds[i] = streamEnds[i]; + } + } + return xref; +} + +int XRef::reserve(int newSize) +{ + if (newSize > capacity) { + + int realNewSize; + for (realNewSize = capacity ? 2 * capacity : 1024; + newSize > realNewSize && realNewSize > 0; + realNewSize <<= 1) ; + if ((realNewSize < 0) || + (realNewSize >= INT_MAX / (int)sizeof(XRefEntry))) { + return 0; + } + + void *p = greallocn_checkoverflow(entries, realNewSize, sizeof(XRefEntry)); + if (p == NULL) { + return 0; + } + + entries = (XRefEntry *) p; + capacity = realNewSize; + + } + + return capacity; +} + +int XRef::resize(int newSize) +{ + if (newSize > size) { + + if (reserve(newSize) < newSize) return size; + + for (int i = size; i < newSize; ++i) { + entries[i].offset = -1; + entries[i].type = xrefEntryNone; + entries[i].obj.initNull (); + entries[i].flags = 0; + entries[i].gen = 0; + } + } else { + for (int i = newSize; i < size; i++) { + entries[i].obj.free (); + } + } + + size = newSize; + + return size; +} + +/* Read one xref table section. Also reads the associated trailer + * dictionary, and returns the prev pointer (if any). + * Arguments: + * pos Points to a Goffset containing the offset of the XRef + * section to be read. If a prev pointer is found, *pos is + * updated with its value + * followedXRefStm Used in case of nested readXRef calls to spot circular + * references in XRefStm pointers + * xrefStreamObjsNum If not NULL, every time a XRef stream is encountered, + * its object number is appended + * Return value: + * gTrue if a prev pointer is found, otherwise gFalse + */ +GBool XRef::readXRef(Goffset *pos, std::vector<Goffset> *followedXRefStm, std::vector<int> *xrefStreamObjsNum) { + Parser *parser; + Object obj; + GBool more; + + // start up a parser, parse one token + obj.initNull(); + parser = new Parser(NULL, + new Lexer(NULL, + str->makeSubStream(start + *pos, gFalse, 0, &obj)), + gTrue); + parser->getObj(&obj, gTrue); + + // parse an old-style xref table + if (obj.isCmd("xref")) { + obj.free(); + more = readXRefTable(parser, pos, followedXRefStm, xrefStreamObjsNum); + + // parse an xref stream + } else if (obj.isInt()) { + const int objNum = obj.getInt(); + obj.free(); + if (!parser->getObj(&obj, gTrue)->isInt()) { + goto err1; + } + obj.free(); + if (!parser->getObj(&obj, gTrue)->isCmd("obj")) { + goto err1; + } + obj.free(); + if (!parser->getObj(&obj)->isStream()) { + goto err1; + } + if (trailerDict.isNone()) { + xRefStream = gTrue; + } + if (xrefStreamObjsNum) { + xrefStreamObjsNum->push_back(objNum); + } + more = readXRefStream(obj.getStream(), pos); + obj.free(); + + } else { + goto err1; + } + + delete parser; + return more; + + err1: + obj.free(); + delete parser; + ok = gFalse; + return gFalse; +} + +GBool XRef::readXRefTable(Parser *parser, Goffset *pos, std::vector<Goffset> *followedXRefStm, std::vector<int> *xrefStreamObjsNum) { + XRefEntry entry; + GBool more; + Object obj, obj2; + Goffset pos2; + int first, n, i; + + while (1) { + parser->getObj(&obj, gTrue); + if (obj.isCmd("trailer")) { + obj.free(); + break; + } + if (!obj.isInt()) { + goto err1; + } + first = obj.getInt(); + obj.free(); + if (!parser->getObj(&obj, gTrue)->isInt()) { + goto err1; + } + n = obj.getInt(); + obj.free(); + if (first < 0 || n < 0 || first + n < 0) { + goto err0; + } + if (first + n > size) { + if (resize(first + n) != first + n) { + error(errSyntaxError, -1, "Invalid 'obj' parameters'"); + goto err0; + } + } + for (i = first; i < first + n; ++i) { + parser->getObj(&obj, gTrue); + if (obj.isInt()) { + entry.offset = obj.getInt(); + } else if (obj.isInt64()) { + entry.offset = obj.getInt64(); + } else { + goto err1; + } + obj.free(); + if (!parser->getObj(&obj, gTrue)->isInt()) { + goto err1; + } + entry.gen = obj.getInt(); + entry.obj.initNull (); + entry.flags = 0; + obj.free(); + parser->getObj(&obj, gTrue); + if (obj.isCmd("n")) { + entry.type = xrefEntryUncompressed; + } else if (obj.isCmd("f")) { + entry.type = xrefEntryFree; + } else { + goto err1; + } + obj.free(); + if (entries[i].offset == -1) { + entries[i] = entry; + // PDF files of patents from the IBM Intellectual Property + // Network have a bug: the xref table claims to start at 1 + // instead of 0. + if (i == 1 && first == 1 && + entries[1].offset == 0 && entries[1].gen == 65535 && + entries[1].type == xrefEntryFree) { + i = first = 0; + entries[0] = entries[1]; + entries[1].offset = -1; + } + } + } + } + + // read the trailer dictionary + if (!parser->getObj(&obj)->isDict()) { + goto err1; + } + + // get the 'Prev' pointer + obj.getDict()->lookupNF("Prev", &obj2); + if (obj2.isInt() || obj2.isInt64()) { + if (obj2.isInt()) + pos2 = obj2.getInt(); + else + pos2 = obj2.getInt64(); + if (pos2 != *pos) { + *pos = pos2; + more = gTrue; + } else { + error(errSyntaxWarning, -1, "Infinite loop in xref table"); + more = gFalse; + } + } else if (obj2.isRef()) { + // certain buggy PDF generators generate "/Prev NNN 0 R" instead + // of "/Prev NNN" + pos2 = (Guint)obj2.getRefNum(); + if (pos2 != *pos) { + *pos = pos2; + more = gTrue; + } else { + error(errSyntaxWarning, -1, "Infinite loop in xref table"); + more = gFalse; + } + } else { + more = gFalse; + } + obj2.free(); + + // save the first trailer dictionary + if (trailerDict.isNone()) { + obj.copy(&trailerDict); + } + + // check for an 'XRefStm' key + obj.getDict()->lookup("XRefStm", &obj2); + if (obj2.isInt() || obj2.isInt64()) { + if (obj2.isInt()) + pos2 = obj2.getInt(); + else + pos2 = obj2.getInt64(); + for (size_t i = 0; ok == gTrue && i < followedXRefStm->size(); ++i) { + if (followedXRefStm->at(i) == pos2) { + ok = gFalse; + } + } + if (ok) { + followedXRefStm->push_back(pos2); + readXRef(&pos2, followedXRefStm, xrefStreamObjsNum); + } + if (!ok) { + obj2.free(); + goto err1; + } + } + obj2.free(); + + obj.free(); + return more; + + err1: + obj.free(); + err0: + ok = gFalse; + return gFalse; +} + +GBool XRef::readXRefStream(Stream *xrefStr, Goffset *pos) { + Dict *dict; + int w[3]; + GBool more; + Object obj, obj2, idx; + int newSize, first, n, i; + + dict = xrefStr->getDict(); + + if (!dict->lookupNF("Size", &obj)->isInt()) { + goto err1; + } + newSize = obj.getInt(); + obj.free(); + if (newSize < 0) { + goto err1; + } + if (newSize > size) { + if (resize(newSize) != newSize) { + error(errSyntaxError, -1, "Invalid 'size' parameter"); + goto err0; + } + } + + if (!dict->lookupNF("W", &obj)->isArray() || + obj.arrayGetLength() < 3) { + goto err1; + } + for (i = 0; i < 3; ++i) { + if (!obj.arrayGet(i, &obj2)->isInt()) { + obj2.free(); + goto err1; + } + w[i] = obj2.getInt(); + obj2.free(); + if (w[i] < 0) { + goto err1; + } + } + obj.free(); + if (w[0] > (int)sizeof(int) || w[1] > (int)sizeof(long long) || w[2] > (int)sizeof(int)) { + goto err1; + } + + xrefStr->reset(); + dict->lookupNF("Index", &idx); + if (idx.isArray()) { + for (i = 0; i+1 < idx.arrayGetLength(); i += 2) { + if (!idx.arrayGet(i, &obj)->isInt()) { + idx.free(); + goto err1; + } + first = obj.getInt(); + obj.free(); + if (!idx.arrayGet(i+1, &obj)->isInt()) { + idx.free(); + goto err1; + } + n = obj.getInt(); + obj.free(); + if (first < 0 || n < 0 || + !readXRefStreamSection(xrefStr, w, first, n)) { + idx.free(); + goto err0; + } + } + } else { + if (!readXRefStreamSection(xrefStr, w, 0, newSize)) { + idx.free(); + goto err0; + } + } + idx.free(); + + dict->lookupNF("Prev", &obj); + if (obj.isInt()) { + *pos = obj.getInt(); + more = gTrue; + } else if (obj.isInt64()) { + *pos = obj.getInt64(); + more = gTrue; + } else { + more = gFalse; + } + obj.free(); + if (trailerDict.isNone()) { + trailerDict.initDict(dict); + } + + return more; + + err1: + obj.free(); + err0: + ok = gFalse; + return gFalse; +} + +GBool XRef::readXRefStreamSection(Stream *xrefStr, int *w, int first, int n) { + unsigned long long offset; + int type, gen, c, i, j; + + if (first + n < 0) { + return gFalse; + } + if (first + n > size) { + if (resize(first + n) != size) { + error(errSyntaxError, -1, "Invalid 'size' inside xref table"); + return gFalse; + } + if (first + n > size) { + error(errSyntaxError, -1, "Invalid 'first' or 'n' inside xref table"); + return gFalse; + } + } + for (i = first; i < first + n; ++i) { + if (w[0] == 0) { + type = 1; + } else { + for (type = 0, j = 0; j < w[0]; ++j) { + if ((c = xrefStr->getChar()) == EOF) { + return gFalse; + } + type = (type << 8) + c; + } + } + for (offset = 0, j = 0; j < w[1]; ++j) { + if ((c = xrefStr->getChar()) == EOF) { + return gFalse; + } + offset = (offset << 8) + c; + } + if (offset > (unsigned long long)GoffsetMax()) { + error(errSyntaxError, -1, "Offset inside xref table too large for fseek"); + return gFalse; + } + for (gen = 0, j = 0; j < w[2]; ++j) { + if ((c = xrefStr->getChar()) == EOF) { + return gFalse; + } + gen = (gen << 8) + c; + } + if (entries[i].offset == -1) { + switch (type) { + case 0: + entries[i].offset = offset; + entries[i].gen = gen; + entries[i].type = xrefEntryFree; + break; + case 1: + entries[i].offset = offset; + entries[i].gen = gen; + entries[i].type = xrefEntryUncompressed; + break; + case 2: + entries[i].offset = offset; + entries[i].gen = gen; + entries[i].type = xrefEntryCompressed; + break; + default: + return gFalse; + } + } + } + + return gTrue; +} + +// Attempt to construct an xref table for a damaged file. +GBool XRef::constructXRef(GBool *wasReconstructed, GBool needCatalogDict) { + Parser *parser; + Object newTrailerDict, obj; + char buf[256]; + Goffset pos; + int num, gen; + int newSize; + int streamEndsSize; + char *p; + GBool gotRoot; + char* token = NULL; + bool oneCycle = true; + int offset = 0; + + gfree(entries); + capacity = 0; + size = 0; + entries = NULL; + + gotRoot = gFalse; + streamEndsLen = streamEndsSize = 0; + + if (wasReconstructed) + { + *wasReconstructed = true; + } + + str->reset(); + while (1) { + pos = str->getPos(); + if (!str->getLine(buf, 256)) { + break; + } + p = buf; + + // skip whitespace + while (*p && Lexer::isSpace(*p & 0xff)) ++p; + + oneCycle = true; + offset = 0; + + while( ( token = strstr( p, "endobj" ) ) || oneCycle ) { + oneCycle = false; + + if( token ) { + oneCycle = true; + token[0] = '\0'; + offset = token - p; + } + + // got trailer dictionary + if (!strncmp(p, "trailer", 7)) { + obj.initNull(); + parser = new Parser(NULL, + new Lexer(NULL, + str->makeSubStream(pos + 7, gFalse, 0, &obj)), + gFalse); + parser->getObj(&newTrailerDict); + if (newTrailerDict.isDict()) { + newTrailerDict.dictLookupNF("Root", &obj); + if (obj.isRef() && (!gotRoot || !needCatalogDict) && rootNum != obj.getRefNum()) { + rootNum = obj.getRefNum(); + rootGen = obj.getRefGen(); + if (!trailerDict.isNone()) { + trailerDict.free(); + } + newTrailerDict.copy(&trailerDict); + gotRoot = gTrue; + } + obj.free(); + } + newTrailerDict.free(); + delete parser; + + // look for object + } else if (isdigit(*p & 0xff)) { + num = atoi(p); + if (num > 0) { + do { + ++p; + } while (*p && isdigit(*p & 0xff)); + if (isspace(*p & 0xff)) { + do { + ++p; + } while (*p && isspace(*p & 0xff)); + if (isdigit(*p & 0xff)) { + gen = atoi(p); + do { + ++p; + } while (*p && isdigit(*p & 0xff)); + if (isspace(*p & 0xff)) { + do { + ++p; + } while (*p && isspace(*p & 0xff)); + if (!strncmp(p, "obj", 3)) { + if (num >= size) { + newSize = (num + 1 + 255) & ~255; + if (newSize < 0) { + error(errSyntaxError, -1, "Bad object number"); + return gFalse; + } + if (resize(newSize) != newSize) { + error(errSyntaxError, -1, "Invalid 'obj' parameters"); + return gFalse; + } + } + if (entries[num].type == xrefEntryFree || + gen >= entries[num].gen) { + entries[num].offset = pos - start; + entries[num].gen = gen; + entries[num].type = xrefEntryUncompressed; + } + } + } + } + } + } + + } else if (!strncmp(p, "endstream", 9)) { + if (streamEndsLen == streamEndsSize) { + streamEndsSize += 64; + if (streamEndsSize >= INT_MAX / (int)sizeof(int)) { + error(errSyntaxError, -1, "Invalid 'endstream' parameter."); + return gFalse; + } + streamEnds = (Goffset *)greallocn(streamEnds, + streamEndsSize, sizeof(Goffset)); + } + streamEnds[streamEndsLen++] = pos; + } + if( token ) { + p = token + 6;// strlen( "endobj" ) = 6 + pos += offset + 6;// strlen( "endobj" ) = 6 + while (*p && Lexer::isSpace(*p & 0xff)) { + ++p; + ++pos; + } + } + } + } + + if (gotRoot) + return gTrue; + + error(errSyntaxError, -1, "Couldn't find trailer dictionary"); + return gFalse; +} + +void XRef::setEncryption(int permFlagsA, GBool ownerPasswordOkA, + Guchar *fileKeyA, int keyLengthA, + int encVersionA, int encRevisionA, + CryptAlgorithm encAlgorithmA) { + int i; + + encrypted = gTrue; + permFlags = permFlagsA; + ownerPasswordOk = ownerPasswordOkA; + if (keyLengthA <= 32) { + keyLength = keyLengthA; + } else { + keyLength = 32; + } + for (i = 0; i < keyLength; ++i) { + fileKey[i] = fileKeyA[i]; + } + encVersion = encVersionA; + encRevision = encRevisionA; + encAlgorithm = encAlgorithmA; +} + +void XRef::getEncryptionParameters(Guchar **fileKeyA, CryptAlgorithm *encAlgorithmA, + int *keyLengthA) { + if (encrypted) { + *fileKeyA = fileKey; + *encAlgorithmA = encAlgorithm; + *keyLengthA = keyLength; + } else { + // null encryption parameters + *fileKeyA = NULL; + *encAlgorithmA = cryptRC4; + *keyLengthA = 0; + } +} + +GBool XRef::okToPrint(GBool ignoreOwnerPW) { + return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permPrint); +} + +// we can print at high res if we are only doing security handler revision +// 2 (and we are allowed to print at all), or with security handler rev +// 3 and we are allowed to print, and bit 12 is set. +GBool XRef::okToPrintHighRes(GBool ignoreOwnerPW) { + if (encrypted) { + if (2 == encRevision) { + return (okToPrint(ignoreOwnerPW)); + } else if (encRevision >= 3) { + return (okToPrint(ignoreOwnerPW) && (permFlags & permHighResPrint)); + } else { + // something weird - unknown security handler version + return gFalse; + } + } else { + return gTrue; + } +} + +GBool XRef::okToChange(GBool ignoreOwnerPW) { + return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permChange); +} + +GBool XRef::okToCopy(GBool ignoreOwnerPW) { + return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permCopy); +} + +GBool XRef::okToAddNotes(GBool ignoreOwnerPW) { + return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permNotes); +} + +GBool XRef::okToFillForm(GBool ignoreOwnerPW) { + return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permFillForm); +} + +GBool XRef::okToAccessibility(GBool ignoreOwnerPW) { + return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permAccessibility); +} + +GBool XRef::okToAssemble(GBool ignoreOwnerPW) { + return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permAssemble); +} + +Object *XRef::getCatalog(Object *catalog) { + Object *obj = fetch(rootNum, rootGen, catalog); + if (obj->isDict()) { + return obj; + } + GBool wasReconstructed = false; + GBool ok = constructXRef(&wasReconstructed, gTrue); + return (ok) ? fetch(rootNum, rootGen, catalog) : obj; +} + +Object *XRef::fetch(int num, int gen, Object *obj, int recursion) { + XRefEntry *e; + Parser *parser; + Object obj1, obj2, obj3; + + xrefLocker(); + // check for bogus ref - this can happen in corrupted PDF files + if (num < 0 || num >= size) { + goto err; + } + + e = getEntry(num); + if(!e->obj.isNull ()) { //check for updated object + obj = e->obj.copy(obj); + return obj; + } + + switch (e->type) { + + case xrefEntryUncompressed: + if (e->gen != gen) { + goto err; + } + obj1.initNull(); + parser = new Parser(this, + new Lexer(this, + str->makeSubStream(start + e->offset, gFalse, 0, &obj1)), + gTrue); + parser->getObj(&obj1, recursion); + parser->getObj(&obj2, recursion); + parser->getObj(&obj3, recursion); + if (!obj1.isInt() || obj1.getInt() != num || + !obj2.isInt() || obj2.getInt() != gen || + !obj3.isCmd("obj")) { + // some buggy pdf have obj1234 for ints that represent 1234 + // try to recover here + if (obj1.isInt() && obj1.getInt() == num && + obj2.isInt() && obj2.getInt() == gen && + obj3.isCmd()) { + char *cmd = obj3.getCmd(); + if (strlen(cmd) > 3 && + cmd[0] == 'o' && + cmd[1] == 'b' && + cmd[2] == 'j') { + char *end_ptr; + long longNumber = strtol(cmd + 3, &end_ptr, 0); + if (longNumber <= INT_MAX && longNumber >= INT_MIN && *end_ptr == '\0') { + int number = longNumber; + error(errSyntaxWarning, -1, "Cmd was not obj but {0:s}, assuming the creator meant obj {1:d}", cmd, number); + obj->initInt(number); + obj1.free(); + obj2.free(); + obj3.free(); + delete parser; + break; + } + } + } + obj1.free(); + obj2.free(); + obj3.free(); + delete parser; + goto err; + } + parser->getObj(obj, gFalse, (encrypted && !e->getFlag(XRefEntry::Unencrypted)) ? fileKey : NULL, + encAlgorithm, keyLength, num, gen, recursion); + obj1.free(); + obj2.free(); + obj3.free(); + delete parser; + break; + + case xrefEntryCompressed: + { +#if 0 // Adobe apparently ignores the generation number on compressed objects + if (gen != 0) { + goto err; + } +#endif + if (e->offset >= (Guint)size || + entries[e->offset].type != xrefEntryUncompressed) { + error(errSyntaxError, -1, "Invalid object stream"); + goto err; + } + + ObjectStream *objStr = NULL; + ObjectStreamKey key(e->offset); + PopplerCacheItem *item = objStrs->lookup(key); + if (item) { + ObjectStreamItem *it = static_cast<ObjectStreamItem *>(item); + objStr = it->objStream; + } + + if (!objStr) { + objStr = new ObjectStream(this, e->offset, recursion + 1); + if (!objStr->isOk()) { + delete objStr; + objStr = NULL; + goto err; + } else { + // XRef could be reconstructed in constructor of ObjectStream: + e = getEntry(num); + ObjectStreamKey *newkey = new ObjectStreamKey(e->offset); + ObjectStreamItem *newitem = new ObjectStreamItem(objStr); + objStrs->put(newkey, newitem); + } + } + objStr->getObject(e->gen, num, obj); + } + break; + + default: + goto err; + } + + return obj; + + err: + return obj->initNull(); +} + +void XRef::lock() { +#if MULTITHREADED + gLockMutex(&mutex); +#endif +} + +void XRef::unlock() { +#if MULTITHREADED + gUnlockMutex(&mutex); +#endif +} + +Object *XRef::getDocInfo(Object *obj) { + return trailerDict.dictLookup("Info", obj); +} + +// Added for the pdftex project. +Object *XRef::getDocInfoNF(Object *obj) { + return trailerDict.dictLookupNF("Info", obj); +} + +GBool XRef::getStreamEnd(Goffset streamStart, Goffset *streamEnd) { + int a, b, m; + + if (streamEndsLen == 0 || + streamStart > streamEnds[streamEndsLen - 1]) { + return gFalse; + } + + a = -1; + b = streamEndsLen - 1; + // invariant: streamEnds[a] < streamStart <= streamEnds[b] + while (b - a > 1) { + m = (a + b) / 2; + if (streamStart <= streamEnds[m]) { + b = m; + } else { + a = m; + } + } + *streamEnd = streamEnds[b]; + return gTrue; +} + +int XRef::getNumEntry(Goffset offset) +{ + if (size > 0) + { + int res = 0; + Goffset resOffset = getEntry(0)->offset; + XRefEntry *e; + for (int i = 1; i < size; ++i) + { + e = getEntry(i, gFalse); + if (e->type != xrefEntryFree && e->offset < offset && e->offset >= resOffset) + { + res = i; + resOffset = e->offset; + } + } + return res; + } + else return -1; +} + +void XRef::add(int num, int gen, Goffset offs, GBool used) { + xrefLocker(); + if (num >= size) { + if (num >= capacity) { + entries = (XRefEntry *)greallocn(entries, num + 1, sizeof(XRefEntry)); + capacity = num + 1; + } + for (int i = size; i < num + 1; ++i) { + entries[i].offset = -1; + entries[i].type = xrefEntryFree; + entries[i].obj.initNull (); + entries[i].flags = 0; + entries[i].gen = 0; + } + size = num + 1; + } + XRefEntry *e = getEntry(num); + e->gen = gen; + e->obj.initNull (); + e->flags = 0; + if (used) { + e->type = xrefEntryUncompressed; + e->offset = offs; + } else { + e->type = xrefEntryFree; + e->offset = 0; + } +} + +void XRef::setModifiedObject (Object* o, Ref r) { + xrefLocker(); + if (r.num < 0 || r.num >= size) { + error(errInternal, -1,"XRef::setModifiedObject on unknown ref: {0:d}, {1:d}\n", r.num, r.gen); + return; + } + XRefEntry *e = getEntry(r.num); + e->obj.free(); + o->copy(&(e->obj)); + e->setFlag(XRefEntry::Updated, gTrue); +} + +Ref XRef::addIndirectObject (Object* o) { + int entryIndexToUse = -1; + for (int i = 1; entryIndexToUse == -1 && i < size; ++i) { + XRefEntry *e = getEntry(i, false /* complainIfMissing */); + if (e->type == xrefEntryFree && e->gen != 65535) { + entryIndexToUse = i; + } + } + + XRefEntry *e; + if (entryIndexToUse == -1) { + entryIndexToUse = size; + add(entryIndexToUse, 0, 0, gFalse); + e = getEntry(entryIndexToUse); + } else { + //reuse a free entry + e = getEntry(entryIndexToUse); + //we don't touch gen number, because it should have been + //incremented when the object was deleted + } + e->type = xrefEntryUncompressed; + o->copy(&e->obj); + e->setFlag(XRefEntry::Updated, gTrue); + + Ref r; + r.num = entryIndexToUse; + r.gen = e->gen; + return r; +} + +void XRef::removeIndirectObject(Ref r) { + xrefLocker(); + if (r.num < 0 || r.num >= size) { + error(errInternal, -1,"XRef::removeIndirectObject on unknown ref: {0:d}, {1:d}\n", r.num, r.gen); + return; + } + XRefEntry *e = getEntry(r.num); + if (e->type == xrefEntryFree) { + return; + } + e->obj.free(); + e->type = xrefEntryFree; + e->gen++; + e->setFlag(XRefEntry::Updated, gTrue); +} + +void XRef::writeXRef(XRef::XRefWriter *writer, GBool writeAllEntries) { + //create free entries linked-list + if (getEntry(0)->gen != 65535) { + error(errInternal, -1, "XRef::writeXRef, entry 0 of the XRef is invalid (gen != 65535)\n"); + } + int lastFreeEntry = 0; + for (int i=0; i<size; i++) { + if (getEntry(i)->type == xrefEntryFree) { + getEntry(lastFreeEntry)->offset = i; + lastFreeEntry = i; + } + } + getEntry(lastFreeEntry)->offset = 0; + + if (writeAllEntries) { + writer->startSection(0, size); + for (int i=0; i<size; i++) { + XRefEntry *e = getEntry(i); + if(e->gen > 65535) e->gen = 65535; //cap generation number to 65535 (required by PDFReference) + writer->writeEntry(e->offset, e->gen, e->type); + } + } else { + int i = 0; + while (i < size) { + int j; + for(j=i; j<size; j++) { //look for consecutive entries + if ((getEntry(j)->type == xrefEntryFree) && (getEntry(j)->gen == 0)) + break; + } + if (j-i != 0) + { + writer->startSection(i, j-i); + for (int k=i; k<j; k++) { + XRefEntry *e = getEntry(k); + if(e->gen > 65535) e->gen = 65535; //cap generation number to 65535 (required by PDFReference) + writer->writeEntry(e->offset, e->gen, e->type); + } + i = j; + } + else ++i; + } + } +} + +XRef::XRefTableWriter::XRefTableWriter(OutStream* outStrA) { + outStr = outStrA; +} + +void XRef::XRefTableWriter::startSection(int first, int count) { + outStr->printf("%i %i\r\n", first, count); +} + +void XRef::XRefTableWriter::writeEntry(Goffset offset, int gen, XRefEntryType type) { + outStr->printf("%010lli %05i %c\r\n", (long long)offset, gen, (type==xrefEntryFree)?'f':'n'); +} + +void XRef::writeTableToFile(OutStream* outStr, GBool writeAllEntries) { + XRefTableWriter writer(outStr); + outStr->printf("xref\r\n"); + writeXRef(&writer, writeAllEntries); +} + +XRef::XRefStreamWriter::XRefStreamWriter(Object *indexA, GooString *stmBufA, int offsetSizeA) { + index = indexA; + stmBuf = stmBufA; + offsetSize = offsetSizeA; +} + +void XRef::XRefStreamWriter::startSection(int first, int count) { + Object obj; + index->arrayAdd( obj.initInt(first) ); + index->arrayAdd( obj.initInt(count) ); +} + +void XRef::XRefStreamWriter::writeEntry(Goffset offset, int gen, XRefEntryType type) { + const int entryTotalSize = 1 + offsetSize + 2; /* type + offset + gen */ + char data[16]; + data[0] = (type==xrefEntryFree) ? 0 : 1; + for (int i = offsetSize; i > 0; i--) { + data[i] = offset & 0xff; + offset >>= 8; + } + data[offsetSize + 1] = (gen >> 8) & 0xff; + data[offsetSize + 2] = gen & 0xff; + stmBuf->append(data, entryTotalSize); +} + +XRef::XRefPreScanWriter::XRefPreScanWriter() { + hasOffsetsBeyond4GB = gFalse; +} + +void XRef::XRefPreScanWriter::startSection(int first, int count) { +} + +void XRef::XRefPreScanWriter::writeEntry(Goffset offset, int gen, XRefEntryType type) { + if (offset >= 0x100000000ll) + hasOffsetsBeyond4GB = gTrue; +} + +void XRef::writeStreamToBuffer(GooString *stmBuf, Dict *xrefDict, XRef *xref) { + Object index; + index.initArray(xref); + stmBuf->clear(); + + // First pass: determine whether all offsets fit in 4 bytes or not + XRefPreScanWriter prescan; + writeXRef(&prescan, gFalse); + const int offsetSize = prescan.hasOffsetsBeyond4GB ? sizeof(Goffset) : 4; + + // Second pass: actually write the xref stream + XRefStreamWriter writer(&index, stmBuf, offsetSize); + writeXRef(&writer, gFalse); + + Object obj1, obj2; + xrefDict->set("Type", obj1.initName("XRef")); + xrefDict->set("Index", &index); + obj2.initArray(xref); + obj2.arrayAdd( obj1.initInt(1) ); + obj2.arrayAdd( obj1.initInt(offsetSize) ); + obj2.arrayAdd( obj1.initInt(2) ); + xrefDict->set("W", &obj2); +} + +GBool XRef::parseEntry(Goffset offset, XRefEntry *entry) +{ + GBool r; + + Object obj; + obj.initNull(); + Parser parser = Parser(NULL, new Lexer(NULL, + str->makeSubStream(offset, gFalse, 20, &obj)), gTrue); + + Object obj1, obj2, obj3; + if (((parser.getObj(&obj1)->isInt()) || + parser.getObj(&obj1)->isInt64()) && + (parser.getObj(&obj2)->isInt()) && + (parser.getObj(&obj3)->isCmd("n") || obj3.isCmd("f"))) { + if (obj1.isInt64()) + entry->offset = obj1.getInt64(); + else + entry->offset = obj1.getInt(); + entry->gen = obj2.getInt(); + entry->type = obj3.isCmd("n") ? xrefEntryUncompressed : xrefEntryFree; + entry->obj.initNull (); + entry->flags = 0; + r = gTrue; + } else { + r = gFalse; + } + obj1.free(); + obj2.free(); + obj3.free(); + + return r; +} + +/* Traverse all XRef tables and, if untilEntryNum != -1, stop as soon as + * untilEntryNum is found, or try to reconstruct the xref table if it's not + * present in any xref. + * If xrefStreamObjsNum is not NULL, it is filled with the list of the object + * numbers of the XRef streams that have been traversed */ +void XRef::readXRefUntil(int untilEntryNum, std::vector<int> *xrefStreamObjsNum) +{ + std::vector<Goffset> followedPrev; + while (prevXRefOffset && (untilEntryNum == -1 || entries[untilEntryNum].type == xrefEntryNone)) { + bool followed = false; + for (size_t j = 0; j < followedPrev.size(); j++) { + if (followedPrev.at(j) == prevXRefOffset) { + followed = true; + break; + } + } + if (followed) { + error(errSyntaxError, -1, "Circular XRef"); + if (!(ok = constructXRef(NULL))) { + errCode = errDamaged; + } + break; + } + + followedPrev.push_back (prevXRefOffset); + + std::vector<Goffset> followedXRefStm; + if (!readXRef(&prevXRefOffset, &followedXRefStm, xrefStreamObjsNum)) { + prevXRefOffset = 0; + } + + // if there was a problem with the xref table, or we haven't found the entry + // we were looking for, try to reconstruct the xref + if (!ok || (!prevXRefOffset && untilEntryNum != -1 && entries[untilEntryNum].type == xrefEntryNone)) { + GBool wasReconstructed = false; + if (!(ok = constructXRef(&wasReconstructed))) { + errCode = errDamaged; + break; + } + break; + } + } +} + +XRefEntry *XRef::getEntry(int i, GBool complainIfMissing) +{ + if (entries[i].type == xrefEntryNone) { + + if ((!xRefStream) && mainXRefEntriesOffset) { + if (!parseEntry(mainXRefEntriesOffset + 20*i, &entries[i])) { + error(errSyntaxError, -1, "Failed to parse XRef entry [{0:d}].", i); + } + } else { + // Read XRef tables until the entry we're looking for is found + readXRefUntil(i); + + // We might have reconstructed the xref + // Check again i is in bounds + if (unlikely(i >= size)) { + static XRefEntry dummy; + dummy.offset = 0; + dummy.gen = -1; + dummy.type = xrefEntryNone; + dummy.flags = 0; + return &dummy; + } + + if (entries[i].type == xrefEntryNone) { + if (complainIfMissing) { + error(errSyntaxError, -1, "Invalid XRef entry"); + } + entries[i].type = xrefEntryFree; + } + } + } + + return &entries[i]; +} + +// Recursively sets the Unencrypted flag in all referenced xref entries +void XRef::markUnencrypted(Object *obj) { + Object obj1; + + switch (obj->getType()) { + case objArray: + { + Array *array = obj->getArray(); + for (int i = 0; i < array->getLength(); i++) { + markUnencrypted(array->getNF(i, &obj1)); + obj1.free(); + } + break; + } + case objStream: + case objDict: + { + Dict *dict; + if (obj->getType() == objStream) { + Stream *stream = obj->getStream(); + dict = stream->getDict(); + } else { + dict = obj->getDict(); + } + for (int i = 0; i < dict->getLength(); i++) { + markUnencrypted(dict->getValNF(i, &obj1)); + obj1.free(); + } + break; + } + case objRef: + { + Ref ref = obj->getRef(); + XRefEntry *e = getEntry(ref.num); + if (e->getFlag(XRefEntry::Unencrypted)) + return; // We've already been here: prevent infinite recursion + e->setFlag(XRefEntry::Unencrypted, gTrue); + fetch(ref.num, ref.gen, &obj1); + markUnencrypted(&obj1); + obj1.free(); + break; + } + default: + break; + } +} + +void XRef::scanSpecialFlags() { + if (scannedSpecialFlags) { + return; + } + scannedSpecialFlags = gTrue; + + // "Rewind" the XRef linked list, so that readXRefUntil re-reads all XRef + // tables/streams, even those that had already been parsed + prevXRefOffset = mainXRefOffset; + + std::vector<int> xrefStreamObjNums; + if (!streamEndsLen) { // don't do it for already reconstructed xref + readXRefUntil(-1 /* read all xref sections */, &xrefStreamObjNums); + } + + // Mark object streams as DontRewrite, because we write each object + // individually in full rewrite mode. + for (int i = 0; i < size; ++i) { + if (entries[i].type == xrefEntryCompressed) { + const int objStmNum = entries[i].offset; + if (unlikely(objStmNum < 0 || objStmNum >= size)) { + error(errSyntaxError, -1, "Compressed object offset out of xref bounds"); + } else { + getEntry(objStmNum)->setFlag(XRefEntry::DontRewrite, gTrue); + } + } + } + + // Mark XRef streams objects as Unencrypted and DontRewrite + for (size_t i = 0; i < xrefStreamObjNums.size(); ++i) { + const int objNum = xrefStreamObjNums.at(i); + getEntry(objNum)->setFlag(XRefEntry::Unencrypted, gTrue); + getEntry(objNum)->setFlag(XRefEntry::DontRewrite, gTrue); + } + + // Mark objects referred from the Encrypt dict as Unencrypted + Object obj; + markUnencrypted(trailerDict.dictLookupNF("Encrypt", &obj)); + obj.free(); +} + +void XRef::markUnencrypted() { + // Mark objects referred from the Encrypt dict as Unencrypted + Object obj; + trailerDict.dictLookupNF("Encrypt", &obj); + if (obj.isRef()) { + XRefEntry *e = getEntry(obj.getRefNum()); + e->setFlag(XRefEntry::Unencrypted, gTrue); + } + obj.free(); +} + |