Java tutorial
/* * * This file is part of the iText (R) project. Copyright (c) 1998-2019 iText Group NV * Authors: Bruno Lowagie, Paulo Soares, et al. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT * OF THIRD PARTY RIGHTS * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA, 02110-1301 USA, or download the license from the following URL: * http://itextpdf.com/terms-of-use/ * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * In accordance with Section 7(b) of the GNU Affero General Public License, * a covered work must retain the producer line in every PDF that is created * or manipulated using iText. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the iText software without * disclosing the source code of your own applications. * These activities include: offering paid services to customers as an ASP, * serving PDFs on the fly in a web application, shipping iText with a closed * source product. * * For more information, please contact iText Software Corp. at this * address: sales@itextpdf.com */ package com.itextpdf.text.pdf; import com.itextpdf.text.Document; import com.itextpdf.text.ExceptionConverter; import com.itextpdf.text.PageSize; import com.itextpdf.text.Rectangle; import com.itextpdf.text.error_messages.MessageLocalization; import com.itextpdf.text.exceptions.BadPasswordException; import com.itextpdf.text.exceptions.InvalidPdfException; import com.itextpdf.text.exceptions.UnsupportedPdfException; import com.itextpdf.text.io.RandomAccessSource; import com.itextpdf.text.io.RandomAccessSourceFactory; import com.itextpdf.text.io.WindowRandomAccessSource; import com.itextpdf.text.log.Counter; import com.itextpdf.text.log.CounterFactory; import com.itextpdf.text.log.Level; import com.itextpdf.text.log.Logger; import com.itextpdf.text.log.LoggerFactory; import com.itextpdf.text.pdf.PRTokeniser.TokenType; import com.itextpdf.text.pdf.interfaces.PdfViewerPreferences; import com.itextpdf.text.pdf.internal.PdfViewerPreferencesImp; import com.itextpdf.text.pdf.security.ExternalDecryptionProcess; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cms.CMSEnvelopedData; import org.bouncycastle.cms.RecipientInformation; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.Key; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.zip.InflaterInputStream; /** * Reads a PDF document. * @author Paulo Soares * @author Kazuya Ujihara */ public class PdfReader implements PdfViewerPreferences { /** * The iText developers are not responsible if you decide to change the * value of this static parameter. * @since 5.0.2 */ public static boolean unethicalreading = false; public static boolean debugmode = false; private static final Logger LOGGER = LoggerFactory.getLogger(PdfReader.class); static final PdfName pageInhCandidates[] = { PdfName.MEDIABOX, PdfName.ROTATE, PdfName.RESOURCES, PdfName.CROPBOX }; static final byte endstream[] = PdfEncodings.convertToBytes("endstream", null); static final byte endobj[] = PdfEncodings.convertToBytes("endobj", null); protected PRTokeniser tokens; // Each xref pair is a position // type 0 -> -1, 0 // type 1 -> offset, 0 // type 2 -> index, obj num protected long xref[]; protected HashMap<Integer, IntHashtable> objStmMark; protected LongHashtable objStmToOffset; protected boolean newXrefType; protected ArrayList<PdfObject> xrefObj; PdfDictionary rootPages; protected PdfDictionary trailer; protected PdfDictionary catalog; protected PageRefs pageRefs; protected PRAcroForm acroForm = null; protected boolean acroFormParsed = false; protected boolean encrypted = false; protected boolean rebuilt = false; protected int freeXref; protected boolean tampered = false; protected long lastXref; protected long eofPos; protected char pdfVersion; protected PdfEncryption decrypt; protected byte password[] = null; //added by ujihara for decryption protected Key certificateKey = null; //added by Aiken Sam for certificate decryption protected Certificate certificate = null; //added by Aiken Sam for certificate decryption protected String certificateKeyProvider = null; //added by Aiken Sam for certificate decryption protected ExternalDecryptionProcess externalDecryptionProcess = null; private boolean ownerPasswordUsed; protected ArrayList<PdfString> strings = new ArrayList<PdfString>(); protected boolean sharedStreams = true; protected boolean consolidateNamedDestinations = false; protected boolean remoteToLocalNamedDestinations = false; protected int rValue; protected long pValue; private int objNum; private int objGen; private long fileLength; private boolean hybridXref; private int lastXrefPartial = -1; private boolean partial; private PRIndirectReference cryptoRef; private final PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp(); private boolean encryptionError; /** * Handler which will be used for decompression of pdf streams. */ MemoryLimitsAwareHandler memoryLimitsAwareHandler = null; /** * Holds value of property appendable. */ private boolean appendable; protected static Counter COUNTER = CounterFactory.getCounter(PdfReader.class); protected Counter getCounter() { return COUNTER; } /** * Constructs a new PdfReader. This is the master constructor. * @param byteSource source of bytes for the reader * @param partialRead if true, the reader is opened in partial mode (PDF is parsed on demand), if false, the entire PDF is parsed into memory as the reader opens * @param ownerPassword the password or null if no password is required * @param certificate the certificate or null if no certificate is required * @param certificateKey the key or null if no certificate key is required * @param certificateKeyProvider the name of the key provider, or null if no key is required * @param externalDecryptionProcess * @param closeSourceOnConstructorError if true, the byteSource will be closed if there is an error during construction of this reader */ private PdfReader(RandomAccessSource byteSource, boolean partialRead, byte ownerPassword[], Certificate certificate, Key certificateKey, String certificateKeyProvider, ExternalDecryptionProcess externalDecryptionProcess, boolean closeSourceOnConstructorError) throws IOException { this(byteSource, new ReaderProperties().setCertificate(certificate).setCertificateKey(certificateKey) .setCertificateKeyProvider(certificateKeyProvider) .setExternalDecryptionProcess(externalDecryptionProcess).setOwnerPassword(ownerPassword) .setPartialRead(partialRead).setCloseSourceOnconstructorError(closeSourceOnConstructorError)); } /** * Constructs a new PdfReader. This is the master constructor. * @param byteSource source of bytes for the reader * @param properties the properties which will be used to create the reader */ private PdfReader(RandomAccessSource byteSource, ReaderProperties properties) throws IOException { this.certificate = properties.certificate; this.certificateKey = properties.certificateKey; this.certificateKeyProvider = properties.certificateKeyProvider; this.externalDecryptionProcess = properties.externalDecryptionProcess; this.password = properties.ownerPassword; this.partial = properties.partialRead; this.memoryLimitsAwareHandler = properties.memoryLimitsAwareHandler; try { tokens = getOffsetTokeniser(byteSource); if (partial) { readPdfPartial(); } else { readPdf(); } } catch (IOException e) { if (properties.closeSourceOnconstructorError) byteSource.close(); throw e; } getCounter().read(fileLength); } /** * Reads and parses a PDF document. * @param filename the file name of the document * @throws IOException on error */ public PdfReader(final String filename) throws IOException { this(filename, (byte[]) null); } /** * Reads and parses a PDF document. * @param properties the properties which will be used to create the reader * @param filename the file name of the document * @throws IOException on error */ public PdfReader(ReaderProperties properties, final String filename) throws IOException { this(new RandomAccessSourceFactory().setForceRead(false).setUsePlainRandomAccess(Document.plainRandomAccess) .createBestSource(filename), properties); } /** * Reads and parses a PDF document. * @param filename the file name of the document * @param ownerPassword the password to read the document * @throws IOException on error */ public PdfReader(final String filename, final byte ownerPassword[]) throws IOException { this(new ReaderProperties().setOwnerPassword(ownerPassword), filename); } /** * Reads and parses a PDF document. * @param filename the file name of the document * @param ownerPassword the password to read the document * @param partial indicates if the reader needs to read the document only partially * @throws IOException on error */ public PdfReader(final String filename, final byte ownerPassword[], boolean partial) throws IOException { this(new RandomAccessSourceFactory().setForceRead(false).setUsePlainRandomAccess(Document.plainRandomAccess) .createBestSource(filename), new ReaderProperties().setOwnerPassword(ownerPassword).setPartialRead(partial)); } /** * Reads and parses a PDF document. * @param pdfIn the byte array with the document * @throws IOException on error */ public PdfReader(final byte pdfIn[]) throws IOException { this(new RandomAccessSourceFactory().createSource(pdfIn), new ReaderProperties()); } /** * Reads and parses a PDF document. * @param pdfIn the byte array with the document * @param ownerPassword the password to read the document * @throws IOException on error */ public PdfReader(final byte pdfIn[], final byte ownerPassword[]) throws IOException { this(new RandomAccessSourceFactory().createSource(pdfIn), new ReaderProperties().setOwnerPassword(ownerPassword)); } /** * Reads and parses a PDF document. * @param filename the file name of the document * @param certificate the certificate to read the document * @param certificateKey the private key of the certificate * @param certificateKeyProvider the security provider for certificateKey * @throws IOException on error */ public PdfReader(final String filename, final Certificate certificate, final Key certificateKey, final String certificateKeyProvider) throws IOException { this(new RandomAccessSourceFactory().setForceRead(false).setUsePlainRandomAccess(Document.plainRandomAccess) .createBestSource(filename), new ReaderProperties().setCertificate(certificate).setCertificateKey(certificateKey) .setCertificateKeyProvider(certificateKeyProvider)); } /** * Reads and parses a PDF document. * @param filename the file name of the document * @param certificate * @param externalDecryptionProcess * @throws IOException on error */ public PdfReader(final String filename, Certificate certificate, final ExternalDecryptionProcess externalDecryptionProcess) throws IOException { this(new RandomAccessSourceFactory().setForceRead(false).setUsePlainRandomAccess(Document.plainRandomAccess) .createBestSource(filename), new ReaderProperties().setCertificate(certificate) .setExternalDecryptionProcess(externalDecryptionProcess)); } /** * Reads and parses a PDF document. * * @param pdfIn the document as a byte array * @param certificate * @param externalDecryptionProcess * @throws IOException on error */ public PdfReader(final byte[] pdfIn, Certificate certificate, final ExternalDecryptionProcess externalDecryptionProcess) throws IOException { this(new RandomAccessSourceFactory().setForceRead(false).setUsePlainRandomAccess(Document.plainRandomAccess) .createSource(pdfIn), new ReaderProperties().setCertificate(certificate) .setExternalDecryptionProcess(externalDecryptionProcess)); } /** * Reads and parses a PDF document. * * @param inputStream the PDF file * @param certificate * @param externalDecryptionProcess * @throws IOException on error */ public PdfReader(final InputStream inputStream, final Certificate certificate, final ExternalDecryptionProcess externalDecryptionProcess) throws IOException { this(new RandomAccessSourceFactory().setForceRead(false).setUsePlainRandomAccess(Document.plainRandomAccess) .createSource(inputStream), new ReaderProperties().setCertificate(certificate) .setExternalDecryptionProcess(externalDecryptionProcess)); } /** * Reads and parses a PDF document. * @param url the URL of the document * @throws IOException on error */ public PdfReader(final URL url) throws IOException { this(new RandomAccessSourceFactory().createSource(url), new ReaderProperties()); } /** * Reads and parses a PDF document. * @param url the URL of the document * @param ownerPassword the password to read the document * @throws IOException on error */ public PdfReader(final URL url, final byte ownerPassword[]) throws IOException { this(new RandomAccessSourceFactory().createSource(url), new ReaderProperties().setOwnerPassword(ownerPassword)); } /** * Reads and parses a PDF document. * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the * end but is not closed * @param ownerPassword the password to read the document * @throws IOException on error */ public PdfReader(final InputStream is, final byte ownerPassword[]) throws IOException { this(new RandomAccessSourceFactory().createSource(is), new ReaderProperties().setOwnerPassword(ownerPassword).setCloseSourceOnconstructorError(false)); } /** * Reads and parses a PDF document. * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the * end but is not closed * @throws IOException on error */ public PdfReader(final InputStream is) throws IOException { this(new RandomAccessSourceFactory().createSource(is), new ReaderProperties().setCloseSourceOnconstructorError(false)); } /** * Reads and parses a PDF document. * @param properties the properties which will be used to create the reader * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the * end but is not closed * @throws IOException on error */ public PdfReader(ReaderProperties properties, final InputStream is) throws IOException { this(new RandomAccessSourceFactory().createSource(is), properties); } /** * Reads and parses a PDF document. * @param properties the properties which will be used to create the reader * @param raf the document location * @throws IOException on error */ public PdfReader(ReaderProperties properties, final RandomAccessFileOrArray raf) throws IOException { this(raf.getByteSource(), properties); } /** * Reads and parses a pdf document. Contrary to the other constructors only the xref is read * into memory. The reader is said to be working in "partial" mode as only parts of the pdf * are read as needed. * @param raf the document location * @param ownerPassword the password or <CODE>null</CODE> for no password * @throws IOException on error */ public PdfReader(final RandomAccessFileOrArray raf, final byte ownerPassword[]) throws IOException { this(new ReaderProperties().setOwnerPassword(ownerPassword).setPartialRead(true) .setCloseSourceOnconstructorError(false), raf); } /** * Reads and parses a pdf document. * @param raf the document location * @param ownerPassword the password or <CODE>null</CODE> for no password * @param partial indicates if the reader needs to read the document only partially. See {@link PdfReader#PdfReader(RandomAccessFileOrArray, byte[])} * @throws IOException on error */ public PdfReader(final RandomAccessFileOrArray raf, final byte ownerPassword[], boolean partial) throws IOException { this(raf.getByteSource(), new ReaderProperties().setPartialRead(partial).setOwnerPassword(ownerPassword) .setCloseSourceOnconstructorError(false)); } /** Creates an independent duplicate. * @param reader the <CODE>PdfReader</CODE> to duplicate */ public PdfReader(final PdfReader reader) { this.appendable = reader.appendable; this.consolidateNamedDestinations = reader.consolidateNamedDestinations; this.encrypted = reader.encrypted; this.rebuilt = reader.rebuilt; this.sharedStreams = reader.sharedStreams; this.tampered = reader.tampered; this.password = reader.password; this.pdfVersion = reader.pdfVersion; this.eofPos = reader.eofPos; this.freeXref = reader.freeXref; this.lastXref = reader.lastXref; this.newXrefType = reader.newXrefType; this.tokens = new PRTokeniser(reader.tokens.getSafeFile()); if (reader.decrypt != null) this.decrypt = new PdfEncryption(reader.decrypt); this.pValue = reader.pValue; this.rValue = reader.rValue; this.xrefObj = new ArrayList<PdfObject>(reader.xrefObj); for (int k = 0; k < reader.xrefObj.size(); ++k) { this.xrefObj.set(k, duplicatePdfObject(reader.xrefObj.get(k), this)); } this.pageRefs = new PageRefs(reader.pageRefs, this); this.trailer = (PdfDictionary) duplicatePdfObject(reader.trailer, this); this.catalog = trailer.getAsDict(PdfName.ROOT); this.rootPages = catalog.getAsDict(PdfName.PAGES); this.fileLength = reader.fileLength; this.partial = reader.partial; this.hybridXref = reader.hybridXref; this.objStmToOffset = reader.objStmToOffset; this.xref = reader.xref; this.cryptoRef = (PRIndirectReference) duplicatePdfObject(reader.cryptoRef, this); this.ownerPasswordUsed = reader.ownerPasswordUsed; } /** * Utility method that checks the provided byte source to see if it has junk bytes at the beginning. If junk bytes * are found, construct a tokeniser that ignores the junk. Otherwise, construct a tokeniser for the byte source as it is * @param byteSource the source to check * @return a tokeniser that is guaranteed to start at the PDF header * @throws IOException if there is a problem reading the byte source */ private static PRTokeniser getOffsetTokeniser(RandomAccessSource byteSource) throws IOException { PRTokeniser tok = new PRTokeniser(new RandomAccessFileOrArray(byteSource)); int offset = tok.getHeaderOffset(); if (offset != 0) { RandomAccessSource offsetSource = new WindowRandomAccessSource(byteSource, offset); tok = new PRTokeniser(new RandomAccessFileOrArray(offsetSource)); } return tok; } /** Gets a new file instance of the original PDF * document. * @return a new file instance of the original PDF document */ public RandomAccessFileOrArray getSafeFile() { return tokens.getSafeFile(); } protected PdfReaderInstance getPdfReaderInstance(final PdfWriter writer) { return new PdfReaderInstance(this, writer); } /** Gets the number of pages in the document. * Partial mode: return the value stored in the COUNT field of the pageref * Full mode: return the total number of pages found while loading in the entire document. * @return the number of pages in the document */ public int getNumberOfPages() { return pageRefs.size(); } /** * Returns the document's catalog. This dictionary is not a copy, * any changes will be reflected in the catalog. * @return the document's catalog */ public PdfDictionary getCatalog() { return catalog; } /** * Returns the document's acroform, if it has one. * @return the document's acroform */ public PRAcroForm getAcroForm() { if (!acroFormParsed) { acroFormParsed = true; PdfObject form = catalog.get(PdfName.ACROFORM); if (form != null) { try { acroForm = new PRAcroForm(this); acroForm.readAcroForm((PdfDictionary) getPdfObject(form)); } catch (Exception e) { acroForm = null; } } } return acroForm; } MemoryLimitsAwareHandler getMemoryLimitsAwareHandler() { return memoryLimitsAwareHandler; } /** * Gets the page rotation. This value can be 0, 90, 180 or 270. * @param index the page number. The first page is 1 * @return the page rotation */ public int getPageRotation(final int index) { return getPageRotation(pageRefs.getPageNRelease(index)); } int getPageRotation(final PdfDictionary page) { PdfNumber rotate = page.getAsNumber(PdfName.ROTATE); if (rotate == null) return 0; else { int n = rotate.intValue(); n %= 360; return n < 0 ? n + 360 : n; } } /** Gets the page size, taking rotation into account. This * is a <CODE>Rectangle</CODE> with the value of the /MediaBox and the /Rotate key. * @param index the page number. The first page is 1 * @return a <CODE>Rectangle</CODE> */ public Rectangle getPageSizeWithRotation(final int index) { return getPageSizeWithRotation(pageRefs.getPageNRelease(index)); } /** * Gets the rotated page from a page dictionary. * @param page the page dictionary * @return the rotated page */ public Rectangle getPageSizeWithRotation(final PdfDictionary page) { Rectangle rect = getPageSize(page); int rotation = getPageRotation(page); while (rotation > 0) { rect = rect.rotate(); rotation -= 90; } return rect; } /** Gets the page size without taking rotation into account. This * is the value of the /MediaBox key. * @param index the page number. The first page is 1 * @return the page size */ public Rectangle getPageSize(final int index) { return getPageSize(pageRefs.getPageNRelease(index)); } /** * Gets the page from a page dictionary * @param page the page dictionary * @return the page */ public Rectangle getPageSize(final PdfDictionary page) { PdfArray mediaBox = page.getAsArray(PdfName.MEDIABOX); return getNormalizedRectangle(mediaBox); } /** Gets the crop box without taking rotation into account. This * is the value of the /CropBox key. The crop box is the part * of the document to be displayed or printed. It usually is the same * as the media box but may be smaller. If the page doesn't have a crop * box the page size will be returned. * @param index the page number. The first page is 1 * @return the crop box */ public Rectangle getCropBox(final int index) { PdfDictionary page = pageRefs.getPageNRelease(index); PdfArray cropBox = (PdfArray) getPdfObjectRelease(page.get(PdfName.CROPBOX)); if (cropBox == null) return getPageSize(page); return getNormalizedRectangle(cropBox); } /** Gets the box size. Allowed names are: "crop", "trim", "art", "bleed" and "media". * @param index the page number. The first page is 1 * @param boxName the box name * @return the box rectangle or null */ public Rectangle getBoxSize(final int index, final String boxName) { PdfDictionary page = pageRefs.getPageNRelease(index); PdfArray box = null; if (boxName.equals("trim")) box = (PdfArray) getPdfObjectRelease(page.get(PdfName.TRIMBOX)); else if (boxName.equals("art")) box = (PdfArray) getPdfObjectRelease(page.get(PdfName.ARTBOX)); else if (boxName.equals("bleed")) box = (PdfArray) getPdfObjectRelease(page.get(PdfName.BLEEDBOX)); else if (boxName.equals("crop")) box = (PdfArray) getPdfObjectRelease(page.get(PdfName.CROPBOX)); else if (boxName.equals("media")) box = (PdfArray) getPdfObjectRelease(page.get(PdfName.MEDIABOX)); if (box == null) return null; return getNormalizedRectangle(box); } /** * Returns the content of the document information dictionary as a <CODE>HashMap</CODE> * of <CODE>String</CODE>. * @return content of the document information dictionary */ public HashMap<String, String> getInfo() { HashMap<String, String> map = new HashMap<String, String>(); PdfDictionary info = trailer.getAsDict(PdfName.INFO); if (info == null) return map; for (Object element : info.getKeys()) { PdfName key = (PdfName) element; PdfObject obj = getPdfObject(info.get(key)); if (obj == null) continue; String value = obj.toString(); switch (obj.type()) { case PdfObject.STRING: { value = ((PdfString) obj).toUnicodeString(); break; } case PdfObject.NAME: { value = PdfName.decodeName(value); break; } } map.put(PdfName.decodeName(key.toString()), value); } return map; } /** Normalizes a <CODE>Rectangle</CODE> so that llx and lly are smaller than urx and ury. * @param box the original rectangle * @return a normalized <CODE>Rectangle</CODE> */ public static Rectangle getNormalizedRectangle(final PdfArray box) { float llx = ((PdfNumber) getPdfObjectRelease(box.getPdfObject(0))).floatValue(); float lly = ((PdfNumber) getPdfObjectRelease(box.getPdfObject(1))).floatValue(); float urx = ((PdfNumber) getPdfObjectRelease(box.getPdfObject(2))).floatValue(); float ury = ((PdfNumber) getPdfObjectRelease(box.getPdfObject(3))).floatValue(); return new Rectangle(Math.min(llx, urx), Math.min(lly, ury), Math.max(llx, urx), Math.max(lly, ury)); } /** * Checks if the PDF is a tagged PDF. */ public boolean isTagged() { PdfDictionary markInfo = catalog.getAsDict(PdfName.MARKINFO); if (markInfo == null) return false; if (PdfBoolean.PDFTRUE.equals(markInfo.getAsBoolean(PdfName.MARKED))) { return catalog.getAsDict(PdfName.STRUCTTREEROOT) != null; } else { return false; } } /** * Parses the entire PDF */ protected void readPdf() throws IOException { fileLength = tokens.getFile().length(); pdfVersion = tokens.checkPdfHeader(); if (null == memoryLimitsAwareHandler) { memoryLimitsAwareHandler = new MemoryLimitsAwareHandler(fileLength); } try { readXref(); } catch (Exception e) { try { rebuilt = true; rebuildXref(); lastXref = -1; } catch (Exception ne) { throw new InvalidPdfException(MessageLocalization.getComposedMessage( "rebuild.failed.1.original.message.2", ne.getMessage(), e.getMessage())); } } try { readDocObj(); } catch (Exception e) { if (e instanceof BadPasswordException) throw new BadPasswordException(e.getMessage()); if (rebuilt || encryptionError) throw new InvalidPdfException(e.getMessage()); rebuilt = true; encrypted = false; try { rebuildXref(); lastXref = -1; readDocObj(); } catch (Exception ne) { throw new InvalidPdfException(MessageLocalization.getComposedMessage( "rebuild.failed.1.original.message.2", ne.getMessage(), e.getMessage())); } } strings.clear(); readPages(); //eliminateSharedStreams(); removeUnusedObjects(); } /** * Partially parses the pdf * * */ protected void readPdfPartial() throws IOException { fileLength = tokens.getFile().length(); pdfVersion = tokens.checkPdfHeader(); if (null == memoryLimitsAwareHandler) { memoryLimitsAwareHandler = new MemoryLimitsAwareHandler(fileLength); } try { readXref(); } catch (Exception e) { try { rebuilt = true; rebuildXref(); lastXref = -1; } catch (Exception ne) { throw new InvalidPdfException(MessageLocalization.getComposedMessage( "rebuild.failed.1.original.message.2", ne.getMessage(), e.getMessage()), ne); } } readDocObjPartial(); readPages(); } private boolean equalsArray(final byte ar1[], final byte ar2[], final int size) { for (int k = 0; k < size; ++k) { if (ar1[k] != ar2[k]) return false; } return true; } /** * @throws IOException */ @SuppressWarnings("unchecked") private void readDecryptedDocObj() throws IOException { if (encrypted) return; PdfObject encDic = trailer.get(PdfName.ENCRYPT); if (encDic == null || encDic.toString().equals("null")) return; encryptionError = true; byte[] encryptionKey = null; encrypted = true; PdfDictionary enc = (PdfDictionary) getPdfObject(encDic); //This string of condidions is to determine whether or not the authevent for this PDF is EFOPEN //If it is, we return since the attachments of the PDF are what are encrypted, not the PDF itself. //Without this check we run into a bad password exception when trying to open documents that have an //auth event type of EFOPEN. PdfDictionary cfDict = enc.getAsDict(PdfName.CF); if (cfDict != null) { PdfDictionary stdCFDict = cfDict.getAsDict(PdfName.STDCF); if (stdCFDict != null) { PdfName authEvent = stdCFDict.getAsName(PdfName.AUTHEVENT); if (authEvent != null) { //Return only if the event is EFOPEN and there is no password so that //attachments that are encrypted can still be opened. if (authEvent.compareTo(PdfName.EFOPEN) == 0 && !this.ownerPasswordUsed) return; } } } String s; PdfObject o; PdfArray documentIDs = trailer.getAsArray(PdfName.ID); byte documentID[] = null; if (documentIDs != null) { o = documentIDs.getPdfObject(0); strings.remove(o); s = o.toString(); documentID = com.itextpdf.text.DocWriter.getISOBytes(s); if (documentIDs.size() > 1) strings.remove(documentIDs.getPdfObject(1)); } // just in case we have a broken producer if (documentID == null) documentID = new byte[0]; byte uValue[] = null; byte oValue[] = null; int cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40; int lengthValue = 0; PdfObject filter = getPdfObjectRelease(enc.get(PdfName.FILTER)); if (filter.equals(PdfName.STANDARD)) { s = enc.get(PdfName.U).toString(); strings.remove(enc.get(PdfName.U)); uValue = com.itextpdf.text.DocWriter.getISOBytes(s); s = enc.get(PdfName.O).toString(); strings.remove(enc.get(PdfName.O)); oValue = com.itextpdf.text.DocWriter.getISOBytes(s); if (enc.contains(PdfName.OE)) strings.remove(enc.get(PdfName.OE)); if (enc.contains(PdfName.UE)) strings.remove(enc.get(PdfName.UE)); if (enc.contains(PdfName.PERMS)) strings.remove(enc.get(PdfName.PERMS)); o = enc.get(PdfName.P); if (!o.isNumber()) throw new InvalidPdfException(MessageLocalization.getComposedMessage("illegal.p.value")); pValue = ((PdfNumber) o).longValue(); o = enc.get(PdfName.R); if (!o.isNumber()) throw new InvalidPdfException(MessageLocalization.getComposedMessage("illegal.r.value")); rValue = ((PdfNumber) o).intValue(); switch (rValue) { case 2: cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40; break; case 3: o = enc.get(PdfName.LENGTH); if (!o.isNumber()) throw new InvalidPdfException(MessageLocalization.getComposedMessage("illegal.length.value")); lengthValue = ((PdfNumber) o).intValue(); if (lengthValue > 128 || lengthValue < 40 || lengthValue % 8 != 0) throw new InvalidPdfException(MessageLocalization.getComposedMessage("illegal.length.value")); cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128; break; case 4: PdfDictionary dic = (PdfDictionary) enc.get(PdfName.CF); if (dic == null) throw new InvalidPdfException( MessageLocalization.getComposedMessage("cf.not.found.encryption")); dic = (PdfDictionary) dic.get(PdfName.STDCF); if (dic == null) throw new InvalidPdfException( MessageLocalization.getComposedMessage("stdcf.not.found.encryption")); if (PdfName.V2.equals(dic.get(PdfName.CFM))) cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128; else if (PdfName.AESV2.equals(dic.get(PdfName.CFM))) cryptoMode = PdfWriter.ENCRYPTION_AES_128; else throw new UnsupportedPdfException( MessageLocalization.getComposedMessage("no.compatible.encryption.found")); PdfObject em = enc.get(PdfName.ENCRYPTMETADATA); if (em != null && em.toString().equals("false")) cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA; break; case 5: cryptoMode = PdfWriter.ENCRYPTION_AES_256; PdfObject em5 = enc.get(PdfName.ENCRYPTMETADATA); if (em5 != null && em5.toString().equals("false")) cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA; break; default: throw new UnsupportedPdfException( MessageLocalization.getComposedMessage("unknown.encryption.type.r.eq.1", rValue)); } } else if (filter.equals(PdfName.PUBSEC)) { boolean foundRecipient = false; byte[] envelopedData = null; PdfArray recipients = null; o = enc.get(PdfName.V); if (!o.isNumber()) throw new InvalidPdfException(MessageLocalization.getComposedMessage("illegal.v.value")); int vValue = ((PdfNumber) o).intValue(); switch (vValue) { case 1: cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40; lengthValue = 40; recipients = (PdfArray) enc.get(PdfName.RECIPIENTS); break; case 2: o = enc.get(PdfName.LENGTH); if (!o.isNumber()) throw new InvalidPdfException(MessageLocalization.getComposedMessage("illegal.length.value")); lengthValue = ((PdfNumber) o).intValue(); if (lengthValue > 128 || lengthValue < 40 || lengthValue % 8 != 0) throw new InvalidPdfException(MessageLocalization.getComposedMessage("illegal.length.value")); cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128; recipients = (PdfArray) enc.get(PdfName.RECIPIENTS); break; case 4: case 5: PdfDictionary dic = (PdfDictionary) enc.get(PdfName.CF); if (dic == null) throw new InvalidPdfException( MessageLocalization.getComposedMessage("cf.not.found.encryption")); dic = (PdfDictionary) dic.get(PdfName.DEFAULTCRYPTFILTER); if (dic == null) throw new InvalidPdfException( MessageLocalization.getComposedMessage("defaultcryptfilter.not.found.encryption")); if (PdfName.V2.equals(dic.get(PdfName.CFM))) { cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128; lengthValue = 128; } else if (PdfName.AESV2.equals(dic.get(PdfName.CFM))) { cryptoMode = PdfWriter.ENCRYPTION_AES_128; lengthValue = 128; } else if (PdfName.AESV3.equals(dic.get(PdfName.CFM))) { cryptoMode = PdfWriter.ENCRYPTION_AES_256; lengthValue = 256; } else throw new UnsupportedPdfException( MessageLocalization.getComposedMessage("no.compatible.encryption.found")); PdfObject em = dic.get(PdfName.ENCRYPTMETADATA); if (em != null && em.toString().equals("false")) cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA; recipients = (PdfArray) dic.get(PdfName.RECIPIENTS); break; default: throw new UnsupportedPdfException( MessageLocalization.getComposedMessage("unknown.encryption.type.v.eq.1", vValue)); } X509CertificateHolder certHolder; try { certHolder = new X509CertificateHolder(certificate.getEncoded()); } catch (Exception f) { throw new ExceptionConverter(f); } if (externalDecryptionProcess == null) { for (int i = 0; i < recipients.size(); i++) { PdfObject recipient = recipients.getPdfObject(i); strings.remove(recipient); CMSEnvelopedData data = null; try { data = new CMSEnvelopedData(recipient.getBytes()); Iterator<RecipientInformation> recipientCertificatesIt = data.getRecipientInfos() .getRecipients().iterator(); while (recipientCertificatesIt.hasNext()) { RecipientInformation recipientInfo = recipientCertificatesIt.next(); if (recipientInfo.getRID().match(certHolder) && !foundRecipient) { envelopedData = PdfEncryptor.getContent(recipientInfo, (PrivateKey) certificateKey, certificateKeyProvider); foundRecipient = true; } } } catch (Exception f) { throw new ExceptionConverter(f); } } } else { for (int i = 0; i < recipients.size(); i++) { PdfObject recipient = recipients.getPdfObject(i); strings.remove(recipient); CMSEnvelopedData data = null; try { data = new CMSEnvelopedData(recipient.getBytes()); RecipientInformation recipientInfo = data.getRecipientInfos() .get(externalDecryptionProcess.getCmsRecipientId()); if (recipientInfo != null) { envelopedData = recipientInfo.getContent(externalDecryptionProcess.getCmsRecipient()); foundRecipient = true; } } catch (Exception f) { throw new ExceptionConverter(f); } } } if (!foundRecipient || envelopedData == null) { throw new UnsupportedPdfException( MessageLocalization.getComposedMessage("bad.certificate.and.key")); } MessageDigest md = null; try { if ((cryptoMode & PdfWriter.ENCRYPTION_MASK) == PdfWriter.ENCRYPTION_AES_256) md = MessageDigest.getInstance("SHA-256"); else md = MessageDigest.getInstance("SHA-1"); md.update(envelopedData, 0, 20); for (int i = 0; i < recipients.size(); i++) { byte[] encodedRecipient = recipients.getPdfObject(i).getBytes(); md.update(encodedRecipient); } if ((cryptoMode & PdfWriter.DO_NOT_ENCRYPT_METADATA) != 0) md.update(new byte[] { (byte) 255, (byte) 255, (byte) 255, (byte) 255 }); encryptionKey = md.digest(); } catch (Exception f) { throw new ExceptionConverter(f); } } decrypt = new PdfEncryption(); decrypt.setCryptoMode(cryptoMode, lengthValue); if (filter.equals(PdfName.STANDARD)) { if (rValue == 5) { ownerPasswordUsed = decrypt.readKey(enc, password); decrypt.documentID = documentID; pValue = decrypt.getPermissions(); } else { //check by owner password decrypt.setupByOwnerPassword(documentID, password, uValue, oValue, pValue); if (!equalsArray(uValue, decrypt.userKey, rValue == 3 || rValue == 4 ? 16 : 32)) { //check by user password decrypt.setupByUserPassword(documentID, password, oValue, pValue); if (!equalsArray(uValue, decrypt.userKey, rValue == 3 || rValue == 4 ? 16 : 32)) { throw new BadPasswordException(MessageLocalization.getComposedMessage("bad.user.password")); } } else ownerPasswordUsed = true; } } else if (filter.equals(PdfName.PUBSEC)) { decrypt.documentID = documentID; if ((cryptoMode & PdfWriter.ENCRYPTION_MASK) == PdfWriter.ENCRYPTION_AES_256) decrypt.setKey(encryptionKey); else decrypt.setupByEncryptionKey(encryptionKey, lengthValue); ownerPasswordUsed = true; } for (int k = 0; k < strings.size(); ++k) { PdfString str = strings.get(k); str.decrypt(this); } if (encDic.isIndirect()) { cryptoRef = (PRIndirectReference) encDic; xrefObj.set(cryptoRef.getNumber(), null); } encryptionError = false; } /** * @param obj * @return a PdfObject */ public static PdfObject getPdfObjectRelease(final PdfObject obj) { PdfObject obj2 = getPdfObject(obj); releaseLastXrefPartial(obj); return obj2; } /** * Reads a <CODE>PdfObject</CODE> resolving an indirect reference * if needed. * @param obj the <CODE>PdfObject</CODE> to read * @return the resolved <CODE>PdfObject</CODE> */ public static PdfObject getPdfObject(PdfObject obj) { if (obj == null) return null; if (!obj.isIndirect()) return obj; try { PRIndirectReference ref = (PRIndirectReference) obj; int idx = ref.getNumber(); boolean appendable = ref.getReader().appendable; obj = ref.getReader().getPdfObject(idx); if (obj == null) { return null; } else { if (appendable) { switch (obj.type()) { case PdfObject.NULL: obj = new PdfNull(); break; case PdfObject.BOOLEAN: obj = new PdfBoolean(((PdfBoolean) obj).booleanValue()); break; case PdfObject.NAME: obj = new PdfName(obj.getBytes()); break; } obj.setIndRef(ref); } return obj; } } catch (Exception e) { throw new ExceptionConverter(e); } } /** * Reads a <CODE>PdfObject</CODE> resolving an indirect reference * if needed. If the reader was opened in partial mode the object will be released * to save memory. * @param obj the <CODE>PdfObject</CODE> to read * @param parent * @return a PdfObject */ public static PdfObject getPdfObjectRelease(final PdfObject obj, final PdfObject parent) { PdfObject obj2 = getPdfObject(obj, parent); releaseLastXrefPartial(obj); return obj2; } /** * @param obj * @param parent * @return a PdfObject */ public static PdfObject getPdfObject(PdfObject obj, final PdfObject parent) { if (obj == null) return null; if (!obj.isIndirect()) { PRIndirectReference ref = null; if (parent != null && (ref = parent.getIndRef()) != null && ref.getReader().isAppendable()) { switch (obj.type()) { case PdfObject.NULL: obj = new PdfNull(); break; case PdfObject.BOOLEAN: obj = new PdfBoolean(((PdfBoolean) obj).booleanValue()); break; case PdfObject.NAME: obj = new PdfName(obj.getBytes()); break; } obj.setIndRef(ref); } return obj; } return getPdfObject(obj); } /** * @param idx * @return a PdfObject */ public PdfObject getPdfObjectRelease(final int idx) { PdfObject obj = getPdfObject(idx); releaseLastXrefPartial(); return obj; } /** * @param idx * @return aPdfObject */ public PdfObject getPdfObject(final int idx) { try { lastXrefPartial = -1; if (idx < 0 || idx >= xrefObj.size()) return null; PdfObject obj = xrefObj.get(idx); if (!partial || obj != null) return obj; if (idx * 2 >= xref.length) return null; obj = readSingleObject(idx); lastXrefPartial = -1; if (obj != null) lastXrefPartial = idx; return obj; } catch (Exception e) { throw new ExceptionConverter(e); } } /** * */ public void resetLastXrefPartial() { lastXrefPartial = -1; } /** * */ public void releaseLastXrefPartial() { if (partial && lastXrefPartial != -1) { xrefObj.set(lastXrefPartial, null); lastXrefPartial = -1; } } /** * @param obj */ public static void releaseLastXrefPartial(final PdfObject obj) { if (obj == null) return; if (!obj.isIndirect()) return; if (!(obj instanceof PRIndirectReference)) return; PRIndirectReference ref = (PRIndirectReference) obj; PdfReader reader = ref.getReader(); if (reader.partial && reader.lastXrefPartial != -1 && reader.lastXrefPartial == ref.getNumber()) { reader.xrefObj.set(reader.lastXrefPartial, null); } reader.lastXrefPartial = -1; } private void setXrefPartialObject(final int idx, final PdfObject obj) { if (!partial || idx < 0) return; xrefObj.set(idx, obj); } /** * @param obj * @return an indirect reference */ public PRIndirectReference addPdfObject(final PdfObject obj) { xrefObj.add(obj); return new PRIndirectReference(this, xrefObj.size() - 1); } protected void readPages() throws IOException { catalog = trailer.getAsDict(PdfName.ROOT); if (catalog == null) { throw new InvalidPdfException( MessageLocalization.getComposedMessage("the.document.has.no.catalog.object")); } rootPages = catalog.getAsDict(PdfName.PAGES); if (rootPages == null || (!PdfName.PAGES.equals(rootPages.get(PdfName.TYPE)) && !PdfName.PAGES.equals(rootPages.get(new PdfName("Types"))))) { if (debugmode) { if (LOGGER.isLogging(Level.ERROR)) { LOGGER.error(MessageLocalization.getComposedMessage("the.document.has.no.page.root")); } } else { throw new InvalidPdfException( MessageLocalization.getComposedMessage("the.document.has.no.page.root")); } } pageRefs = new PageRefs(this); } protected void readDocObjPartial() throws IOException { xrefObj = new ArrayList<PdfObject>(xref.length / 2); xrefObj.addAll(Collections.<PdfObject>nCopies(xref.length / 2, null)); readDecryptedDocObj(); if (objStmToOffset != null) { long keys[] = objStmToOffset.getKeys(); for (int k = 0; k < keys.length; ++k) { long n = keys[k]; objStmToOffset.put(n, xref[(int) (n * 2)]); xref[(int) (n * 2)] = -1; } } } protected PdfObject readSingleObject(final int k) throws IOException { strings.clear(); int k2 = k * 2; long pos = xref[k2]; if (pos < 0) return null; if (xref[k2 + 1] > 0) pos = objStmToOffset.get(xref[k2 + 1]); if (pos == 0) return null; tokens.seek(pos); tokens.nextValidToken(); if (tokens.getTokenType() != TokenType.NUMBER) tokens.throwError(MessageLocalization.getComposedMessage("invalid.object.number")); objNum = tokens.intValue(); tokens.nextValidToken(); if (tokens.getTokenType() != TokenType.NUMBER) tokens.throwError(MessageLocalization.getComposedMessage("invalid.generation.number")); objGen = tokens.intValue(); tokens.nextValidToken(); if (!tokens.getStringValue().equals("obj")) tokens.throwError(MessageLocalization.getComposedMessage("token.obj.expected")); PdfObject obj; try { obj = readPRObject(); for (int j = 0; j < strings.size(); ++j) { PdfString str = strings.get(j); str.decrypt(this); } if (obj.isStream()) { checkPRStreamLength((PRStream) obj); } } catch (IOException e) { if (debugmode) { if (LOGGER.isLogging(Level.ERROR)) LOGGER.error(e.getMessage(), e); obj = null; } else throw e; } if (xref[k2 + 1] > 0) { obj = readOneObjStm((PRStream) obj, (int) xref[k2]); } xrefObj.set(k, obj); return obj; } protected PdfObject readOneObjStm(final PRStream stream, int idx) throws IOException { int first = stream.getAsNumber(PdfName.FIRST).intValue(); byte b[] = getStreamBytes(stream, tokens.getFile()); PRTokeniser saveTokens = tokens; tokens = new PRTokeniser(new RandomAccessFileOrArray(new RandomAccessSourceFactory().createSource(b))); try { int address = 0; boolean ok = true; ++idx; for (int k = 0; k < idx; ++k) { ok = tokens.nextToken(); if (!ok) break; if (tokens.getTokenType() != TokenType.NUMBER) { ok = false; break; } ok = tokens.nextToken(); if (!ok) break; if (tokens.getTokenType() != TokenType.NUMBER) { ok = false; break; } address = tokens.intValue() + first; } if (!ok) throw new InvalidPdfException(MessageLocalization.getComposedMessage("error.reading.objstm")); tokens.seek(address); tokens.nextToken(); PdfObject obj; if (tokens.getTokenType() == PRTokeniser.TokenType.NUMBER) { obj = new PdfNumber(tokens.getStringValue()); } else { tokens.seek(address); obj = readPRObject(); } return obj; //return readPRObject(); } finally { tokens = saveTokens; } } /** * @return the percentage of the cross reference table that has been read */ public double dumpPerc() { int total = 0; for (int k = 0; k < xrefObj.size(); ++k) { if (xrefObj.get(k) != null) ++total; } return total * 100.0 / xrefObj.size(); } protected void readDocObj() throws IOException { ArrayList<PRStream> streams = new ArrayList<PRStream>(); xrefObj = new ArrayList<PdfObject>(xref.length / 2); xrefObj.addAll(Collections.<PdfObject>nCopies(xref.length / 2, null)); for (int k = 2; k < xref.length; k += 2) { long pos = xref[k]; if (pos <= 0 || xref[k + 1] > 0) continue; tokens.seek(pos); tokens.nextValidToken(); if (tokens.getTokenType() != TokenType.NUMBER) tokens.throwError(MessageLocalization.getComposedMessage("invalid.object.number")); objNum = tokens.intValue(); tokens.nextValidToken(); if (tokens.getTokenType() != TokenType.NUMBER) tokens.throwError(MessageLocalization.getComposedMessage("invalid.generation.number")); objGen = tokens.intValue(); tokens.nextValidToken(); if (!tokens.getStringValue().equals("obj")) tokens.throwError(MessageLocalization.getComposedMessage("token.obj.expected")); PdfObject obj; try { obj = readPRObject(); if (obj.isStream()) { streams.add((PRStream) obj); } } catch (IOException e) { if (debugmode) { if (LOGGER.isLogging(Level.ERROR)) LOGGER.error(e.getMessage(), e); obj = null; } else throw e; } xrefObj.set(k / 2, obj); } for (int k = 0; k < streams.size(); ++k) { checkPRStreamLength(streams.get(k)); } readDecryptedDocObj(); if (objStmMark != null) { for (Map.Entry<Integer, IntHashtable> entry : objStmMark.entrySet()) { int n = entry.getKey().intValue(); IntHashtable h = entry.getValue(); readObjStm((PRStream) xrefObj.get(n), h); xrefObj.set(n, null); } objStmMark = null; } xref = null; } private void checkPRStreamLength(final PRStream stream) throws IOException { long fileLength = tokens.length(); long start = stream.getOffset(); boolean calc = false; long streamLength = 0; PdfObject obj = getPdfObjectRelease(stream.get(PdfName.LENGTH)); if (obj != null && obj.type() == PdfObject.NUMBER) { streamLength = ((PdfNumber) obj).intValue(); if (streamLength + start > fileLength - 20) calc = true; else { tokens.seek(start + streamLength); String line = tokens.readString(20); if (!line.startsWith("\nendstream") && !line.startsWith("\r\nendstream") && !line.startsWith("\rendstream") && !line.startsWith("endstream")) calc = true; } } else calc = true; if (calc) { byte tline[] = new byte[16]; tokens.seek(start); long pos; while (true) { pos = tokens.getFilePointer(); if (!tokens.readLineSegment(tline, false)) // added boolean because of mailing list issue (17 Feb. 2014) break; if (equalsn(tline, endstream)) { streamLength = pos - start; break; } if (equalsn(tline, endobj)) { tokens.seek(pos - 16); String s = tokens.readString(16); int index = s.indexOf("endstream"); if (index >= 0) pos = pos - 16 + index; streamLength = pos - start; break; } } tokens.seek(pos - 2); if (tokens.read() == 13) streamLength--; tokens.seek(pos - 1); if (tokens.read() == 10) streamLength--; if (streamLength < 0) { streamLength = 0; } } stream.setLength((int) streamLength); } protected void readObjStm(final PRStream stream, final IntHashtable map) throws IOException { if (stream == null) return; int first = stream.getAsNumber(PdfName.FIRST).intValue(); int n = stream.getAsNumber(PdfName.N).intValue(); byte b[] = getStreamBytes(stream, tokens.getFile()); PRTokeniser saveTokens = tokens; tokens = new PRTokeniser(new RandomAccessFileOrArray(new RandomAccessSourceFactory().createSource(b))); try { int address[] = new int[n]; int objNumber[] = new int[n]; boolean ok = true; for (int k = 0; k < n; ++k) { ok = tokens.nextToken(); if (!ok) break; if (tokens.getTokenType() != TokenType.NUMBER) { ok = false; break; } objNumber[k] = tokens.intValue(); ok = tokens.nextToken(); if (!ok) break; if (tokens.getTokenType() != TokenType.NUMBER) { ok = false; break; } address[k] = tokens.intValue() + first; } if (!ok) throw new InvalidPdfException(MessageLocalization.getComposedMessage("error.reading.objstm")); for (int k = 0; k < n; ++k) { if (map.containsKey(k)) { tokens.seek(address[k]); tokens.nextToken(); PdfObject obj; if (tokens.getTokenType() == PRTokeniser.TokenType.NUMBER) { obj = new PdfNumber(tokens.getStringValue()); } else { tokens.seek(address[k]); obj = readPRObject(); } xrefObj.set(objNumber[k], obj); } } } finally { tokens = saveTokens; } } /** * Eliminates the reference to the object freeing the memory used by it and clearing * the xref entry. * @param obj the object. If it's an indirect reference it will be eliminated * @return the object or the already erased dereferenced object */ public static PdfObject killIndirect(final PdfObject obj) { if (obj == null || obj.isNull()) return null; PdfObject ret = getPdfObjectRelease(obj); if (obj.isIndirect()) { PRIndirectReference ref = (PRIndirectReference) obj; PdfReader reader = ref.getReader(); int n = ref.getNumber(); reader.xrefObj.set(n, null); if (reader.partial) reader.xref[n * 2] = -1; } return ret; } private void ensureXrefSize(final int size) { if (size == 0) return; if (xref == null) xref = new long[size]; else { if (xref.length < size) { long xref2[] = new long[size]; System.arraycopy(xref, 0, xref2, 0, xref.length); xref = xref2; } } } protected void readXref() throws IOException { hybridXref = false; newXrefType = false; tokens.seek(tokens.getStartxref()); tokens.nextToken(); if (!tokens.getStringValue().equals("startxref")) throw new InvalidPdfException(MessageLocalization.getComposedMessage("startxref.not.found")); tokens.nextToken(); if (tokens.getTokenType() != TokenType.NUMBER) throw new InvalidPdfException( MessageLocalization.getComposedMessage("startxref.is.not.followed.by.a.number")); long startxref = tokens.longValue(); lastXref = startxref; eofPos = tokens.getFilePointer(); try { if (readXRefStream(startxref)) { newXrefType = true; return; } } catch (Exception e) { } xref = null; tokens.seek(startxref); trailer = readXrefSection(); PdfDictionary trailer2 = trailer; while (true) { PdfNumber prev = (PdfNumber) trailer2.get(PdfName.PREV); if (prev == null) break; if (prev.longValue() == startxref) throw new InvalidPdfException(MessageLocalization .getComposedMessage("trailer.prev.entry.points.to.its.own.cross.reference.section")); startxref = prev.longValue(); tokens.seek(startxref); trailer2 = readXrefSection(); } } protected PdfDictionary readXrefSection() throws IOException { tokens.nextValidToken(); if (!tokens.getStringValue().equals("xref")) tokens.throwError(MessageLocalization.getComposedMessage("xref.subsection.not.found")); int start = 0; int end = 0; long pos = 0; int gen = 0; while (true) { tokens.nextValidToken(); if (tokens.getStringValue().equals("trailer")) break; if (tokens.getTokenType() != TokenType.NUMBER) tokens.throwError(MessageLocalization .getComposedMessage("object.number.of.the.first.object.in.this.xref.subsection.not.found")); start = tokens.intValue(); tokens.nextValidToken(); if (tokens.getTokenType() != TokenType.NUMBER) tokens.throwError(MessageLocalization .getComposedMessage("number.of.entries.in.this.xref.subsection.not.found")); end = tokens.intValue() + start; if (start == 1) { // fix incorrect start number long back = tokens.getFilePointer(); tokens.nextValidToken(); pos = tokens.longValue(); tokens.nextValidToken(); gen = tokens.intValue(); if (pos == 0 && gen == PdfWriter.GENERATION_MAX) { --start; --end; } tokens.seek(back); } ensureXrefSize(end * 2); for (int k = start; k < end; ++k) { tokens.nextValidToken(); pos = tokens.longValue(); tokens.nextValidToken(); gen = tokens.intValue(); tokens.nextValidToken(); int p = k * 2; if (tokens.getStringValue().equals("n")) { if (xref[p] == 0 && xref[p + 1] == 0) { // if (pos == 0) // tokens.throwError(MessageLocalization.getComposedMessage("file.position.0.cross.reference.entry.in.this.xref.subsection")); xref[p] = pos; } } else if (tokens.getStringValue().equals("f")) { if (xref[p] == 0 && xref[p + 1] == 0) xref[p] = -1; } else tokens.throwError(MessageLocalization .getComposedMessage("invalid.cross.reference.entry.in.this.xref.subsection")); } } PdfDictionary trailer = (PdfDictionary) readPRObject(); PdfNumber xrefSize = (PdfNumber) trailer.get(PdfName.SIZE); ensureXrefSize(xrefSize.intValue() * 2); PdfObject xrs = trailer.get(PdfName.XREFSTM); if (xrs != null && xrs.isNumber()) { int loc = ((PdfNumber) xrs).intValue(); try { readXRefStream(loc); newXrefType = true; hybridXref = true; } catch (IOException e) { xref = null; throw e; } } return trailer; } protected boolean readXRefStream(final long ptr) throws IOException { tokens.seek(ptr); int thisStream = 0; if (!tokens.nextToken()) return false; if (tokens.getTokenType() != TokenType.NUMBER) return false; thisStream = tokens.intValue(); if (!tokens.nextToken() || tokens.getTokenType() != TokenType.NUMBER) return false; if (!tokens.nextToken() || !tokens.getStringValue().equals("obj")) return false; PdfObject object = readPRObject(); PRStream stm = null; if (object.isStream()) { stm = (PRStream) object; if (!PdfName.XREF.equals(stm.get(PdfName.TYPE))) return false; } else return false; if (trailer == null) { trailer = new PdfDictionary(); trailer.putAll(stm); } stm.setLength(((PdfNumber) stm.get(PdfName.LENGTH)).intValue()); int size = ((PdfNumber) stm.get(PdfName.SIZE)).intValue(); PdfArray index; PdfObject obj = stm.get(PdfName.INDEX); if (obj == null) { index = new PdfArray(); index.add(new int[] { 0, size }); } else index = (PdfArray) obj; PdfArray w = (PdfArray) stm.get(PdfName.W); long prev = -1; obj = stm.get(PdfName.PREV); if (obj != null) prev = ((PdfNumber) obj).longValue(); // Each xref pair is a position // type 0 -> -1, 0 // type 1 -> offset, 0 // type 2 -> index, obj num ensureXrefSize(size * 2); if (objStmMark == null && !partial) objStmMark = new HashMap<Integer, IntHashtable>(); if (objStmToOffset == null && partial) objStmToOffset = new LongHashtable(); byte b[] = getStreamBytes(stm, tokens.getFile()); int bptr = 0; int wc[] = new int[3]; for (int k = 0; k < 3; ++k) wc[k] = w.getAsNumber(k).intValue(); for (int idx = 0; idx < index.size(); idx += 2) { int start = index.getAsNumber(idx).intValue(); int length = index.getAsNumber(idx + 1).intValue(); ensureXrefSize((start + length) * 2); while (length-- > 0) { int type = 1; if (wc[0] > 0) { type = 0; for (int k = 0; k < wc[0]; ++k) type = (type << 8) + (b[bptr++] & 0xff); } long field2 = 0; for (int k = 0; k < wc[1]; ++k) field2 = (field2 << 8) + (b[bptr++] & 0xff); int field3 = 0; for (int k = 0; k < wc[2]; ++k) field3 = (field3 << 8) + (b[bptr++] & 0xff); int base = start * 2; if (xref[base] == 0 && xref[base + 1] == 0) { switch (type) { case 0: xref[base] = -1; break; case 1: xref[base] = field2; break; case 2: xref[base] = field3; xref[base + 1] = field2; if (partial) { objStmToOffset.put(field2, 0); } else { Integer on = Integer.valueOf((int) field2); IntHashtable seq = objStmMark.get(on); if (seq == null) { seq = new IntHashtable(); seq.put(field3, 1); objStmMark.put(on, seq); } else seq.put(field3, 1); } break; } } ++start; } } thisStream *= 2; if (thisStream + 1 < xref.length && xref[thisStream] == 0 && xref[thisStream + 1] == 0) xref[thisStream] = -1; if (prev == -1) return true; return readXRefStream(prev); } protected void rebuildXref() throws IOException { hybridXref = false; newXrefType = false; tokens.seek(0); long xr[][] = new long[1024][]; long top = 0; trailer = null; byte line[] = new byte[64]; for (;;) { long pos = tokens.getFilePointer(); if (!tokens.readLineSegment(line, true)) // added boolean because of mailing list issue (17 Feb. 2014) break; if (line[0] == 't') { if (!PdfEncodings.convertToString(line, null).startsWith("trailer")) continue; tokens.seek(pos); tokens.nextToken(); pos = tokens.getFilePointer(); try { PdfDictionary dic = (PdfDictionary) readPRObject(); if (dic.get(PdfName.ROOT) != null) trailer = dic; else tokens.seek(pos); } catch (Exception e) { tokens.seek(pos); } } else if (line[0] >= '0' && line[0] <= '9') { long obj[] = PRTokeniser.checkObjectStart(line); if (obj == null) continue; long num = obj[0]; long gen = obj[1]; if (num >= xr.length) { long newLength = num * 2; long xr2[][] = new long[(int) newLength][]; System.arraycopy(xr, 0, xr2, 0, (int) top); xr = xr2; } if (num >= top) top = num + 1; if (xr[(int) num] == null || gen >= xr[(int) num][1]) { obj[0] = pos; xr[(int) num] = obj; } } } if (trailer == null) throw new InvalidPdfException(MessageLocalization.getComposedMessage("trailer.not.found")); xref = new long[(int) (top * 2)]; for (int k = 0; k < top; ++k) { long obj[] = xr[k]; if (obj != null) xref[k * 2] = obj[0]; } } protected PdfDictionary readDictionary() throws IOException { PdfDictionary dic = new PdfDictionary(); while (true) { tokens.nextValidToken(); if (tokens.getTokenType() == TokenType.END_DIC) break; if (tokens.getTokenType() != TokenType.NAME) tokens.throwError(MessageLocalization.getComposedMessage("dictionary.key.1.is.not.a.name", tokens.getStringValue())); PdfName name = new PdfName(tokens.getStringValue(), false); PdfObject obj = readPRObject(); int type = obj.type(); if (-type == TokenType.END_DIC.ordinal()) tokens.throwError(MessageLocalization.getComposedMessage("unexpected.gt.gt")); if (-type == TokenType.END_ARRAY.ordinal()) tokens.throwError(MessageLocalization.getComposedMessage("unexpected.close.bracket")); dic.put(name, obj); } return dic; } protected PdfArray readArray() throws IOException { PdfArray array = new PdfArray(); while (true) { PdfObject obj = readPRObject(); int type = obj.type(); if (-type == TokenType.END_ARRAY.ordinal()) break; if (-type == TokenType.END_DIC.ordinal()) tokens.throwError(MessageLocalization.getComposedMessage("unexpected.gt.gt")); array.add(obj); } return array; } // Track how deeply nested the current object is, so // we know when to return an individual null or boolean, or // reuse one of the static ones. private int readDepth = 0; protected PdfObject readPRObject() throws IOException { tokens.nextValidToken(); TokenType type = tokens.getTokenType(); switch (type) { case START_DIC: { ++readDepth; PdfDictionary dic = readDictionary(); --readDepth; long pos = tokens.getFilePointer(); // be careful in the trailer. May not be a "next" token. boolean hasNext; do { hasNext = tokens.nextToken(); } while (hasNext && tokens.getTokenType() == TokenType.COMMENT); if (hasNext && tokens.getStringValue().equals("stream")) { //skip whitespaces int ch; do { ch = tokens.read(); } while (ch == 32 || ch == 9 || ch == 0 || ch == 12); if (ch != '\n') ch = tokens.read(); if (ch != '\n') tokens.backOnePosition(ch); PRStream stream = new PRStream(this, tokens.getFilePointer()); stream.putAll(dic); // crypto handling stream.setObjNum(objNum, objGen); return stream; } else { tokens.seek(pos); return dic; } } case START_ARRAY: { ++readDepth; PdfArray arr = readArray(); --readDepth; return arr; } case NUMBER: return new PdfNumber(tokens.getStringValue()); case STRING: PdfString str = new PdfString(tokens.getStringValue(), null).setHexWriting(tokens.isHexString()); // crypto handling str.setObjNum(objNum, objGen); if (strings != null) strings.add(str); return str; case NAME: { PdfName cachedName = PdfName.staticNames.get(tokens.getStringValue()); if (readDepth > 0 && cachedName != null) { return cachedName; } else { // an indirect name (how odd...), or a non-standard one return new PdfName(tokens.getStringValue(), false); } } case REF: { int num = tokens.getReference(); if (num >= 0) { return new PRIndirectReference(this, num, tokens.getGeneration()); } else { if (LOGGER.isLogging(Level.ERROR)) { LOGGER.error(MessageLocalization.getComposedMessage("invalid.reference.number.skip")); } return PdfNull.PDFNULL; } } case ENDOFFILE: throw new IOException(MessageLocalization.getComposedMessage("unexpected.end.of.file")); default: String sv = tokens.getStringValue(); if ("null".equals(sv)) { if (readDepth == 0) { return new PdfNull(); } //else return PdfNull.PDFNULL; } else if ("true".equals(sv)) { if (readDepth == 0) { return new PdfBoolean(true); } //else return PdfBoolean.PDFTRUE; } else if ("false".equals(sv)) { if (readDepth == 0) { return new PdfBoolean(false); } //else return PdfBoolean.PDFFALSE; } return new PdfLiteral(-type.ordinal(), tokens.getStringValue()); } } /** Decodes a stream that has the FlateDecode filter. * @param in the input data * @return the decoded data */ public static byte[] FlateDecode(final byte in[]) { byte b[] = FlateDecode(in, true); if (b == null) return FlateDecode(in, false); return b; } /** Decodes a stream that has the FlateDecode filter. * @param in the input data * @return the decoded data */ static byte[] FlateDecode(final byte in[], ByteArrayOutputStream out) { byte b[] = FlateDecode(in, true, out); if (b == null) return FlateDecode(in, false, out); return b; } /** * @param in * @param dicPar * @return a byte array */ public static byte[] decodePredictor(final byte in[], final PdfObject dicPar) { if (dicPar == null || !dicPar.isDictionary()) return in; PdfDictionary dic = (PdfDictionary) dicPar; PdfObject obj = getPdfObject(dic.get(PdfName.PREDICTOR)); if (obj == null || !obj.isNumber()) return in; int predictor = ((PdfNumber) obj).intValue(); if (predictor < 10 && predictor != 2) return in; int width = 1; obj = getPdfObject(dic.get(PdfName.COLUMNS)); if (obj != null && obj.isNumber()) width = ((PdfNumber) obj).intValue(); int colors = 1; obj = getPdfObject(dic.get(PdfName.COLORS)); if (obj != null && obj.isNumber()) colors = ((PdfNumber) obj).intValue(); int bpc = 8; obj = getPdfObject(dic.get(PdfName.BITSPERCOMPONENT)); if (obj != null && obj.isNumber()) bpc = ((PdfNumber) obj).intValue(); DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(in)); ByteArrayOutputStream fout = new ByteArrayOutputStream(in.length); int bytesPerPixel = colors * bpc / 8; int bytesPerRow = (colors * width * bpc + 7) / 8; byte[] curr = new byte[bytesPerRow]; byte[] prior = new byte[bytesPerRow]; if (predictor == 2) { if (bpc == 8) { int numRows = in.length / bytesPerRow; for (int row = 0; row < numRows; row++) { int rowStart = row * bytesPerRow; for (int col = 0 + bytesPerPixel; col < bytesPerRow; col++) { in[rowStart + col] = (byte) (in[rowStart + col] + in[rowStart + col - bytesPerPixel]); } } } return in; } // Decode the (sub)image row-by-row while (true) { // Read the filter type byte and a row of data int filter = 0; try { filter = dataStream.read(); if (filter < 0) { return fout.toByteArray(); } dataStream.readFully(curr, 0, bytesPerRow); } catch (Exception e) { return fout.toByteArray(); } switch (filter) { case 0: //PNG_FILTER_NONE break; case 1: //PNG_FILTER_SUB for (int i = bytesPerPixel; i < bytesPerRow; i++) { curr[i] += curr[i - bytesPerPixel]; } break; case 2: //PNG_FILTER_UP for (int i = 0; i < bytesPerRow; i++) { curr[i] += prior[i]; } break; case 3: //PNG_FILTER_AVERAGE for (int i = 0; i < bytesPerPixel; i++) { curr[i] += prior[i] / 2; } for (int i = bytesPerPixel; i < bytesPerRow; i++) { curr[i] += ((curr[i - bytesPerPixel] & 0xff) + (prior[i] & 0xff)) / 2; } break; case 4: //PNG_FILTER_PAETH for (int i = 0; i < bytesPerPixel; i++) { curr[i] += prior[i]; } for (int i = bytesPerPixel; i < bytesPerRow; i++) { int a = curr[i - bytesPerPixel] & 0xff; int b = prior[i] & 0xff; int c = prior[i - bytesPerPixel] & 0xff; int p = a + b - c; int pa = Math.abs(p - a); int pb = Math.abs(p - b); int pc = Math.abs(p - c); int ret; if (pa <= pb && pa <= pc) { ret = a; } else if (pb <= pc) { ret = b; } else { ret = c; } curr[i] += (byte) ret; } break; default: // Error -- unknown filter type throw new RuntimeException(MessageLocalization.getComposedMessage("png.filter.unknown")); } try { fout.write(curr); } catch (IOException ioe) { // Never happens } // Swap curr and prior byte[] tmp = prior; prior = curr; curr = tmp; } } /** A helper to FlateDecode. * @param in the input data * @param strict <CODE>true</CODE> to read a correct stream. <CODE>false</CODE> * to try to read a corrupted stream * @return the decoded data */ public static byte[] FlateDecode(final byte in[], final boolean strict) { return FlateDecode(in, strict, new ByteArrayOutputStream()); } private static byte[] FlateDecode(final byte in[], final boolean strict, ByteArrayOutputStream out) { ByteArrayInputStream stream = new ByteArrayInputStream(in); InflaterInputStream zip = new InflaterInputStream(stream); byte b[] = new byte[strict ? 4092 : 1]; try { int n; while ((n = zip.read(b)) >= 0) { out.write(b, 0, n); } zip.close(); out.close(); return out.toByteArray(); } catch (MemoryLimitsAwareException e) { throw e; } catch (Exception e) { if (strict) return null; return out.toByteArray(); } finally { try { zip.close(); } catch (IOException ex) { } try { out.close(); } catch (IOException ex) { } } } /** Decodes a stream that has the ASCIIHexDecode filter. * @param in the input data * @return the decoded data */ public static byte[] ASCIIHexDecode(final byte in[]) { return ASCIIHexDecode(in, new ByteArrayOutputStream()); } static byte[] ASCIIHexDecode(final byte in[], ByteArrayOutputStream out) { boolean first = true; int n1 = 0; for (int k = 0; k < in.length; ++k) { int ch = in[k] & 0xff; if (ch == '>') break; if (PRTokeniser.isWhitespace(ch)) continue; int n = PRTokeniser.getHex(ch); if (n == -1) throw new RuntimeException( MessageLocalization.getComposedMessage("illegal.character.in.asciihexdecode")); if (first) n1 = n; else out.write((byte) ((n1 << 4) + n)); first = !first; } if (!first) out.write((byte) (n1 << 4)); return out.toByteArray(); } /** Decodes a stream that has the ASCII85Decode filter. * @param in the input data * @return the decoded data */ public static byte[] ASCII85Decode(final byte in[]) { return ASCII85Decode(in, new ByteArrayOutputStream()); } static byte[] ASCII85Decode(final byte in[], ByteArrayOutputStream out) { int state = 0; int chn[] = new int[5]; for (int k = 0; k < in.length; ++k) { int ch = in[k] & 0xff; if (ch == '~') break; if (PRTokeniser.isWhitespace(ch)) continue; if (ch == 'z' && state == 0) { out.write(0); out.write(0); out.write(0); out.write(0); continue; } if (ch < '!' || ch > 'u') throw new RuntimeException( MessageLocalization.getComposedMessage("illegal.character.in.ascii85decode")); chn[state] = ch - '!'; ++state; if (state == 5) { state = 0; int r = 0; for (int j = 0; j < 5; ++j) r = r * 85 + chn[j]; out.write((byte) (r >> 24)); out.write((byte) (r >> 16)); out.write((byte) (r >> 8)); out.write((byte) r); } } int r = 0; // We'll ignore the next two lines for the sake of perpetuating broken PDFs // if (state == 1) // throw new RuntimeException(MessageLocalization.getComposedMessage("illegal.length.in.ascii85decode")); if (state == 2) { r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + 85 * 85 * 85 + 85 * 85 + 85; out.write((byte) (r >> 24)); } else if (state == 3) { r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + chn[2] * 85 * 85 + 85 * 85 + 85; out.write((byte) (r >> 24)); out.write((byte) (r >> 16)); } else if (state == 4) { r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + chn[2] * 85 * 85 + chn[3] * 85 + 85; out.write((byte) (r >> 24)); out.write((byte) (r >> 16)); out.write((byte) (r >> 8)); } return out.toByteArray(); } /** Decodes a stream that has the LZWDecode filter. * @param in the input data * @return the decoded data */ public static byte[] LZWDecode(final byte in[]) { return LZWDecode(in, new ByteArrayOutputStream()); } static byte[] LZWDecode(final byte in[], ByteArrayOutputStream out) { LZWDecoder lzw = new LZWDecoder(); lzw.decode(in, out); return out.toByteArray(); } /** Checks if the document had errors and was rebuilt. * @return true if rebuilt. * */ public boolean isRebuilt() { return this.rebuilt; } /** Gets the dictionary that represents a page. * @param pageNum the page number. 1 is the first * @return the page dictionary */ public PdfDictionary getPageN(final int pageNum) { PdfDictionary dic = pageRefs.getPageN(pageNum); if (dic == null) return null; if (appendable) dic.setIndRef(pageRefs.getPageOrigRef(pageNum)); return dic; } /** * @param pageNum * @return a Dictionary object */ public PdfDictionary getPageNRelease(final int pageNum) { PdfDictionary dic = getPageN(pageNum); pageRefs.releasePage(pageNum); return dic; } /** * @param pageNum */ public void releasePage(final int pageNum) { pageRefs.releasePage(pageNum); } /** * */ public void resetReleasePage() { pageRefs.resetReleasePage(); } /** Gets the page reference to this page. * @param pageNum the page number. 1 is the first * @return the page reference */ public PRIndirectReference getPageOrigRef(final int pageNum) { return pageRefs.getPageOrigRef(pageNum); } /** Gets the contents of the page. * @param pageNum the page number. 1 is the first * @param file the location of the PDF document * @throws IOException on error * @return the content */ public byte[] getPageContent(final int pageNum, final RandomAccessFileOrArray file) throws IOException { PdfDictionary page = getPageNRelease(pageNum); if (page == null) return null; PdfObject contents = getPdfObjectRelease(page.get(PdfName.CONTENTS)); if (contents == null) return new byte[0]; MemoryLimitsAwareHandler handler = memoryLimitsAwareHandler; long usedMemory = null == handler ? -1 : handler.getAllMemoryUsedForDecompression(); if (contents.isStream()) { return getStreamBytes((PRStream) contents, file); } else if (contents.isArray()) { PdfArray array = (PdfArray) contents; MemoryLimitsAwareOutputStream bout = new MemoryLimitsAwareOutputStream(); for (int k = 0; k < array.size(); ++k) { PdfObject item = getPdfObjectRelease(array.getPdfObject(k)); if (item == null || !item.isStream()) continue; byte[] b = getStreamBytes((PRStream) item, file); // usedMemory has changed, that means that some of currently processed pdf streams are suspicious if (null != handler && usedMemory < handler.getAllMemoryUsedForDecompression()) { bout.setMaxStreamSize(handler.getMaxSizeOfSingleDecompressedPdfStream()); } bout.write(b); if (k != array.size() - 1) bout.write('\n'); } return bout.toByteArray(); } else return new byte[0]; } /** Gets the content from the page dictionary. * @param page the page dictionary * @throws IOException on error * @return the content * @since 5.0.6 */ public static byte[] getPageContent(final PdfDictionary page) throws IOException { if (page == null) return null; RandomAccessFileOrArray rf = null; try { PdfObject contents = getPdfObjectRelease(page.get(PdfName.CONTENTS)); if (contents == null) return new byte[0]; if (contents.isStream()) { if (rf == null) { rf = ((PRStream) contents).getReader().getSafeFile(); rf.reOpen(); } return getStreamBytes((PRStream) contents, rf); } else if (contents.isArray()) { PdfArray array = (PdfArray) contents; ByteArrayOutputStream bout = new ByteArrayOutputStream(); for (int k = 0; k < array.size(); ++k) { PdfObject item = getPdfObjectRelease(array.getPdfObject(k)); if (item == null || !item.isStream()) continue; if (rf == null) { rf = ((PRStream) item).getReader().getSafeFile(); rf.reOpen(); } byte[] b = getStreamBytes((PRStream) item, rf); bout.write(b); if (k != array.size() - 1) bout.write('\n'); } return bout.toByteArray(); } else return new byte[0]; } finally { try { if (rf != null) rf.close(); } catch (Exception e) { } } } /** * Retrieve the given page's resource dictionary * @param pageNum 1-based page number from which to retrieve the resource dictionary * @return The page's resources, or 'null' if the page has none. * @since 5.1 */ public PdfDictionary getPageResources(final int pageNum) { return getPageResources(getPageN(pageNum)); } /** * Retrieve the given page's resource dictionary * @param pageDict the given page * @return The page's resources, or 'null' if the page has none. * @since 5.1 */ public PdfDictionary getPageResources(final PdfDictionary pageDict) { return pageDict.getAsDict(PdfName.RESOURCES); } /** Gets the contents of the page. * @param pageNum the page number. 1 is the first * @throws IOException on error * @return the content */ public byte[] getPageContent(final int pageNum) throws IOException { RandomAccessFileOrArray rf = getSafeFile(); try { rf.reOpen(); return getPageContent(pageNum, rf); } finally { try { rf.close(); } catch (Exception e) { } } } protected void killXref(PdfObject obj) { if (obj == null) return; if (obj instanceof PdfIndirectReference && !obj.isIndirect()) return; switch (obj.type()) { case PdfObject.INDIRECT: { int xr = ((PRIndirectReference) obj).getNumber(); obj = xrefObj.get(xr); xrefObj.set(xr, null); freeXref = xr; killXref(obj); break; } case PdfObject.ARRAY: { PdfArray t = (PdfArray) obj; for (int i = 0; i < t.size(); ++i) killXref(t.getPdfObject(i)); break; } case PdfObject.STREAM: case PdfObject.DICTIONARY: { PdfDictionary dic = (PdfDictionary) obj; for (Object element : dic.getKeys()) { killXref(dic.get((PdfName) element)); } break; } } } /** Sets the contents of the page. * @param content the new page content * @param pageNum the page number. 1 is the first */ public void setPageContent(final int pageNum, final byte content[]) { setPageContent(pageNum, content, PdfStream.DEFAULT_COMPRESSION); } /** Sets the contents of the page. * @param content the new page content * @param pageNum the page number. 1 is the first * @param compressionLevel the compressionLevel * @since 2.1.3 (the method already existed without param compressionLevel) */ public void setPageContent(final int pageNum, final byte content[], final int compressionLevel) { setPageContent(pageNum, content, compressionLevel, false); } /** Sets the contents of the page. * @param content the new page content * @param pageNum the page number. 1 is the first * @param compressionLevel the compressionLevel * @param killOldXRefRecursively if true, old contents will be deeply removed from the pdf (i.e. if it was an array, * all its entries will also be removed). Use careful when a content stream may be reused. * If false, old contents will not be removed and will stay in the document if not manually deleted. * @since 5.5.7 (the method already existed without param killOldXRefRecursively) */ public void setPageContent(final int pageNum, final byte content[], final int compressionLevel, final boolean killOldXRefRecursively) { PdfDictionary page = getPageN(pageNum); if (page == null) return; PdfObject contents = page.get(PdfName.CONTENTS); freeXref = -1; if (killOldXRefRecursively) { killXref(contents); } if (freeXref == -1) { xrefObj.add(null); freeXref = xrefObj.size() - 1; } page.put(PdfName.CONTENTS, new PRIndirectReference(this, freeXref)); xrefObj.set(freeXref, new PRStream(this, content, compressionLevel)); } /** * Decode a byte[] applying the filters specified in the provided dictionary using default filter handlers. * @param b the bytes to decode * @param streamDictionary the dictionary that contains filter information * @return the decoded bytes * @throws IOException if there are any problems decoding the bytes * @since 5.0.4 */ public static byte[] decodeBytes(byte[] b, final PdfDictionary streamDictionary) throws IOException { return decodeBytes(b, streamDictionary, FilterHandlers.getDefaultFilterHandlers()); } /** * Decode a byte[] applying the filters specified in the provided dictionary using the provided filter handlers. * @param b the bytes to decode * @param streamDictionary the dictionary that contains filter information * @param filterHandlers the map used to look up a handler for each type of filter * @return the decoded bytes * @throws IOException if there are any problems decoding the bytes * @since 5.0.4 */ public static byte[] decodeBytes(byte[] b, final PdfDictionary streamDictionary, Map<PdfName, FilterHandlers.FilterHandler> filterHandlers) throws IOException { PdfObject filter = getPdfObjectRelease(streamDictionary.get(PdfName.FILTER)); ArrayList<PdfObject> filters = new ArrayList<PdfObject>(); if (filter != null) { if (filter.isName()) filters.add(filter); else if (filter.isArray()) filters = ((PdfArray) filter).getArrayList(); } MemoryLimitsAwareHandler memoryLimitsAwareHandler = null; if (streamDictionary instanceof PRStream && null != ((PRStream) streamDictionary).getReader()) { memoryLimitsAwareHandler = ((PRStream) streamDictionary).getReader().getMemoryLimitsAwareHandler(); } if (null != memoryLimitsAwareHandler) { HashSet<PdfName> filterSet = new HashSet<PdfName>(); int index; for (index = 0; index < filters.size(); index++) { PdfName filterName = (PdfName) filters.get(index); if (!filterSet.add(filterName)) { memoryLimitsAwareHandler.beginDecompressedPdfStreamProcessing(); break; } } if (index == filters.size()) { // The stream isn't suspicious. We shouldn't process it. memoryLimitsAwareHandler = null; } } ArrayList<PdfObject> dp = new ArrayList<PdfObject>(); PdfObject dpo = getPdfObjectRelease(streamDictionary.get(PdfName.DECODEPARMS)); if (dpo == null || !dpo.isDictionary() && !dpo.isArray()) dpo = getPdfObjectRelease(streamDictionary.get(PdfName.DP)); if (dpo != null) { if (dpo.isDictionary()) dp.add(dpo); else if (dpo.isArray()) dp = ((PdfArray) dpo).getArrayList(); } for (int j = 0; j < filters.size(); ++j) { PdfName filterName = (PdfName) filters.get(j); FilterHandlers.FilterHandler filterHandler = filterHandlers.get(filterName); if (filterHandler == null) throw new UnsupportedPdfException( MessageLocalization.getComposedMessage("the.filter.1.is.not.supported", filterName)); PdfDictionary decodeParams; if (j < dp.size()) { PdfObject dpEntry = getPdfObject(dp.get(j)); if (dpEntry instanceof PdfDictionary) { decodeParams = (PdfDictionary) dpEntry; } else if (dpEntry == null || dpEntry instanceof PdfNull || (dpEntry instanceof PdfLiteral && Arrays.equals("null".getBytes(), ((PdfLiteral) dpEntry).getBytes()))) { decodeParams = null; } else { throw new UnsupportedPdfException(MessageLocalization.getComposedMessage( "the.decode.parameter.type.1.is.not.supported", dpEntry.getClass().toString())); } } else { decodeParams = null; } b = filterHandler.decode(b, filterName, decodeParams, streamDictionary); if (null != memoryLimitsAwareHandler) { memoryLimitsAwareHandler.considerBytesOccupiedByDecompressedPdfStream(b.length); } } if (null != memoryLimitsAwareHandler) { memoryLimitsAwareHandler.endDecompressedPdfStreamProcessing(); } return b; } /** Get the content from a stream applying the required filters. * @param stream the stream * @param file the location where the stream is * @throws IOException on error * @return the stream content */ public static byte[] getStreamBytes(final PRStream stream, final RandomAccessFileOrArray file) throws IOException { byte[] b = getStreamBytesRaw(stream, file); return decodeBytes(b, stream); } /** Get the content from a stream applying the required filters. * @param stream the stream * @throws IOException on error * @return the stream content */ public static byte[] getStreamBytes(final PRStream stream) throws IOException { RandomAccessFileOrArray rf = stream.getReader().getSafeFile(); try { rf.reOpen(); return getStreamBytes(stream, rf); } finally { try { rf.close(); } catch (Exception e) { } } } /** Get the content from a stream as it is without applying any filter. * @param stream the stream * @param file the location where the stream is * @throws IOException on error * @return the stream content */ public static byte[] getStreamBytesRaw(final PRStream stream, final RandomAccessFileOrArray file) throws IOException { PdfReader reader = stream.getReader(); byte b[]; if (stream.getOffset() < 0) b = stream.getBytes(); else { b = new byte[stream.getLength()]; file.seek(stream.getOffset()); file.readFully(b); PdfEncryption decrypt = reader.getDecrypt(); if (decrypt != null) { PdfObject filter = getPdfObjectRelease(stream.get(PdfName.FILTER)); ArrayList<PdfObject> filters = new ArrayList<PdfObject>(); if (filter != null) { if (filter.isName()) filters.add(filter); else if (filter.isArray()) filters = ((PdfArray) filter).getArrayList(); } boolean skip = false; for (int k = 0; k < filters.size(); ++k) { PdfObject obj = getPdfObjectRelease(filters.get(k)); if (obj != null && obj.toString().equals("/Crypt")) { skip = true; break; } } if (!skip) { decrypt.setHashKey(stream.getObjNum(), stream.getObjGen()); b = decrypt.decryptByteArray(b); } } } return b; } /** Get the content from a stream as it is without applying any filter. * @param stream the stream * @throws IOException on error * @return the stream content */ public static byte[] getStreamBytesRaw(final PRStream stream) throws IOException { RandomAccessFileOrArray rf = stream.getReader().getSafeFile(); try { rf.reOpen(); return getStreamBytesRaw(stream, rf); } finally { try { rf.close(); } catch (Exception e) { } } } /** Eliminates shared streams if they exist. */ public void eliminateSharedStreams() { if (!sharedStreams) return; sharedStreams = false; if (pageRefs.size() == 1) return; ArrayList<PRIndirectReference> newRefs = new ArrayList<PRIndirectReference>(); ArrayList<PRStream> newStreams = new ArrayList<PRStream>(); IntHashtable visited = new IntHashtable(); for (int k = 1; k <= pageRefs.size(); ++k) { PdfDictionary page = pageRefs.getPageN(k); if (page == null) continue; PdfObject contents = getPdfObject(page.get(PdfName.CONTENTS)); if (contents == null) continue; if (contents.isStream()) { PRIndirectReference ref = (PRIndirectReference) page.get(PdfName.CONTENTS); if (visited.containsKey(ref.getNumber())) { // need to duplicate newRefs.add(ref); newStreams.add(new PRStream((PRStream) contents, null)); } else visited.put(ref.getNumber(), 1); } else if (contents.isArray()) { PdfArray array = (PdfArray) contents; for (int j = 0; j < array.size(); ++j) { PRIndirectReference ref = (PRIndirectReference) array.getPdfObject(j); if (visited.containsKey(ref.getNumber())) { // need to duplicate newRefs.add(ref); newStreams.add(new PRStream((PRStream) getPdfObject(ref), null)); } else visited.put(ref.getNumber(), 1); } } } if (newStreams.isEmpty()) return; for (int k = 0; k < newStreams.size(); ++k) { xrefObj.add(newStreams.get(k)); PRIndirectReference ref = newRefs.get(k); ref.setNumber(xrefObj.size() - 1, 0); } } /** Checks if the document was changed. * @return <CODE>true</CODE> if the document was changed, * <CODE>false</CODE> otherwise */ public boolean isTampered() { return tampered; } /** * Sets the tampered state. A tampered PdfReader cannot be reused in PdfStamper. * @param tampered the tampered state */ public void setTampered(final boolean tampered) { this.tampered = tampered; pageRefs.keepPages(); } /** Gets the XML metadata. * @throws IOException on error * @return the XML metadata */ public byte[] getMetadata() throws IOException { PdfObject obj = getPdfObject(catalog.get(PdfName.METADATA)); if (!(obj instanceof PRStream)) return null; RandomAccessFileOrArray rf = getSafeFile(); byte b[] = null; try { rf.reOpen(); b = getStreamBytes((PRStream) obj, rf); } finally { try { rf.close(); } catch (Exception e) { // empty on purpose } } return b; } /** * Gets the byte address of the last xref table. * @return the byte address of the last xref table */ public long getLastXref() { return lastXref; } /** * Gets the number of xref objects. * @return the number of xref objects */ public int getXrefSize() { return xrefObj.size(); } /** * Gets the byte address of the %%EOF marker. * @return the byte address of the %%EOF marker */ public long getEofPos() { return eofPos; } /** * Gets the PDF version. Only the last version char is returned. For example * version 1.4 is returned as '4'. * @return the PDF version */ public char getPdfVersion() { return pdfVersion; } /** * Returns <CODE>true</CODE> if the PDF is encrypted. * @return <CODE>true</CODE> if the PDF is encrypted */ public boolean isEncrypted() { return encrypted; } /** * Gets the encryption permissions. It can be used directly in * <CODE>PdfWriter.setEncryption()</CODE>. * @return the encryption permissions */ public long getPermissions() { return pValue; } /** * Returns <CODE>true</CODE> if the PDF has a 128 bit key encryption. * @return <CODE>true</CODE> if the PDF has a 128 bit key encryption */ public boolean is128Key() { return rValue == 3; } /** * Gets the trailer dictionary * @return the trailer dictionary */ public PdfDictionary getTrailer() { return trailer; } PdfEncryption getDecrypt() { return decrypt; } static boolean equalsn(final byte a1[], final byte a2[]) { int length = a2.length; for (int k = 0; k < length; ++k) { if (a1[k] != a2[k]) return false; } return true; } static boolean existsName(final PdfDictionary dic, final PdfName key, final PdfName value) { PdfObject type = getPdfObjectRelease(dic.get(key)); if (type == null || !type.isName()) return false; PdfName name = (PdfName) type; return name.equals(value); } static String getFontName(final PdfDictionary dic) { if (dic == null) return null; PdfObject type = getPdfObjectRelease(dic.get(PdfName.BASEFONT)); if (type == null || !type.isName()) return null; return PdfName.decodeName(type.toString()); } static String getSubsetPrefix(final PdfDictionary dic) { if (dic == null) return null; String s = getFontName(dic); if (s == null) return null; if (s.length() < 8 || s.charAt(6) != '+') return null; for (int k = 0; k < 6; ++k) { char c = s.charAt(k); if (c < 'A' || c > 'Z') return null; } return s; } /** Finds all the font subsets and changes the prefixes to some * random values. * @return the number of font subsets altered */ public int shuffleSubsetNames() { int total = 0; for (int k = 1; k < xrefObj.size(); ++k) { PdfObject obj = getPdfObjectRelease(k); if (obj == null || !obj.isDictionary()) continue; PdfDictionary dic = (PdfDictionary) obj; if (!existsName(dic, PdfName.TYPE, PdfName.FONT)) continue; if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE1) || existsName(dic, PdfName.SUBTYPE, PdfName.MMTYPE1) || existsName(dic, PdfName.SUBTYPE, PdfName.TRUETYPE)) { String s = getSubsetPrefix(dic); if (s == null) continue; String ns = BaseFont.createSubsetPrefix() + s.substring(7); PdfName newName = new PdfName(ns); dic.put(PdfName.BASEFONT, newName); setXrefPartialObject(k, dic); ++total; PdfDictionary fd = dic.getAsDict(PdfName.FONTDESCRIPTOR); if (fd == null) continue; fd.put(PdfName.FONTNAME, newName); } else if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE0)) { String s = getSubsetPrefix(dic); PdfArray arr = dic.getAsArray(PdfName.DESCENDANTFONTS); if (arr == null) continue; if (arr.isEmpty()) continue; PdfDictionary desc = arr.getAsDict(0); String sde = getSubsetPrefix(desc); if (sde == null) continue; String ns = BaseFont.createSubsetPrefix(); if (s != null) dic.put(PdfName.BASEFONT, new PdfName(ns + s.substring(7))); setXrefPartialObject(k, dic); PdfName newName = new PdfName(ns + sde.substring(7)); desc.put(PdfName.BASEFONT, newName); ++total; PdfDictionary fd = desc.getAsDict(PdfName.FONTDESCRIPTOR); if (fd == null) continue; fd.put(PdfName.FONTNAME, newName); } } return total; } /** Finds all the fonts not subset but embedded and marks them as subset. * @return the number of fonts altered */ public int createFakeFontSubsets() { int total = 0; for (int k = 1; k < xrefObj.size(); ++k) { PdfObject obj = getPdfObjectRelease(k); if (obj == null || !obj.isDictionary()) continue; PdfDictionary dic = (PdfDictionary) obj; if (!existsName(dic, PdfName.TYPE, PdfName.FONT)) continue; if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE1) || existsName(dic, PdfName.SUBTYPE, PdfName.MMTYPE1) || existsName(dic, PdfName.SUBTYPE, PdfName.TRUETYPE)) { String s = getSubsetPrefix(dic); if (s != null) continue; s = getFontName(dic); if (s == null) continue; String ns = BaseFont.createSubsetPrefix() + s; PdfDictionary fd = (PdfDictionary) getPdfObjectRelease(dic.get(PdfName.FONTDESCRIPTOR)); if (fd == null) continue; if (fd.get(PdfName.FONTFILE) == null && fd.get(PdfName.FONTFILE2) == null && fd.get(PdfName.FONTFILE3) == null) continue; fd = dic.getAsDict(PdfName.FONTDESCRIPTOR); PdfName newName = new PdfName(ns); dic.put(PdfName.BASEFONT, newName); fd.put(PdfName.FONTNAME, newName); setXrefPartialObject(k, dic); ++total; } } return total; } private static PdfArray getNameArray(PdfObject obj) { if (obj == null) return null; obj = getPdfObjectRelease(obj); if (obj == null) return null; if (obj.isArray()) return (PdfArray) obj; else if (obj.isDictionary()) { PdfObject arr2 = getPdfObjectRelease(((PdfDictionary) obj).get(PdfName.D)); if (arr2 != null && arr2.isArray()) return (PdfArray) arr2; } return null; } /** * Gets all the named destinations as an <CODE>HashMap</CODE>. The key is the name * and the value is the destinations array. * @return gets all the named destinations */ public HashMap<Object, PdfObject> getNamedDestination() { return getNamedDestination(false); } /** * Gets all the named destinations as an <CODE>HashMap</CODE>. The key is the name * and the value is the destinations array. * @param keepNames true if you want the keys to be real PdfNames instead of Strings * @return gets all the named destinations * @since 2.1.6 */ public HashMap<Object, PdfObject> getNamedDestination(final boolean keepNames) { HashMap<Object, PdfObject> names = getNamedDestinationFromNames(keepNames); names.putAll(getNamedDestinationFromStrings()); return names; } /** * Gets the named destinations from the /Dests key in the catalog as an <CODE>HashMap</CODE>. The key is the name * and the value is the destinations array. * @return gets the named destinations * @since 5.0.1 (generic type in signature) */ @SuppressWarnings("unchecked") public HashMap<String, PdfObject> getNamedDestinationFromNames() { return new HashMap(getNamedDestinationFromNames(false)); } /** * Gets the named destinations from the /Dests key in the catalog as an <CODE>HashMap</CODE>. The key is the name * and the value is the destinations array. * @param keepNames true if you want the keys to be real PdfNames instead of Strings * @return gets the named destinations * @since 2.1.6 */ public HashMap<Object, PdfObject> getNamedDestinationFromNames(final boolean keepNames) { HashMap<Object, PdfObject> names = new HashMap<Object, PdfObject>(); if (catalog.get(PdfName.DESTS) != null) { PdfDictionary dic = (PdfDictionary) getPdfObjectRelease(catalog.get(PdfName.DESTS)); if (dic == null) return names; Set<PdfName> keys = dic.getKeys(); for (PdfName key : keys) { PdfArray arr = getNameArray(dic.get(key)); if (arr == null) continue; if (keepNames) { names.put(key, arr); } else { String name = PdfName.decodeName(key.toString()); names.put(name, arr); } } } return names; } /** * Gets the named destinations from the /Names key in the catalog as an <CODE>HashMap</CODE>. The key is the name * and the value is the destinations array. * @return gets the named destinations */ public HashMap<String, PdfObject> getNamedDestinationFromStrings() { if (catalog.get(PdfName.NAMES) != null) { PdfDictionary dic = (PdfDictionary) getPdfObjectRelease(catalog.get(PdfName.NAMES)); if (dic != null) { dic = (PdfDictionary) getPdfObjectRelease(dic.get(PdfName.DESTS)); if (dic != null) { HashMap<String, PdfObject> names = PdfNameTree.readTree(dic); for (Iterator<Map.Entry<String, PdfObject>> it = names.entrySet().iterator(); it.hasNext();) { Map.Entry<String, PdfObject> entry = it.next(); PdfArray arr = getNameArray(entry.getValue()); if (arr != null) entry.setValue(arr); else it.remove(); } return names; } } } return new HashMap<String, PdfObject>(); } /** * Removes all the fields from the document. */ public void removeFields() { pageRefs.resetReleasePage(); for (int k = 1; k <= pageRefs.size(); ++k) { PdfDictionary page = pageRefs.getPageN(k); PdfArray annots = page.getAsArray(PdfName.ANNOTS); if (annots == null) { pageRefs.releasePage(k); continue; } for (int j = 0; j < annots.size(); ++j) { PdfObject obj = getPdfObjectRelease(annots.getPdfObject(j)); if (obj == null || !obj.isDictionary()) continue; PdfDictionary annot = (PdfDictionary) obj; if (PdfName.WIDGET.equals(annot.get(PdfName.SUBTYPE))) annots.remove(j--); } if (annots.isEmpty()) page.remove(PdfName.ANNOTS); else pageRefs.releasePage(k); } catalog.remove(PdfName.ACROFORM); pageRefs.resetReleasePage(); } /** * Removes all the annotations and fields from the document. */ public void removeAnnotations() { pageRefs.resetReleasePage(); for (int k = 1; k <= pageRefs.size(); ++k) { PdfDictionary page = pageRefs.getPageN(k); if (page.get(PdfName.ANNOTS) == null) pageRefs.releasePage(k); else page.remove(PdfName.ANNOTS); } catalog.remove(PdfName.ACROFORM); pageRefs.resetReleasePage(); } /** * Retrieves links for a certain page. * @param page the page to inspect * @return a list of links */ public ArrayList<PdfAnnotation.PdfImportedLink> getLinks(final int page) { pageRefs.resetReleasePage(); ArrayList<PdfAnnotation.PdfImportedLink> result = new ArrayList<PdfAnnotation.PdfImportedLink>(); PdfDictionary pageDic = pageRefs.getPageN(page); if (pageDic.get(PdfName.ANNOTS) != null) { PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS); for (int j = 0; j < annots.size(); ++j) { PdfDictionary annot = (PdfDictionary) getPdfObjectRelease(annots.getPdfObject(j)); if (PdfName.LINK.equals(annot.get(PdfName.SUBTYPE))) { result.add(new PdfAnnotation.PdfImportedLink(annot)); } } } pageRefs.releasePage(page); pageRefs.resetReleasePage(); return result; } private void iterateBookmarks(PdfObject outlineRef, final HashMap<Object, PdfObject> names) { while (outlineRef != null) { replaceNamedDestination(outlineRef, names); PdfDictionary outline = (PdfDictionary) getPdfObjectRelease(outlineRef); PdfObject first = outline.get(PdfName.FIRST); if (first != null) { iterateBookmarks(first, names); } outlineRef = outline.get(PdfName.NEXT); } } /** * Replaces remote named links with local destinations that have the same name. * @since 5.0 */ public void makeRemoteNamedDestinationsLocal() { if (remoteToLocalNamedDestinations) return; remoteToLocalNamedDestinations = true; HashMap<Object, PdfObject> names = getNamedDestination(true); if (names.isEmpty()) return; for (int k = 1; k <= pageRefs.size(); ++k) { PdfDictionary page = pageRefs.getPageN(k); PdfObject annotsRef; PdfArray annots = (PdfArray) getPdfObject(annotsRef = page.get(PdfName.ANNOTS)); int annotIdx = lastXrefPartial; releaseLastXrefPartial(); if (annots == null) { pageRefs.releasePage(k); continue; } boolean commitAnnots = false; for (int an = 0; an < annots.size(); ++an) { PdfObject objRef = annots.getPdfObject(an); if (convertNamedDestination(objRef, names) && !objRef.isIndirect()) commitAnnots = true; } if (commitAnnots) setXrefPartialObject(annotIdx, annots); if (!commitAnnots || annotsRef.isIndirect()) pageRefs.releasePage(k); } } /** * Converts a remote named destination GoToR with a local named destination * if there's a corresponding name. * @param obj an annotation that needs to be screened for links to external named destinations. * @param names a map with names of local named destinations * @since iText 5.0 */ private boolean convertNamedDestination(PdfObject obj, final HashMap<Object, PdfObject> names) { obj = getPdfObject(obj); int objIdx = lastXrefPartial; releaseLastXrefPartial(); if (obj != null && obj.isDictionary()) { PdfObject ob2 = getPdfObject(((PdfDictionary) obj).get(PdfName.A)); if (ob2 != null) { int obj2Idx = lastXrefPartial; releaseLastXrefPartial(); PdfDictionary dic = (PdfDictionary) ob2; PdfName type = (PdfName) getPdfObjectRelease(dic.get(PdfName.S)); if (PdfName.GOTOR.equals(type)) { PdfObject ob3 = getPdfObjectRelease(dic.get(PdfName.D)); Object name = null; if (ob3 != null) { if (ob3.isName()) name = ob3; else if (ob3.isString()) name = ob3.toString(); PdfArray dest = (PdfArray) names.get(name); if (dest != null) { dic.remove(PdfName.F); dic.remove(PdfName.NEWWINDOW); dic.put(PdfName.S, PdfName.GOTO); setXrefPartialObject(obj2Idx, ob2); setXrefPartialObject(objIdx, obj); return true; } } } } } return false; } /** Replaces all the local named links with the actual destinations. */ public void consolidateNamedDestinations() { if (consolidateNamedDestinations) return; consolidateNamedDestinations = true; HashMap<Object, PdfObject> names = getNamedDestination(true); if (names.isEmpty()) return; for (int k = 1; k <= pageRefs.size(); ++k) { PdfDictionary page = pageRefs.getPageN(k); PdfObject annotsRef; PdfArray annots = (PdfArray) getPdfObject(annotsRef = page.get(PdfName.ANNOTS)); int annotIdx = lastXrefPartial; releaseLastXrefPartial(); if (annots == null) { pageRefs.releasePage(k); continue; } boolean commitAnnots = false; for (int an = 0; an < annots.size(); ++an) { PdfObject objRef = annots.getPdfObject(an); if (replaceNamedDestination(objRef, names) && !objRef.isIndirect()) commitAnnots = true; } if (commitAnnots) setXrefPartialObject(annotIdx, annots); if (!commitAnnots || annotsRef.isIndirect()) pageRefs.releasePage(k); } PdfDictionary outlines = (PdfDictionary) getPdfObjectRelease(catalog.get(PdfName.OUTLINES)); if (outlines == null) return; iterateBookmarks(outlines.get(PdfName.FIRST), names); } private boolean replaceNamedDestination(PdfObject obj, final HashMap<Object, PdfObject> names) { obj = getPdfObject(obj); int objIdx = lastXrefPartial; releaseLastXrefPartial(); if (obj != null && obj.isDictionary()) { PdfObject ob2 = getPdfObjectRelease(((PdfDictionary) obj).get(PdfName.DEST)); Object name = null; if (ob2 != null) { if (ob2.isName()) name = ob2; else if (ob2.isString()) name = ob2.toString(); PdfArray dest = (PdfArray) names.get(name); if (dest != null) { ((PdfDictionary) obj).put(PdfName.DEST, dest); setXrefPartialObject(objIdx, obj); return true; } } else if ((ob2 = getPdfObject(((PdfDictionary) obj).get(PdfName.A))) != null) { int obj2Idx = lastXrefPartial; releaseLastXrefPartial(); PdfDictionary dic = (PdfDictionary) ob2; PdfName type = (PdfName) getPdfObjectRelease(dic.get(PdfName.S)); if (PdfName.GOTO.equals(type)) { PdfObject ob3 = getPdfObjectRelease(dic.get(PdfName.D)); if (ob3 != null) { if (ob3.isName()) name = ob3; else if (ob3.isString()) name = ob3.toString(); } PdfArray dest = (PdfArray) names.get(name); if (dest != null) { dic.put(PdfName.D, dest); setXrefPartialObject(obj2Idx, ob2); setXrefPartialObject(objIdx, obj); return true; } } } } return false; } protected static PdfDictionary duplicatePdfDictionary(final PdfDictionary original, PdfDictionary copy, final PdfReader newReader) { if (copy == null) copy = new PdfDictionary(original.size()); for (Object element : original.getKeys()) { PdfName key = (PdfName) element; copy.put(key, duplicatePdfObject(original.get(key), newReader)); } return copy; } protected static PdfObject duplicatePdfObject(final PdfObject original, final PdfReader newReader) { if (original == null) return null; switch (original.type()) { case PdfObject.DICTIONARY: { return duplicatePdfDictionary((PdfDictionary) original, null, newReader); } case PdfObject.STREAM: { PRStream org = (PRStream) original; PRStream stream = new PRStream(org, null, newReader); duplicatePdfDictionary(org, stream, newReader); return stream; } case PdfObject.ARRAY: { PdfArray originalArray = (PdfArray) original; PdfArray arr = new PdfArray(originalArray.size()); for (Iterator<PdfObject> it = originalArray.listIterator(); it.hasNext();) { arr.add(duplicatePdfObject(it.next(), newReader)); } return arr; } case PdfObject.INDIRECT: { PRIndirectReference org = (PRIndirectReference) original; return new PRIndirectReference(newReader, org.getNumber(), org.getGeneration()); } default: return original; } } /** * Closes the reader, and any underlying stream or data source used to create the reader */ public void close() { try { tokens.close(); } catch (IOException e) { throw new ExceptionConverter(e); } } @SuppressWarnings("unchecked") protected void removeUnusedNode(PdfObject obj, final boolean hits[]) { Stack<Object> state = new Stack<Object>(); state.push(obj); while (!state.empty()) { Object current = state.pop(); if (current == null) continue; ArrayList<PdfObject> ar = null; PdfDictionary dic = null; PdfName[] keys = null; Object[] objs = null; int idx = 0; if (current instanceof PdfObject) { obj = (PdfObject) current; switch (obj.type()) { case PdfObject.DICTIONARY: case PdfObject.STREAM: dic = (PdfDictionary) obj; keys = new PdfName[dic.size()]; dic.getKeys().toArray(keys); break; case PdfObject.ARRAY: ar = ((PdfArray) obj).getArrayList(); break; case PdfObject.INDIRECT: PRIndirectReference ref = (PRIndirectReference) obj; int num = ref.getNumber(); if (!hits[num]) { hits[num] = true; state.push(getPdfObjectRelease(ref)); } continue; default: continue; } } else { objs = (Object[]) current; if (objs[0] instanceof ArrayList) { ar = (ArrayList<PdfObject>) objs[0]; idx = ((Integer) objs[1]).intValue(); } else { keys = (PdfName[]) objs[0]; dic = (PdfDictionary) objs[1]; idx = ((Integer) objs[2]).intValue(); } } if (ar != null) { for (int k = idx; k < ar.size(); ++k) { PdfObject v = ar.get(k); if (v.isIndirect()) { int num = ((PRIndirectReference) v).getNumber(); if (num >= xrefObj.size() || !partial && xrefObj.get(num) == null) { ar.set(k, PdfNull.PDFNULL); continue; } } if (objs == null) state.push(new Object[] { ar, Integer.valueOf(k + 1) }); else { objs[1] = Integer.valueOf(k + 1); state.push(objs); } state.push(v); break; } } else { for (int k = idx; k < keys.length; ++k) { PdfName key = keys[k]; PdfObject v = dic.get(key); if (v.isIndirect()) { int num = ((PRIndirectReference) v).getNumber(); if (num < 0 || num >= xrefObj.size() || !partial && xrefObj.get(num) == null) { dic.put(key, PdfNull.PDFNULL); continue; } } if (objs == null) state.push(new Object[] { keys, dic, Integer.valueOf(k + 1) }); else { objs[2] = Integer.valueOf(k + 1); state.push(objs); } state.push(v); break; } } } } /** * Removes all the unreachable objects. * @return the number of indirect objects removed */ public int removeUnusedObjects() { boolean hits[] = new boolean[xrefObj.size()]; removeUnusedNode(trailer, hits); int total = 0; if (partial) { for (int k = 1; k < hits.length; ++k) { if (!hits[k]) { xref[k * 2] = -1; xref[k * 2 + 1] = 0; xrefObj.set(k, null); ++total; } } } else { for (int k = 1; k < hits.length; ++k) { if (!hits[k]) { xrefObj.set(k, null); ++total; } } } return total; } /** Gets a read-only version of <CODE>AcroFields</CODE>. * @return a read-only version of <CODE>AcroFields</CODE> */ public AcroFields getAcroFields() { return new AcroFields(this, null); } /** * Gets the global document JavaScript. * @param file the document file * @throws IOException on error * @return the global document JavaScript */ public String getJavaScript(final RandomAccessFileOrArray file) throws IOException { PdfDictionary names = (PdfDictionary) getPdfObjectRelease(catalog.get(PdfName.NAMES)); if (names == null) return null; PdfDictionary js = (PdfDictionary) getPdfObjectRelease(names.get(PdfName.JAVASCRIPT)); if (js == null) return null; HashMap<String, PdfObject> jscript = PdfNameTree.readTree(js); String sortedNames[] = new String[jscript.size()]; sortedNames = jscript.keySet().toArray(sortedNames); Arrays.sort(sortedNames); StringBuffer buf = new StringBuffer(); for (int k = 0; k < sortedNames.length; ++k) { PdfDictionary j = (PdfDictionary) getPdfObjectRelease(jscript.get(sortedNames[k])); if (j == null) continue; PdfObject obj = getPdfObjectRelease(j.get(PdfName.JS)); if (obj != null) { if (obj.isString()) buf.append(((PdfString) obj).toUnicodeString()).append('\n'); else if (obj.isStream()) { byte bytes[] = getStreamBytes((PRStream) obj, file); if (bytes.length >= 2 && bytes[0] == (byte) 254 && bytes[1] == (byte) 255) buf.append(PdfEncodings.convertToString(bytes, PdfObject.TEXT_UNICODE)); else buf.append(PdfEncodings.convertToString(bytes, PdfObject.TEXT_PDFDOCENCODING)); buf.append('\n'); } } } return buf.toString(); } /** * Gets the global document JavaScript. * @throws IOException on error * @return the global document JavaScript */ public String getJavaScript() throws IOException { RandomAccessFileOrArray rf = getSafeFile(); try { rf.reOpen(); return getJavaScript(rf); } finally { try { rf.close(); } catch (Exception e) { } } } /** * Selects the pages to keep in the document. The pages are described as * ranges. The page ordering can be changed but * no page repetitions are allowed. Note that it may be very slow in partial mode. * @param ranges the comma separated ranges as described in {@link SequenceList} */ public void selectPages(final String ranges) { selectPages(SequenceList.expand(ranges, getNumberOfPages())); } /** * Selects the pages to keep in the document. The pages are described as a * <CODE>List</CODE> of <CODE>Integer</CODE>. The page ordering can be changed but * no page repetitions are allowed. Note that it may be very slow in partial mode. * @param pagesToKeep the pages to keep in the document */ public void selectPages(final List<Integer> pagesToKeep) { selectPages(pagesToKeep, true); } /** * Selects the pages to keep in the document. The pages are described as a * <CODE>List</CODE> of <CODE>Integer</CODE>. The page ordering can be changed but * no page repetitions are allowed. Note that it may be very slow in partial mode. * @param pagesToKeep the pages to keep in the document * @param removeUnused indicate if to remove unsed objects. @see removeUnusedObjects */ protected void selectPages(final List<Integer> pagesToKeep, boolean removeUnused) { pageRefs.selectPages(pagesToKeep); if (removeUnused) removeUnusedObjects(); } /** Sets the viewer preferences as the sum of several constants. * @param preferences the viewer preferences * @see PdfViewerPreferences#setViewerPreferences */ public void setViewerPreferences(final int preferences) { this.viewerPreferences.setViewerPreferences(preferences); setViewerPreferences(this.viewerPreferences); } /** Adds a viewer preference * @param key a key for a viewer preference * @param value a value for the viewer preference * @see PdfViewerPreferences#addViewerPreference */ public void addViewerPreference(final PdfName key, final PdfObject value) { this.viewerPreferences.addViewerPreference(key, value); setViewerPreferences(this.viewerPreferences); } public void setViewerPreferences(final PdfViewerPreferencesImp vp) { vp.addToCatalog(catalog); } /** * Returns a bitset representing the PageMode and PageLayout viewer preferences. * Doesn't return any information about the ViewerPreferences dictionary. * @return an int that contains the Viewer Preferences. */ public int getSimpleViewerPreferences() { return PdfViewerPreferencesImp.getViewerPreferences(catalog).getPageLayoutAndMode(); } /** * Getter for property appendable. * @return Value of property appendable. */ public boolean isAppendable() { return this.appendable; } /** * Setter for property appendable. * @param appendable New value of property appendable. */ public void setAppendable(final boolean appendable) { this.appendable = appendable; if (appendable) getPdfObject(trailer.get(PdfName.ROOT)); } /** * Getter for property newXrefType. * @return Value of property newXrefType. */ public boolean isNewXrefType() { return newXrefType; } /** * Getter for property fileLength. * @return Value of property fileLength. */ public long getFileLength() { return fileLength; } /** * Getter for property hybridXref. * @return Value of property hybridXref. */ public boolean isHybridXref() { return hybridXref; } static class PageRefs { private final PdfReader reader; /** ArrayList with the indirect references to every page. Element 0 = page 1; 1 = page 2;... Not used for partial reading. */ private ArrayList<PRIndirectReference> refsn; /** The number of pages, updated only in case of partial reading. */ private int sizep; /** intHashtable that does the same thing as refsn in case of partial reading: major difference: not all the pages are read. */ private IntHashtable refsp; /** Page number of the last page that was read (partial reading only) */ private int lastPageRead = -1; /** stack to which pages dictionaries are pushed to keep track of the current page attributes */ private ArrayList<PdfDictionary> pageInh; private boolean keepPages; /** * Keeps track of all pages nodes to avoid circular references. */ private Set<PdfObject> pagesNodes = new HashSet<PdfObject>(); private PageRefs(final PdfReader reader) throws IOException { this.reader = reader; if (reader.partial) { refsp = new IntHashtable(); PdfNumber npages = (PdfNumber) PdfReader.getPdfObjectRelease(reader.rootPages.get(PdfName.COUNT)); sizep = npages.intValue(); } else { readPages(); } } PageRefs(final PageRefs other, final PdfReader reader) { this.reader = reader; this.sizep = other.sizep; if (other.refsn != null) { refsn = new ArrayList<PRIndirectReference>(other.refsn); for (int k = 0; k < refsn.size(); ++k) { refsn.set(k, (PRIndirectReference) duplicatePdfObject(refsn.get(k), reader)); } } else this.refsp = (IntHashtable) other.refsp.clone(); } int size() { if (refsn != null) return refsn.size(); else return sizep; } void readPages() throws IOException { if (refsn != null) return; refsp = null; refsn = new ArrayList<PRIndirectReference>(); pageInh = new ArrayList<PdfDictionary>(); iteratePages((PRIndirectReference) reader.catalog.get(PdfName.PAGES)); pageInh = null; reader.rootPages.put(PdfName.COUNT, new PdfNumber(refsn.size())); } void reReadPages() throws IOException { refsn = null; readPages(); } /** Gets the dictionary that represents a page. * @param pageNum the page number. 1 is the first * @return the page dictionary */ public PdfDictionary getPageN(final int pageNum) { PRIndirectReference ref = getPageOrigRef(pageNum); return (PdfDictionary) PdfReader.getPdfObject(ref); } /** * @param pageNum * @return a dictionary object */ public PdfDictionary getPageNRelease(final int pageNum) { PdfDictionary page = getPageN(pageNum); releasePage(pageNum); return page; } /** * @param pageNum * @return an indirect reference */ public PRIndirectReference getPageOrigRefRelease(final int pageNum) { PRIndirectReference ref = getPageOrigRef(pageNum); releasePage(pageNum); return ref; } /** * Gets the page reference to this page. * @param pageNum the page number. 1 is the first * @return the page reference */ public PRIndirectReference getPageOrigRef(int pageNum) { try { --pageNum; if (pageNum < 0 || pageNum >= size()) return null; if (refsn != null) return refsn.get(pageNum); else { int n = refsp.get(pageNum); if (n == 0) { PRIndirectReference ref = getSinglePage(pageNum); if (reader.lastXrefPartial == -1) lastPageRead = -1; else lastPageRead = pageNum; reader.lastXrefPartial = -1; refsp.put(pageNum, ref.getNumber()); if (keepPages) lastPageRead = -1; return ref; } else { if (lastPageRead != pageNum) lastPageRead = -1; if (keepPages) lastPageRead = -1; return new PRIndirectReference(reader, n); } } } catch (Exception e) { throw new ExceptionConverter(e); } } void keepPages() { if (refsp == null || keepPages) return; keepPages = true; refsp.clear(); } /** * @param pageNum */ public void releasePage(int pageNum) { if (refsp == null) return; --pageNum; if (pageNum < 0 || pageNum >= size()) return; if (pageNum != lastPageRead) return; lastPageRead = -1; reader.lastXrefPartial = refsp.get(pageNum); reader.releaseLastXrefPartial(); refsp.remove(pageNum); } /** * */ public void resetReleasePage() { if (refsp == null) return; lastPageRead = -1; } void insertPage(int pageNum, final PRIndirectReference ref) { --pageNum; if (refsn != null) { if (pageNum >= refsn.size()) refsn.add(ref); else refsn.add(pageNum, ref); } else { ++sizep; lastPageRead = -1; if (pageNum >= size()) { refsp.put(size(), ref.getNumber()); } else { IntHashtable refs2 = new IntHashtable((refsp.size() + 1) * 2); for (Iterator<IntHashtable.Entry> it = refsp.getEntryIterator(); it.hasNext();) { IntHashtable.Entry entry = it.next(); int p = entry.getKey(); refs2.put(p >= pageNum ? p + 1 : p, entry.getValue()); } refs2.put(pageNum, ref.getNumber()); refsp = refs2; } } } /** * Adds a PdfDictionary to the pageInh stack to keep track of the page attributes. * @param nodePages a Pages dictionary */ private void pushPageAttributes(final PdfDictionary nodePages) { PdfDictionary dic = new PdfDictionary(); if (!pageInh.isEmpty()) { dic.putAll(pageInh.get(pageInh.size() - 1)); } for (int k = 0; k < pageInhCandidates.length; ++k) { PdfObject obj = nodePages.get(pageInhCandidates[k]); if (obj != null) dic.put(pageInhCandidates[k], obj); } pageInh.add(dic); } /** * Removes the last PdfDictionary that was pushed to the pageInh stack. */ private void popPageAttributes() { pageInh.remove(pageInh.size() - 1); } private void iteratePages(final PRIndirectReference rpage) throws IOException { PdfDictionary page = (PdfDictionary) getPdfObject(rpage); if (page == null) return; if (!pagesNodes.add(getPdfObject(rpage))) throw new InvalidPdfException(MessageLocalization.getComposedMessage("illegal.pages.tree")); PdfArray kidsPR = page.getAsArray(PdfName.KIDS); // reference to a leaf if (kidsPR == null) { page.put(PdfName.TYPE, PdfName.PAGE); PdfDictionary dic = pageInh.get(pageInh.size() - 1); PdfName key; for (Object element : dic.getKeys()) { key = (PdfName) element; if (page.get(key) == null) page.put(key, dic.get(key)); } if (page.get(PdfName.MEDIABOX) == null) { PdfArray arr = new PdfArray( new float[] { 0, 0, PageSize.LETTER.getRight(), PageSize.LETTER.getTop() }); page.put(PdfName.MEDIABOX, arr); } refsn.add(rpage); } // reference to a branch else { page.put(PdfName.TYPE, PdfName.PAGES); pushPageAttributes(page); for (int k = 0; k < kidsPR.size(); ++k) { PdfObject obj = kidsPR.getPdfObject(k); if (!obj.isIndirect()) { while (k < kidsPR.size()) kidsPR.remove(k); break; } iteratePages((PRIndirectReference) obj); } popPageAttributes(); } } protected PRIndirectReference getSinglePage(final int n) { PdfDictionary acc = new PdfDictionary(); PdfDictionary top = reader.rootPages; int base = 0; while (true) { for (int k = 0; k < pageInhCandidates.length; ++k) { PdfObject obj = top.get(pageInhCandidates[k]); if (obj != null) acc.put(pageInhCandidates[k], obj); } PdfArray kids = (PdfArray) PdfReader.getPdfObjectRelease(top.get(PdfName.KIDS)); for (Iterator<PdfObject> it = kids.listIterator(); it.hasNext();) { PRIndirectReference ref = (PRIndirectReference) it.next(); PdfDictionary dic = (PdfDictionary) getPdfObject(ref); int last = reader.lastXrefPartial; PdfObject count = getPdfObjectRelease(dic.get(PdfName.COUNT)); reader.lastXrefPartial = last; int acn = 1; if (count != null && count.type() == PdfObject.NUMBER) acn = ((PdfNumber) count).intValue(); if (n < base + acn) { if (count == null) { dic.mergeDifferent(acc); return ref; } reader.releaseLastXrefPartial(); top = dic; break; } reader.releaseLastXrefPartial(); base += acn; } } } private void selectPages(final List<Integer> pagesToKeep) { IntHashtable pg = new IntHashtable(); ArrayList<Integer> finalPages = new ArrayList<Integer>(); int psize = size(); for (Integer pi : pagesToKeep) { int p = pi.intValue(); if (p >= 1 && p <= psize && pg.put(p, 1) == 0) finalPages.add(pi); } if (reader.partial) { for (int k = 1; k <= psize; ++k) { getPageOrigRef(k); resetReleasePage(); } } PRIndirectReference parent = (PRIndirectReference) reader.catalog.get(PdfName.PAGES); PdfDictionary topPages = (PdfDictionary) PdfReader.getPdfObject(parent); ArrayList<PRIndirectReference> newPageRefs = new ArrayList<PRIndirectReference>(finalPages.size()); PdfArray kids = new PdfArray(); for (int k = 0; k < finalPages.size(); ++k) { int p = finalPages.get(k).intValue(); PRIndirectReference pref = getPageOrigRef(p); resetReleasePage(); kids.add(pref); newPageRefs.add(pref); getPageN(p).put(PdfName.PARENT, parent); } AcroFields af = reader.getAcroFields(); boolean removeFields = af.getFields().size() > 0; for (int k = 1; k <= psize; ++k) { if (!pg.containsKey(k)) { if (removeFields) af.removeFieldsFromPage(k); PRIndirectReference pref = getPageOrigRef(k); int nref = pref.getNumber(); reader.xrefObj.set(nref, null); if (reader.partial) { reader.xref[nref * 2] = -1; reader.xref[nref * 2 + 1] = 0; } } } topPages.put(PdfName.COUNT, new PdfNumber(finalPages.size())); topPages.put(PdfName.KIDS, kids); refsp = null; refsn = newPageRefs; } } PdfIndirectReference getCryptoRef() { if (cryptoRef == null) return null; return new PdfIndirectReference(0, cryptoRef.getNumber(), cryptoRef.getGeneration()); } /** * Checks if this PDF has usage rights enabled. * * @return <code>true</code> if usage rights are present; <code>false</code> otherwise */ public boolean hasUsageRights() { PdfDictionary perms = catalog.getAsDict(PdfName.PERMS); if (perms == null) return false; return perms.contains(PdfName.UR) || perms.contains(PdfName.UR3); } /** * Removes any usage rights that this PDF may have. Only Adobe can grant usage rights * and any PDF modification with iText will invalidate them. Invalidated usage rights may * confuse Acrobat and it's advisable to remove them altogether. */ public void removeUsageRights() { PdfDictionary perms = catalog.getAsDict(PdfName.PERMS); if (perms == null) return; perms.remove(PdfName.UR); perms.remove(PdfName.UR3); if (perms.size() == 0) catalog.remove(PdfName.PERMS); } /** * Gets the certification level for this document. The return values can be <code>PdfSignatureAppearance.NOT_CERTIFIED</code>, * <code>PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED</code>, * <code>PdfSignatureAppearance.CERTIFIED_FORM_FILLING</code> and * <code>PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS</code>. * <p> * No signature validation is made, use the methods available for that in <CODE>AcroFields</CODE>. * </p> * @return gets the certification level for this document */ public int getCertificationLevel() { PdfDictionary dic = catalog.getAsDict(PdfName.PERMS); if (dic == null) return PdfSignatureAppearance.NOT_CERTIFIED; dic = dic.getAsDict(PdfName.DOCMDP); if (dic == null) return PdfSignatureAppearance.NOT_CERTIFIED; PdfArray arr = dic.getAsArray(PdfName.REFERENCE); if (arr == null || arr.size() == 0) return PdfSignatureAppearance.NOT_CERTIFIED; dic = arr.getAsDict(0); if (dic == null) return PdfSignatureAppearance.NOT_CERTIFIED; dic = dic.getAsDict(PdfName.TRANSFORMPARAMS); if (dic == null) return PdfSignatureAppearance.NOT_CERTIFIED; PdfNumber p = dic.getAsNumber(PdfName.P); if (p == null) return PdfSignatureAppearance.NOT_CERTIFIED; return p.intValue(); } /** * Checks if the document was opened with the owner password so that the end application * can decide what level of access restrictions to apply. If the document is not encrypted * it will return <CODE>true</CODE>. * @return <CODE>true</CODE> if the document was opened with the owner password or if it's not encrypted, * <CODE>false</CODE> if the document was opened with the user password */ public final boolean isOpenedWithFullPermissions() { return !encrypted || ownerPasswordUsed || unethicalreading; } /** * @return the crypto mode, or -1 of none */ public int getCryptoMode() { if (decrypt == null) return -1; else return decrypt.getCryptoMode(); } /** * @return true if the metadata is encrypted. */ public boolean isMetadataEncrypted() { if (decrypt == null) return false; else return decrypt.isMetadataEncrypted(); } /** * Computes user password if standard encryption handler is used with Standard40, Standard128 or AES128 encryption algorithm. * * @return user password, or null if not a standard encryption handler was used, * if standard encryption handler was used with AES256 encryption algorithm, * or if ownerPasswordUsed wasn't use to open the document. */ public byte[] computeUserPassword() { if (!encrypted || !ownerPasswordUsed) return null; return decrypt.computeUserPassword(password); } }