/** * read the cross reference table from a PDF file. When this method is called, the file pointer * must point to the start of the word "xref" in the file. Reads the xref table and the trailer * dictionary. If dictionary has a /Prev entry, move file pointer and read new trailer * * @param password */ private void readTrailer(PDFPassword password) throws IOException, PDFAuthenticationFailureException, EncryptionUnsupportedByProductException, EncryptionUnsupportedByPlatformException { // the table of xrefs this.objIdx = new PDFXref[50]; int pos = this.buf.position(); PDFDecrypter newDefaultDecrypter = null; // read a bunch of nested trailer tables while (true) { // make sure we are looking at an xref table if (!nextItemIs("xref")) { this.buf.position(pos); readTrailer15(password); return; // throw new PDFParseException("Expected 'xref' at start of table"); } // read a bunch of linked tabled while (true) { // read until the word "trailer" PDFObject obj = readObject(-1, -1, IdentityDecrypter.getInstance()); if (obj.getType() == PDFObject.KEYWORD && obj.getStringValue().equals("trailer")) { break; } // read the starting position of the reference if (obj.getType() != PDFObject.NUMBER) { throw new PDFParseException("Expected number for first xref entry"); } int refstart = obj.getIntValue(); // read the size of the reference table obj = readObject(-1, -1, IdentityDecrypter.getInstance()); if (obj.getType() != PDFObject.NUMBER) { throw new PDFParseException("Expected number for length of xref table"); } int reflen = obj.getIntValue(); // skip a line readLine(); if (refstart == 1) { // Check and try to fix incorrect Object Number Start int startPos = this.buf.position(); try { byte[] refline = new byte[20]; this.buf.get(refline); if (refline[17] == 'f') { // free PDFXref objIndex = new PDFXref(refline); if (objIndex.getID() == 0 && objIndex.getGeneration() == 65535) { // The highest generation number possible refstart--; } } } catch (Exception e) { // in case of error ignore } this.buf.position(startPos); } // extend the objIdx table, if necessary if (refstart + reflen >= this.objIdx.length) { PDFXref nobjIdx[] = new PDFXref[refstart + reflen]; System.arraycopy(this.objIdx, 0, nobjIdx, 0, this.objIdx.length); this.objIdx = nobjIdx; } // read reference lines for (int refID = refstart; refID < refstart + reflen; refID++) { // each reference line is 20 bytes long byte[] refline = new byte[20]; this.buf.get(refline); // ignore this line if the object ID is already defined if (this.objIdx[refID] != null) { continue; } // see if it's an active object if (refline[17] == 'n') { this.objIdx[refID] = new PDFXref(refline); } else { this.objIdx[refID] = new PDFXref(null); } } } // at this point, the "trailer" word (not EOL) has been read. PDFObject trailerdict = readObject(-1, -1, IdentityDecrypter.getInstance()); if (trailerdict.getType() != PDFObject.DICTIONARY) { throw new IOException("Expected dictionary after \"trailer\""); } // read the root object location if (this.root == null) { this.root = trailerdict.getDictRef("Root"); if (this.root != null) { this.root.setObjectId(PDFObject.OBJ_NUM_TRAILER, PDFObject.OBJ_NUM_TRAILER); } } // read the encryption information if (this.encrypt == null) { this.encrypt = trailerdict.getDictRef("Encrypt"); if (this.encrypt != null) { this.encrypt.setObjectId(PDFObject.OBJ_NUM_TRAILER, PDFObject.OBJ_NUM_TRAILER); } if (this.encrypt != null && !PDFDecrypterFactory.isFilterExist(this.encrypt)) { this.encrypt = null; // the filter is not located at this trailer, we will try later again } else { newDefaultDecrypter = PDFDecrypterFactory.createDecryptor( this.encrypt, trailerdict.getDictRef("ID"), password); } } if (this.info == null) { this.info = trailerdict.getDictRef("Info"); if (this.info != null) { if (!this.info.isIndirect()) { throw new PDFParseException("Info in trailer must be an indirect reference"); } this.info.setObjectId(PDFObject.OBJ_NUM_TRAILER, PDFObject.OBJ_NUM_TRAILER); } } // support for hybrid-PDFs containing an additional compressed-xref-stream PDFObject xrefstmPos = trailerdict.getDictRef("XRefStm"); if (xrefstmPos != null) { int pos14 = this.buf.position(); this.buf.position(xrefstmPos.getIntValue()); readTrailer15(password); this.buf.position(pos14); } // read the location of the previous xref table PDFObject prevloc = trailerdict.getDictRef("Prev"); if (prevloc != null) { this.buf.position(prevloc.getIntValue()); } else { break; } // see if we have an optional Version entry if (this.root.getDictRef("Version") != null) { processVersion(this.root.getDictRef("Version").getStringValue()); } } // make sure we found a root if (this.root == null) { throw new PDFParseException("No /Root key found in trailer dictionary"); } if (this.encrypt != null && newDefaultDecrypter != null) { PDFObject permissions = this.encrypt.getDictRef("P"); if (permissions != null && !newDefaultDecrypter.isOwnerAuthorised()) { int perms = permissions != null ? permissions.getIntValue() : 0; if (permissions != null) { this.printable = (perms & 4) != 0; this.saveable = (perms & 16) != 0; } } // Install the new default decrypter only after the trailer has // been read, as nothing we're reading passing through is encrypted this.defaultDecrypter = newDefaultDecrypter; } // dereference the root object this.root.dereference(); }
public boolean resolves(PDFXref ref) { // it's expected that the object number is relevant return type != Type.FREE && generation == ref.getGeneration(); }
/** * Used internally to track down PDFObject references. You should never need to call this. * * <p>Since this is the only public method for tracking down PDF objects, it is synchronized. This * means that the PDFFile can only hunt down one object at a time, preventing the file's location * from getting messed around. * * <p>This call stores the current buffer position before any changes are made and restores it * afterwards, so callers need not know that the position has changed. */ public synchronized PDFObject dereference(PDFXref ref, PDFDecrypter decrypter) throws IOException { int id = ref.getID(); // make sure the id is valid and has been read if (id >= this.objIdx.length || this.objIdx[id] == null) { return PDFObject.nullObj; } // check to see if this is already dereferenced PDFObject obj = this.objIdx[id].getObject(); if (obj != null) { return obj; } // store the current position in the buffer int startPos = this.buf.position(); boolean compressed = this.objIdx[id].getCompressed(); if (!compressed) { int loc = this.objIdx[id].getFilePos(); if (loc < 0) { return PDFObject.nullObj; } // move to where this object is this.buf.position(loc); // read the object and cache the reference obj = readObject(ref.getID(), ref.getGeneration(), decrypter); } else { // compressed int compId = this.objIdx[id].getID(); int idx = this.objIdx[id].getIndex(); if (idx < 0) return PDFObject.nullObj; PDFXref compRef = new PDFXref(compId, 0); PDFObject compObj = dereference(compRef, decrypter); int first = compObj.getDictionary().get("First").getIntValue(); int length = compObj.getDictionary().get("Length").getIntValue(); int n = compObj.getDictionary().get("N").getIntValue(); if (idx >= n) return PDFObject.nullObj; ByteBuffer strm = compObj.getStreamBuffer(); ByteBuffer oldBuf = this.buf; this.buf = strm; // skip other nums for (int i = 0; i < idx; i++) { PDFObject skip1num = readObject(-1, -1, true, IdentityDecrypter.getInstance()); PDFObject skip2num = readObject(-1, -1, true, IdentityDecrypter.getInstance()); } PDFObject objNumPO = readObject(-1, -1, true, IdentityDecrypter.getInstance()); PDFObject offsetPO = readObject(-1, -1, true, IdentityDecrypter.getInstance()); int objNum = objNumPO.getIntValue(); int offset = offsetPO.getIntValue(); if (objNum != id) return PDFObject.nullObj; this.buf.position(first + offset); obj = readObject(objNum, 0, IdentityDecrypter.getInstance()); this.buf = oldBuf; } if (obj == null) { obj = PDFObject.nullObj; } this.objIdx[id].setObject(obj); // reset to the previous position this.buf.position(startPos); return obj; }