Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* $Id: 0315f17ade6762f85dfd4bdc4aa477b99b070d67 $
*
* This file is part of the iText (R) project.
* Copyright (c) 1998-2016 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.spongycastle.cert.X509CertificateHolder;
import org.spongycastle.cms.CMSEnvelopedData;
import org.spongycastle.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 objStmMark;
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 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),
false,
null,
certificate,
null,
null,
externalDecryptionProcess,
true
);
}
/**
* 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),
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 the InputStream 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 or null for no password
* @throws IOException on error
*/
public PdfReader(final RandomAccessFileOrArray raf, final byte ownerPassword[]) throws IOException {
this(raf, ownerPassword, true);
}
/**
* Reads and parses a pdf document.
* @param raf the document location
* @param ownerPassword the password or null 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(),
partial,
ownerPassword,
null,
null,
null,
null,
false
);
}
/** Creates an independent duplicate.
* @param reader the PdfReader 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 a Rectangle
*/
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 HashMap
* of String.
* @return content of the document information dictionary
*/
public HashMap getInfo() {
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 normalized Rectangle
*/
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());
Iterator 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; iPdfObject resolving an indirect reference
* if needed.
* @param obj the PdfObject to read
* @return the resolved PdfObject
*/
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 PdfObject 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 PdfObject 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))) {
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(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.Entryentry: 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();
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) {
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 filterHandlers) 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 ||
(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);
}
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;
}
/**
* Returns true if the PDF is encrypted.
* @return true 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;
}
/**
* Returns true if the PDF has a 128 bit key encryption.
* @return true 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 HashMap. The key is the name
* and the value is the destinations array.
* @return gets all the named destinations
*/
public HashMap