| //======================================================================== |
| // |
| // XRef.cc |
| // |
| // Copyright 1996-2003 Glyph & Cog, LLC |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #ifdef USE_GCC_PRAGMAS |
| #pragma implementation |
| #endif |
| |
| #include <stdlib.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include "goo/gmem.h" |
| #include "Object.h" |
| #include "Stream.h" |
| #include "Lexer.h" |
| #include "Parser.h" |
| #include "Dict.h" |
| #ifndef NO_DECRYPTION |
| #include "Decrypt.h" |
| #endif |
| #include "Error.h" |
| #include "ErrorCodes.h" |
| #include "XRef.h" |
| |
| //------------------------------------------------------------------------ |
| |
| #define xrefSearchSize 1024 // read this many bytes at end of file |
| // to look for 'startxref' |
| |
| #ifndef NO_DECRYPTION |
| //------------------------------------------------------------------------ |
| // 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 |
| #endif |
| |
| //------------------------------------------------------------------------ |
| // ObjectStream |
| //------------------------------------------------------------------------ |
| |
| class ObjectStream { |
| public: |
| |
| // Create an object stream, using object number <objStrNum>, |
| // generation 0. |
| ObjectStream(XRef *xref, int objStrNumA); |
| |
| ~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) |
| }; |
| |
| ObjectStream::ObjectStream(XRef *xref, int objStrNumA) { |
| Stream *str; |
| Parser *parser; |
| int *offsets; |
| Object objStr, obj1, obj2; |
| int first, i; |
| |
| objStrNum = objStrNumA; |
| nObjects = 0; |
| objs = NULL; |
| objNums = NULL; |
| |
| if (!xref->fetch(objStrNum, 0, &objStr)->isStream()) { |
| goto err1; |
| } |
| |
| if (!objStr.streamGetDict()->lookup("N", &obj1)->isInt()) { |
| obj1.free(); |
| goto err1; |
| } |
| nObjects = obj1.getInt(); |
| obj1.free(); |
| if (nObjects <= 0) { |
| goto err1; |
| } |
| |
| if (!objStr.streamGetDict()->lookup("First", &obj1)->isInt()) { |
| obj1.free(); |
| goto err1; |
| } |
| first = obj1.getInt(); |
| obj1.free(); |
| if (first < 0) { |
| goto err1; |
| } |
| |
| if (nObjects*(int)sizeof(int)/sizeof(int) != nObjects) { |
| error(-1, "Invalid 'nObjects'"); |
| goto err1; |
| } |
| |
| objs = new Object[nObjects]; |
| objNums = (int *)gmalloc(nObjects * sizeof(int)); |
| offsets = (int *)gmalloc(nObjects * sizeof(int)); |
| |
| // 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)); |
| for (i = 0; i < nObjects; ++i) { |
| parser->getObj(&obj1); |
| parser->getObj(&obj2); |
| if (!obj1.isInt() || !obj2.isInt()) { |
| obj1.free(); |
| obj2.free(); |
| delete parser; |
| gfree(offsets); |
| goto err1; |
| } |
| objNums[i] = obj1.getInt(); |
| offsets[i] = obj2.getInt(); |
| 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 (i = first; i < offsets[0]; ++i) { |
| 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)); |
| parser->getObj(&objs[i]); |
| while (str->getChar() != EOF) ; |
| delete parser; |
| } |
| |
| gfree(offsets); |
| |
| err1: |
| objStr.free(); |
| return; |
| } |
| |
| 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 |
| //------------------------------------------------------------------------ |
| |
| XRef::XRef(BaseStream *strA, GooString *ownerPassword, GooString *userPassword) { |
| Guint pos; |
| Object obj; |
| |
| ok = gTrue; |
| errCode = errNone; |
| size = 0; |
| entries = NULL; |
| streamEnds = NULL; |
| streamEndsLen = 0; |
| objStr = NULL; |
| |
| // read the trailer |
| str = strA; |
| start = str->getStart(); |
| pos = getStartXref(); |
| |
| // if there was a problem with the 'startxref' position, try to |
| // reconstruct the xref table |
| if (pos == 0) { |
| if (!(ok = constructXRef())) { |
| errCode = errDamaged; |
| return; |
| } |
| |
| // read the xref table |
| } else { |
| while (readXRef(&pos)) ; |
| |
| // if there was a problem with the xref table, |
| // try to reconstruct it |
| if (!ok) { |
| if (!(ok = constructXRef())) { |
| errCode = errDamaged; |
| return; |
| } |
| } |
| } |
| |
| // 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())) { |
| errCode = errDamaged; |
| return; |
| } |
| } |
| |
| // now set the trailer dictionary's xref pointer so we can fetch |
| // indirect objects from it |
| trailerDict.getDict()->setXRef(this); |
| |
| // check for encryption |
| #ifndef NO_DECRYPTION |
| encrypted = gFalse; |
| #endif |
| if (checkEncrypted(ownerPassword, userPassword)) { |
| ok = gFalse; |
| errCode = errEncrypted; |
| return; |
| } |
| } |
| |
| XRef::~XRef() { |
| gfree(entries); |
| trailerDict.free(); |
| if (streamEnds) { |
| gfree(streamEnds); |
| } |
| if (objStr) { |
| delete objStr; |
| } |
| } |
| |
| // Read the 'startxref' position. |
| Guint XRef::getStartXref() { |
| char buf[xrefSearchSize+1]; |
| char *p; |
| int c, n, i; |
| |
| // read last xrefSearchSize bytes |
| str->setPos(xrefSearchSize, -1); |
| for (n = 0; n < xrefSearchSize; ++n) { |
| if ((c = str->getChar()) == EOF) { |
| break; |
| } |
| buf[n] = c; |
| } |
| buf[n] = '\0'; |
| |
| // find startxref |
| for (i = n - 9; i >= 0; --i) { |
| if (!strncmp(&buf[i], "startxref", 9)) { |
| break; |
| } |
| } |
| if (i < 0) { |
| return 0; |
| } |
| for (p = &buf[i+9]; isspace(*p); ++p) ; |
| lastXRefPos = strToUnsigned(p); |
| |
| return lastXRefPos; |
| } |
| |
| // Read one xref table section. Also reads the associated trailer |
| // dictionary, and returns the prev pointer (if any). |
| GBool XRef::readXRef(Guint *pos) { |
| 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))); |
| parser->getObj(&obj); |
| |
| // parse an old-style xref table |
| if (obj.isCmd("xref")) { |
| obj.free(); |
| more = readXRefTable(parser, pos); |
| |
| // parse an xref stream |
| } else if (obj.isInt()) { |
| obj.free(); |
| if (!parser->getObj(&obj)->isInt()) { |
| goto err1; |
| } |
| obj.free(); |
| if (!parser->getObj(&obj)->isCmd("obj")) { |
| goto err1; |
| } |
| obj.free(); |
| if (!parser->getObj(&obj)->isStream()) { |
| goto err1; |
| } |
| 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, Guint *pos) { |
| XRefEntry entry; |
| GBool more; |
| Object obj, obj2; |
| Guint pos2; |
| int first, n, newSize, i; |
| |
| while (1) { |
| parser->getObj(&obj); |
| if (obj.isCmd("trailer")) { |
| obj.free(); |
| break; |
| } |
| if (!obj.isInt()) { |
| goto err1; |
| } |
| first = obj.getInt(); |
| obj.free(); |
| if (!parser->getObj(&obj)->isInt()) { |
| goto err1; |
| } |
| n = obj.getInt(); |
| obj.free(); |
| if (first < 0 || n < 0 || first + n < 0) { |
| goto err1; |
| } |
| if (first + n > size) { |
| for (newSize = size ? 2 * size : 1024; |
| first + n > newSize && newSize > 0; |
| newSize <<= 1) ; |
| if (newSize < 0) { |
| goto err1; |
| } |
| if (newSize*(int)sizeof(XRefEntry)/sizeof(XRefEntry) != newSize) { |
| error(-1, "Invalid 'obj' parameters'"); |
| goto err1; |
| } |
| |
| entries = (XRefEntry *)grealloc(entries, newSize * sizeof(XRefEntry)); |
| for (i = size; i < newSize; ++i) { |
| entries[i].offset = 0xffffffff; |
| entries[i].type = xrefEntryFree; |
| } |
| size = newSize; |
| } |
| for (i = first; i < first + n; ++i) { |
| if (!parser->getObj(&obj)->isInt()) { |
| goto err1; |
| } |
| entry.offset = (Guint)obj.getInt(); |
| obj.free(); |
| if (!parser->getObj(&obj)->isInt()) { |
| goto err1; |
| } |
| entry.gen = obj.getInt(); |
| obj.free(); |
| parser->getObj(&obj); |
| if (obj.isCmd("n")) { |
| entry.type = xrefEntryUncompressed; |
| } else if (obj.isCmd("f")) { |
| entry.type = xrefEntryFree; |
| } else { |
| goto err1; |
| } |
| obj.free(); |
| if (entries[i].offset == 0xffffffff) { |
| 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 = 0xffffffff; |
| } |
| } |
| } |
| } |
| |
| // read the trailer dictionary |
| if (!parser->getObj(&obj)->isDict()) { |
| goto err1; |
| } |
| |
| // get the 'Prev' pointer |
| obj.getDict()->lookupNF("Prev", &obj2); |
| if (obj2.isInt()) { |
| *pos = (Guint)obj2.getInt(); |
| more = gTrue; |
| } else if (obj2.isRef()) { |
| // certain buggy PDF generators generate "/Prev NNN 0 R" instead |
| // of "/Prev NNN" |
| *pos = (Guint)obj2.getRefNum(); |
| more = gTrue; |
| } else { |
| more = gFalse; |
| } |
| obj2.free(); |
| |
| // save the first trailer dictionary |
| if (trailerDict.isNone()) { |
| obj.copy(&trailerDict); |
| } |
| |
| // check for an 'XRefStm' key |
| if (obj.getDict()->lookup("XRefStm", &obj2)->isInt()) { |
| pos2 = (Guint)obj2.getInt(); |
| readXRef(&pos2); |
| if (!ok) { |
| goto err1; |
| } |
| } |
| obj2.free(); |
| |
| obj.free(); |
| return more; |
| |
| err1: |
| obj.free(); |
| ok = gFalse; |
| return gFalse; |
| } |
| |
| GBool XRef::readXRefStream(Stream *xrefStr, Guint *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 (newSize * (int)sizeof(XRefEntry)/sizeof(XRefEntry) != newSize) { |
| error(-1, "Invalid 'size' parameter."); |
| return gFalse; |
| } |
| entries = (XRefEntry *)grealloc(entries, newSize * sizeof(XRefEntry)); |
| for (i = size; i < newSize; ++i) { |
| entries[i].offset = 0xffffffff; |
| entries[i].type = xrefEntryFree; |
| } |
| size = newSize; |
| } |
| |
| 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 || w[i] > 4) { |
| goto err1; |
| } |
| } |
| obj.free(); |
| |
| 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 = (Guint)obj.getInt(); |
| 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) { |
| Guint offset; |
| int type, gen, c, newSize, i, j; |
| |
| if (first + n < 0) { |
| return gFalse; |
| } |
| if (first + n > size) { |
| for (newSize = size ? 2 * size : 1024; |
| first + n > newSize && newSize > 0; |
| newSize <<= 1) ; |
| if (newSize < 0) { |
| return gFalse; |
| } |
| if (newSize*(int)sizeof(XRefEntry)/sizeof(XRefEntry) != newSize) { |
| error(-1, "Invalid 'size' inside xref table."); |
| return gFalse; |
| } |
| entries = (XRefEntry *)grealloc(entries, newSize * sizeof(XRefEntry)); |
| for (i = size; i < newSize; ++i) { |
| entries[i].offset = 0xffffffff; |
| entries[i].type = xrefEntryFree; |
| } |
| size = newSize; |
| } |
| 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; |
| } |
| for (gen = 0, j = 0; j < w[2]; ++j) { |
| if ((c = xrefStr->getChar()) == EOF) { |
| return gFalse; |
| } |
| gen = (gen << 8) + c; |
| } |
| if (entries[i].offset == 0xffffffff) { |
| 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() { |
| Parser *parser; |
| Object newTrailerDict, obj; |
| char buf[256]; |
| Guint pos; |
| int num, gen; |
| int newSize; |
| int streamEndsSize; |
| char *p; |
| int i; |
| GBool gotRoot; |
| |
| gfree(entries); |
| size = 0; |
| entries = NULL; |
| |
| error(0, "PDF file is damaged - attempting to reconstruct xref table..."); |
| gotRoot = gFalse; |
| streamEndsLen = streamEndsSize = 0; |
| |
| str->reset(); |
| while (1) { |
| pos = str->getPos(); |
| if (!str->getLine(buf, 256)) { |
| break; |
| } |
| p = buf; |
| |
| // got trailer dictionary |
| if (!strncmp(p, "trailer", 7)) { |
| obj.initNull(); |
| parser = new Parser(NULL, |
| new Lexer(NULL, |
| str->makeSubStream(start + pos + 7, gFalse, 0, &obj))); |
| parser->getObj(&newTrailerDict); |
| if (newTrailerDict.isDict()) { |
| newTrailerDict.dictLookupNF("Root", &obj); |
| if (obj.isRef()) { |
| 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)) { |
| num = atoi(p); |
| if (num > 0) { |
| do { |
| ++p; |
| } while (*p && isdigit(*p)); |
| if (isspace(*p)) { |
| do { |
| ++p; |
| } while (*p && isspace(*p)); |
| if (isdigit(*p)) { |
| gen = atoi(p); |
| do { |
| ++p; |
| } while (*p && isdigit(*p)); |
| if (isspace(*p)) { |
| do { |
| ++p; |
| } while (*p && isspace(*p)); |
| if (!strncmp(p, "obj", 3)) { |
| if (num >= size) { |
| newSize = (num + 1 + 255) & ~255; |
| if (newSize < 0) { |
| error(-1, "Bad object number"); |
| return gFalse; |
| } |
| if (newSize*(int)sizeof(XRefEntry)/sizeof(XRefEntry) != newSize) { |
| error(-1, "Invalid 'obj' parameters."); |
| return gFalse; |
| } |
| entries = (XRefEntry *) |
| grealloc(entries, newSize * sizeof(XRefEntry)); |
| for (i = size; i < newSize; ++i) { |
| entries[i].offset = 0xffffffff; |
| entries[i].type = xrefEntryFree; |
| } |
| size = newSize; |
| } |
| 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)sizeof(int)/sizeof(int) != streamEndsSize) { |
| error(-1, "Invalid 'endstream' parameter."); |
| return gFalse; |
| } |
| streamEnds = (Guint *)grealloc(streamEnds, |
| streamEndsSize * sizeof(int)); |
| } |
| streamEnds[streamEndsLen++] = pos; |
| } |
| } |
| |
| if (gotRoot) |
| return gTrue; |
| |
| error(-1, "Couldn't find trailer dictionary"); |
| return gFalse; |
| } |
| |
| #ifndef NO_DECRYPTION |
| GBool XRef::checkEncrypted(GooString *ownerPassword, GooString *userPassword) { |
| Object encrypt, filterObj, versionObj, revisionObj, lengthObj; |
| Object ownerKey, userKey, permissions, fileID, fileID1; |
| GBool encrypted1; |
| GBool ret; |
| |
| keyLength = 0; |
| encVersion = encRevision = 0; |
| ret = gFalse; |
| |
| permFlags = defPermFlags; |
| ownerPasswordOk = gFalse; |
| trailerDict.dictLookup("Encrypt", &encrypt); |
| if ((encrypted1 = encrypt.isDict())) { |
| ret = gTrue; |
| encrypt.dictLookup("Filter", &filterObj); |
| if (filterObj.isName("Standard")) { |
| encrypt.dictLookup("V", &versionObj); |
| encrypt.dictLookup("R", &revisionObj); |
| encrypt.dictLookup("Length", &lengthObj); |
| encrypt.dictLookup("O", &ownerKey); |
| encrypt.dictLookup("U", &userKey); |
| encrypt.dictLookup("P", &permissions); |
| trailerDict.dictLookup("ID", &fileID); |
| if (versionObj.isInt() && |
| revisionObj.isInt() && |
| ownerKey.isString() && ownerKey.getString()->getLength() == 32 && |
| userKey.isString() && userKey.getString()->getLength() == 32 && |
| permissions.isInt() && |
| fileID.isArray()) { |
| encVersion = versionObj.getInt(); |
| encRevision = revisionObj.getInt(); |
| if (lengthObj.isInt()) { |
| keyLength = lengthObj.getInt() / 8; |
| } else { |
| keyLength = 5; |
| } |
| if (keyLength > 16) { |
| keyLength = 16; |
| } |
| /* special case for revision 2. |
| * See Algorithm 3.2 step 9 from PDF Reference, fifth edition.*/ |
| if (encRevision == 2) { |
| keyLength = 5; |
| } |
| |
| permFlags = permissions.getInt(); |
| if (encVersion >= 1 && encVersion <= 2 && |
| encRevision >= 2 && encRevision <= 3) { |
| fileID.arrayGet(0, &fileID1); |
| if (fileID1.isString()) { |
| if (Decrypt::makeFileKey(encVersion, encRevision, keyLength, |
| ownerKey.getString(), userKey.getString(), |
| permFlags, fileID1.getString(), |
| ownerPassword, userPassword, fileKey, |
| &ownerPasswordOk)) { |
| if (ownerPassword && !ownerPasswordOk) { |
| error(-1, "Incorrect owner password"); |
| } |
| ret = gFalse; |
| } else { |
| error(-1, "Incorrect password"); |
| } |
| } else { |
| error(-1, "Weird encryption info"); |
| } |
| fileID1.free(); |
| } else { |
| error(-1, "Unsupported version/revision (%d/%d) of Standard security handler", |
| encVersion, encRevision); |
| } |
| } else { |
| error(-1, "Weird encryption info"); |
| } |
| fileID.free(); |
| permissions.free(); |
| userKey.free(); |
| ownerKey.free(); |
| lengthObj.free(); |
| revisionObj.free(); |
| versionObj.free(); |
| } else { |
| error(-1, "Unknown security handler '%s'", |
| filterObj.isName() ? filterObj.getName() : "???"); |
| } |
| filterObj.free(); |
| } |
| encrypt.free(); |
| |
| // this flag has to be set *after* we read the O/U/P strings |
| encrypted = encrypted1; |
| |
| return ret; |
| } |
| #else |
| GBool XRef::checkEncrypted(GooString *ownerPassword, GooString *userPassword) { |
| Object obj; |
| GBool encrypted; |
| |
| trailerDict.dictLookup("Encrypt", &obj); |
| if ((encrypted = !obj.isNull())) { |
| error(-1, "PDF file is encrypted and this version of the Xpdf tools"); |
| error(-1, "was built without decryption support."); |
| } |
| obj.free(); |
| return encrypted; |
| } |
| #endif |
| |
| GBool XRef::okToPrint(GBool ignoreOwnerPW) { |
| #ifndef NO_DECRYPTION |
| return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permPrint); |
| #else |
| return gTrue; |
| #endif |
| } |
| |
| // 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) { |
| #ifndef NO_DECRYPTION |
| 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; |
| #endif |
| } |
| |
| GBool XRef::okToChange(GBool ignoreOwnerPW) { |
| #ifndef NO_DECRYPTION |
| return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permChange); |
| #else |
| return gTrue; |
| #endif |
| } |
| |
| GBool XRef::okToCopy(GBool ignoreOwnerPW) { |
| #ifndef NO_DECRYPTION |
| return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permCopy); |
| #else |
| return gTrue; |
| #endif |
| } |
| |
| GBool XRef::okToAddNotes(GBool ignoreOwnerPW) { |
| #ifndef NO_DECRYPTION |
| return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permNotes); |
| #else |
| return gTrue; |
| #endif |
| } |
| |
| GBool XRef::okToFillForm(GBool ignoreOwnerPW) { |
| #ifndef NO_DECRYPTION |
| return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permFillForm); |
| #else |
| return gTrue; |
| #endif |
| } |
| |
| GBool XRef::okToAccessibility(GBool ignoreOwnerPW) { |
| #ifndef NO_DECRYPTION |
| return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permAccessibility); |
| #else |
| return gTrue; |
| #endif |
| } |
| |
| GBool XRef::okToAssemble(GBool ignoreOwnerPW) { |
| #ifndef NO_DECRYPTION |
| return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permAssemble); |
| #else |
| return gTrue; |
| #endif |
| } |
| |
| Object *XRef::fetch(int num, int gen, Object *obj) { |
| XRefEntry *e; |
| Parser *parser; |
| Object obj1, obj2, obj3; |
| |
| // check for bogus ref - this can happen in corrupted PDF files |
| if (num < 0 || num >= size) { |
| goto err; |
| } |
| |
| e = &entries[num]; |
| 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))); |
| parser->getObj(&obj1); |
| parser->getObj(&obj2); |
| parser->getObj(&obj3); |
| if (!obj1.isInt() || obj1.getInt() != num || |
| !obj2.isInt() || obj2.getInt() != gen || |
| !obj3.isCmd("obj")) { |
| goto err; |
| } |
| #ifndef NO_DECRYPTION |
| parser->getObj(obj, encrypted ? fileKey : (Guchar *)NULL, keyLength, |
| num, gen); |
| #else |
| parser->getObj(obj); |
| #endif |
| obj1.free(); |
| obj2.free(); |
| obj3.free(); |
| delete parser; |
| break; |
| |
| case xrefEntryCompressed: |
| if (gen != 0) { |
| goto err; |
| } |
| if (!objStr || objStr->getObjStrNum() != (int)e->offset) { |
| if (objStr) { |
| delete objStr; |
| } |
| objStr = new ObjectStream(this, e->offset); |
| } |
| objStr->getObject(e->gen, num, obj); |
| break; |
| |
| default: |
| goto err; |
| } |
| |
| return obj; |
| |
| err: |
| return obj->initNull(); |
| } |
| |
| 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(Guint streamStart, Guint *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; |
| } |
| |
| Guint XRef::strToUnsigned(char *s) { |
| Guint x; |
| char *p; |
| int i; |
| |
| x = 0; |
| for (p = s, i = 0; *p && isdigit(*p) && i < 10; ++p, ++i) { |
| x = 10 * x + (*p - '0'); |
| } |
| return x; |
| } |