com.itextpdf.text.pdf.PdfReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of itextpdf Show documentation
Show all versions of itextpdf Show documentation
iText, a free Java-PDF library
/* * $Id: PdfReader.java 6551 2014-09-10 09:47:44Z rafhens $ * * This file is part of the iText (R) project. * Copyright (c) 1998-2014 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: [email protected] */ 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.*; 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.*; import java.net.URL; import java.security.Key; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.*; 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
resolving an indirect reference * if needed. * @param obj theobjStmMark; protected LongHashtable objStmToOffset; protected boolean newXrefType; protected ArrayList 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 strings = new ArrayList (); 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; /** * 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.certificate = certificate; this.certificateKey = certificateKey; this.certificateKeyProvider = certificateKeyProvider; this.externalDecryptionProcess = externalDecryptionProcess; this.password = ownerPassword; this.partial = partialRead; try{ tokens = getOffsetTokeniser(byteSource); if (partialRead){ readPdfPartial(); } else { readPdf(); } } catch (IOException e){ if (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 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(filename, ownerPassword, false); } /** * 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), partial, ownerPassword, null, null, null, null, true ); } /** * 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(pdfIn, null); } /** * 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), false, ownerPassword, null, null, null, null, true ); } /** * 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), false, null, certificate, certificateKey, certificateKeyProvider, null, true ); } /** * 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), false, null, certificate, null, null, externalDecryptionProcess, true ); } /** * 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(url, null); } /** * 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), false, ownerPassword, null, null, null, null, true ); } /** * Reads and parses a PDF document. * @param is the InputStream
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), false, ownerPassword, null, null, null, null, false ); } /** * Reads and parses a PDF document. * @param is theInputStream
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(is, null); } /** * 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 ornull
for no password * @throws IOException on error */ public PdfReader(final RandomAccessFileOrArray raf, final byte ownerPassword[]) throws IOException { this( raf.getByteSource(), true, ownerPassword, null, null, null, null, false ); } /** Creates an independent duplicate. * @param reader thePdfReader
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(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. * @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; } /** * 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 Rectangle
with the value of the /MediaBox and the /Rotate key. * @param index the page number. The first page is 1 * @return aRectangle
*/ 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 aHashMap
* ofString
. * @return content of the document information dictionary */ public HashMapgetInfo() { HashMap map = new HashMap (); 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 Rectangle
so that llx and lly are smaller than urx and ury. * @param box the original rectangle * @return a normalizedRectangle
*/ 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(); 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(); } protected void readPdfPartial() throws IOException { fileLength = tokens.getFile().length(); pdfVersion = tokens.checkPdfHeader(); 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); 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()); IteratorrecipientCertificatesIt = 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 PdfObject PdfObject
to read * @return the resolvedPdfObject
*/ 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 aPdfObject
resolving an indirect reference * if needed. If the reader was opened in partial mode the object will be released * to save memory. * @param obj thePdfObject
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) throw new InvalidPdfException(MessageLocalization.getComposedMessage("the.document.has.no.page.root")); pageRefs = new PageRefs(this); } protected void readDocObjPartial() throws IOException { xrefObj = new ArrayList(xref.length / 2); xrefObj.addAll(Collections. 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 streams = new ArrayList (); xrefObj = new ArrayList (xref.length / 2); xrefObj.addAll(Collections. 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 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--; } 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; tokens.seek(prev.longValue()); 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 (); 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(); PRIndirectReference ref = new PRIndirectReference(this, num, tokens.getGeneration()); return ref; 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; } /** * @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 true
to read a correct stream.false
* to try to read a corrupted stream * @return the decoded data */ public static byte[] FlateDecode(final byte in[], final boolean strict) { ByteArrayInputStream stream = new ByteArrayInputStream(in); InflaterInputStream zip = new InflaterInputStream(stream); ByteArrayOutputStream out = new ByteArrayOutputStream(); 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 (Exception e) { if (strict) return null; return out.toByteArray(); } } /** Decodes a stream that has the ASCIIHexDecode filter. * @param in the input data * @return the decoded data */ public static byte[] ASCIIHexDecode(final byte in[]) { ByteArrayOutputStream out = new ByteArrayOutputStream(); 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[]) { ByteArrayOutputStream out = new ByteArrayOutputStream(); 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[]) { ByteArrayOutputStream out = new ByteArrayOutputStream(); 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]; ByteArrayOutputStream bout = null; if (contents.isStream()) { return getStreamBytes((PRStream)contents, file); } else if (contents.isArray()) { PdfArray array = (PdfArray)contents; bout = new ByteArrayOutputStream(); 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); 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) { PdfDictionary page = getPageN(pageNum); if (page == null) return; PdfObject contents = page.get(PdfName.CONTENTS); freeXref = -1; 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, MapfilterHandlers) throws IOException { PdfObject filter = getPdfObjectRelease(streamDictionary.get(PdfName.FILTER)); ArrayList filters = new ArrayList (); if (filter != null) { if (filter.isName()) filters.add(filter); else if (filter.isArray()) filters = ((PdfArray)filter).getArrayList(); } ArrayList dp = new ArrayList (); 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) { 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); } 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 filters = new ArrayList (); 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 newRefs = new ArrayList (); ArrayList newStreams = new ArrayList (); 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 true
if the document was changed, *false
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; } /** * Returnstrue
if the PDF is encrypted. * @returntrue
if the PDF is encrypted */ public boolean isEncrypted() { return encrypted; } /** * Gets the encryption permissions. It can be used directly in *PdfWriter.setEncryption()
. * @return the encryption permissions */ public long getPermissions() { return pValue; } /** * Returnstrue
if the PDF has a 128 bit key encryption. * @returntrue
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 anHashMap
. The key is the name * and the value is the destinations array. * @return gets all the named destinations */ public HashMap
© 2015 - 2024 Weber Informatics LLC | Privacy Policy