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: PdfReader.java 4096 2009-11-12 15:31:13Z blowagie $
*
* Copyright 2001, 2002 Paulo Soares
*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the License.
*
* The Original Code is 'iText, a free JAVA-PDF library'.
*
* The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
* the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
* All Rights Reserved.
* Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
* are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
*
* Contributor(s): all the names of the contributors are added in the source code
* where applicable.
*
* Alternatively, the contents of this file may be used under the terms of the
* LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
* provisions of LGPL are applicable instead of those above. If you wish to
* allow use of your version of this file only under the terms of the LGPL
* License and not to allow others to use your version of this file under
* the MPL, indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by the LGPL.
* If you do not delete the provisions above, a recipient may use your version
* of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MPL as stated above or under the terms of the GNU
* Library General Public License as published by the Free Software Foundation;
* either version 2 of the License, or any later version.
*
* This library 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 Library general Public License for more
* details.
*
* If you didn't download this code from the following link, you should check if
* you aren't using an obsolete version:
* https://github.com/LibrePDF/OpenPDF
*/
package com.lowagie.text.pdf;
import com.lowagie.bouncycastle.BouncyCastleHelper;
import com.lowagie.text.ExceptionConverter;
import com.lowagie.text.PageSize;
import com.lowagie.text.Rectangle;
import com.lowagie.text.error_messages.MessageLocalization;
import com.lowagie.text.exceptions.BadPasswordException;
import com.lowagie.text.exceptions.InvalidPdfException;
import com.lowagie.text.exceptions.UnsupportedPdfException;
import com.lowagie.text.pdf.interfaces.PdfViewerPreferences;
import com.lowagie.text.pdf.internal.PdfViewerPreferencesImp;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.Key;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.zip.InflaterInputStream;
/**
* Reads a PDF document.
*
* @author Paulo Soares ([email protected])
* @author Kazuya Ujihara
*/
public class PdfReader implements PdfViewerPreferences, Closeable {
static final PdfName[] pageInhCandidates = {PdfName.MEDIABOX,
PdfName.ROTATE, PdfName.RESOURCES, PdfName.CROPBOX};
private static final byte[] endstream = PdfEncodings
.convertToBytes("endstream", null);
private 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 int[] xref;
protected Map objStmMark;
protected IntHashtable objStmToOffset;
protected boolean newXrefType;
private List 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 int lastXref;
protected int 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
private boolean ownerPasswordUsed;
// allow the PDF to be modified even if the owner password was not supplied
// if encrypted (non-encrypted documents may be modified regardless)
private boolean modificationAllowedWithoutOwnerPassword = true;
protected List strings = new ArrayList<>();
protected boolean sharedStreams = true;
protected boolean consolidateNamedDestinations = false;
protected boolean remoteToLocalNamedDestinations = false;
protected int rValue;
protected int pValue;
private int objNum;
private int objGen;
private int 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 PdfReader() {
}
/**
* Reads and parses a PDF document.
*
* @param filename
* the file name of the document
* @throws IOException
* on error
*/
public PdfReader(String filename) throws IOException {
this(filename, 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(String filename, byte[] ownerPassword) throws IOException {
password = ownerPassword;
tokens = new PRTokeniser(filename);
readPdf();
}
/**
* Reads and parses a PDF document.
*
* @param pdfIn
* the byte array with the document
* @throws IOException
* on error
*/
public PdfReader(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(byte[] pdfIn, byte[] ownerPassword) throws IOException {
password = ownerPassword;
tokens = new PRTokeniser(pdfIn);
readPdf();
}
/**
* 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(String filename, Certificate certificate,
Key certificateKey, String certificateKeyProvider) throws IOException {
this.certificate = certificate;
this.certificateKey = certificateKey;
this.certificateKeyProvider = certificateKeyProvider;
tokens = new PRTokeniser(filename);
readPdf();
}
/**
* Reads and parses a PDF document.
*
* @param url
* the URL of the document
* @throws IOException
* on error
*/
public PdfReader(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(URL url, byte[] ownerPassword) throws IOException {
password = ownerPassword;
tokens = new PRTokeniser(new RandomAccessFileOrArray(url));
readPdf();
}
/**
* 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(InputStream is, byte[] ownerPassword) throws IOException {
password = ownerPassword;
tokens = new PRTokeniser(new RandomAccessFileOrArray(is));
readPdf();
}
/**
* 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(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. The pdf is left open but
* may be closed at any time with PdfReader.close(), reopen is
* automatic.
*
* @param raf
* the document location
* @param ownerPassword
* the password or null for no password
* @throws IOException
* on error
*/
public PdfReader(RandomAccessFileOrArray raf, byte[] ownerPassword)
throws IOException {
password = ownerPassword;
partial = true;
tokens = new PRTokeniser(raf);
readPdfPartial();
}
/**
* Creates an independent duplicate.
*
* @param reader
* the PdfReader to duplicate
*/
public PdfReader(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.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;
}
/**
* 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(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(int index) {
return getPageRotation(pageRefs.getPageNRelease(index));
}
static int getPageRotation(PdfDictionary page) {
if(page==null) {
throw new NullPointerException("To get the rotation the page must not be null!");
}
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(int index) {
return getPageSizeWithRotation(pageRefs.getPageNRelease(index));
}
/**
* Gets the rotated page from a page dictionary.
*
* @param page the page dictionary
* @return the rotated page or null when the page does not exists
*/
public Rectangle getPageSizeWithRotation(PdfDictionary page) {
if(page!=null) {
Rectangle rect = getPageSize(page);
int rotation = getPageRotation(page);
while (rotation > 0) {
rect = rect.rotate();
rotation -= 90;
}
return rect;
}
return null;
}
/** Gets the page size, taking rotation into account. This
* is a Rectangle with the value of a an arbitrary box and the /Rotate key.
* @param index the page number. The first page is 1
* @param boxName of the rotated box. Allowed names are: "crop", "trim", "art", "bleed" and "media".
* @return a Rectangle or null if the page does not exist
*/
public Rectangle getPageSizeWithRotation(int index, String boxName) {
Rectangle rect = getBoxSize(index,boxName);
PdfDictionary page = this.pageRefs.getPageNRelease(index);
if(rect==null || page ==null) {
return null;
}
int rotation = getPageRotation(page);
//except for the mediabox all other boxes can be null
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(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(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(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(int index, String boxName) {
PdfDictionary page = pageRefs.getPageNRelease(index);
PdfArray box = null;
switch (boxName) {
case "trim":
box = (PdfArray) getPdfObjectRelease(page.get(PdfName.TRIMBOX));
break;
case "art":
box = (PdfArray) getPdfObjectRelease(page.get(PdfName.ARTBOX));
break;
case "bleed":
box = (PdfArray) getPdfObjectRelease(page.get(PdfName.BLEEDBOX));
break;
case "crop":
box = (PdfArray) getPdfObjectRelease(page.get(PdfName.CROPBOX));
break;
case "media":
box = (PdfArray) getPdfObjectRelease(page.get(PdfName.MEDIABOX));
break;
}
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 Map getInfo() {
Map map = new HashMap<>();
PdfDictionary info = trailer.getAsDict(PdfName.INFO);
if (info == null)
return map;
for (Object o : info.getKeys()) {
PdfName key = (PdfName) o;
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(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));
}
protected void readPdf() throws IOException {
try {
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;
rebuildXref();
lastXref = -1;
readDocObj();
}
strings.clear();
readPages();
//eliminateSharedStreams();
removeUnusedObjects();
} finally {
try {
tokens.close();
} catch (Exception e) {
// empty on purpose
}
}
}
protected void readPdfPartial() throws IOException {
try {
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()));
}
}
readDocObjPartial();
readPages();
} catch (IOException e) {
try {
tokens.close();
} catch (Exception ignored) {
}
throw e;
}
}
private boolean equalsArray(byte[] ar1, byte[] ar2, int size) {
for (int k = 0; k < size; ++k) {
if (ar1[k] != ar2[k])
return false;
}
return true;
}
/**
*/
private void readDecryptedDocObj() throws IOException {
if (encrypted)
return;
if (trailer == null) {
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.lowagie.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.lowagie.text.DocWriter.getISOBytes(s);
s = enc.get(PdfName.O).toString();
strings.remove(enc.get(PdfName.O));
oValue = com.lowagie.text.DocWriter.getISOBytes(s);
o = enc.get(PdfName.P);
if (!o.isNumber())
throw new InvalidPdfException(
MessageLocalization.getComposedMessage("illegal.p.value"));
pValue = ((PdfNumber) o).intValue();
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 6:
cryptoMode = PdfWriter.ENCRYPTION_AES_256_V3;
em = enc.get(PdfName.ENCRYPTMETADATA);
if (em != null && em.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)) {
PdfArray recipients;
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:
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
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", rValue));
}
BouncyCastleHelper.checkCertificateEncodingOrThrowException(certificate);
byte[] envelopedData = BouncyCastleHelper.getEnvelopedData(recipients, strings, certificate, certificateKey, certificateKeyProvider);
if (envelopedData == null) {
throw new UnsupportedPdfException(
MessageLocalization.getComposedMessage("bad.certificate.and.key"));
}
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-1");
md.update(envelopedData, 0, 20);
for (int i = 0; i < recipients.size(); i++) {
byte[] encodedRecipient = recipients.getPdfObject(i).getBytes();
md.update(encodedRecipient);
}
if ((cryptoMode & PdfWriter.DO_NOT_ENCRYPT_METADATA) != 0)
md.update(new byte[] { (byte) 255, (byte) 255, (byte) 255, (byte) 255 });
encryptionKey = md.digest();
} catch (Exception f) {
throw new ExceptionConverter(f);
}
}
decrypt = new PdfEncryption();
decrypt.setCryptoMode(cryptoMode, lengthValue);
if (filter.equals(PdfName.STANDARD)) {
if (rValue < 6) {
// check by owner password
decrypt.setupByOwnerPassword(documentID, password, uValue, oValue, pValue);
if (!equalsArray(uValue, decrypt.userKey,
(rValue == 3 || rValue == 4) ? 16 : 32)) {
// check by user password
decrypt.setupByUserPassword(documentID, password, oValue, pValue);
if (!equalsArray(uValue, decrypt.userKey,
(rValue == 3 || rValue == 4) ? 16 : 32)) {
throw new BadPasswordException(
MessageLocalization.getComposedMessage("bad.user.password"));
}
} else
ownerPasswordUsed = true;
} else {
// implements Algorithm 2.A: Retrieving the file encryption key from an encrypted document in order to decrypt it (revision 6 and later) - ISO 32000-2 section 7.6.4.3.3
s = enc.get(PdfName.UE).toString();
strings.remove(enc.get(PdfName.UE));
byte[] ueValue = com.lowagie.text.DocWriter.getISOBytes(s);
s = enc.get(PdfName.OE).toString();
strings.remove(enc.get(PdfName.OE));
byte[] oeValue = com.lowagie.text.DocWriter.getISOBytes(s);
s = enc.get(PdfName.PERMS).toString();
strings.remove(enc.get(PdfName.PERMS));
byte[] permsValue = com.lowagie.text.DocWriter.getISOBytes(s);
// step b of Algorithm 2.A
byte[] password = this.password;
if (password == null)
password = new byte[0];
else if (password.length > 127)
password = Arrays.copyOf(password, 127);
// According to ISO 32000-2 the uValue is expected to be 48 bytes in length.
// Actual documents from the wild tend to have the uValue filled with zeroes
// to a 127 bytes length. As input to computeHash for owner password related
// operations, though, we must only use the 48 bytes.
if (uValue != null && uValue.length > 48)
uValue = Arrays.copyOf(uValue, 48);
try {
// step c of Algorithm 2.A
byte[] hashAlg2B = decrypt.hashAlg2B(password, Arrays.copyOfRange(oValue, 32, 40), uValue);
if (equalsArray(hashAlg2B, oValue, 32)) {
// step d of Algorithm 2.A
decrypt.setupByOwnerPassword(documentID, password, uValue, ueValue, oValue, oeValue, pValue);
// step f of Algorithm 2.A
if (decrypt.decryptAndCheckPerms(permsValue))
ownerPasswordUsed = true;
}
if (!ownerPasswordUsed) {
// analog of step c of Algorithm 2.A for user password
hashAlg2B = decrypt.hashAlg2B(password, Arrays.copyOfRange(uValue, 32, 40), null);
if (!equalsArray(hashAlg2B, uValue, 32))
throw new BadPasswordException(MessageLocalization.getComposedMessage("bad.user.password"));
// step e of Algorithm 2.A
decrypt.setupByUserPassword(documentID, password, uValue, ueValue, oValue, oeValue, pValue);
// step f of Algorithm 2.A
if (!decrypt.decryptAndCheckPerms(permsValue))
throw new BadPasswordException(MessageLocalization.getComposedMessage("bad.user.password"));
}
pValue = decrypt.permissions;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
}
}
} else if (filter.equals(PdfName.PUBSEC)) {
decrypt.setupByEncryptionKey(encryptionKey, lengthValue);
ownerPasswordUsed = true;
}
for (Object string : strings) {
PdfString str = (PdfString) string;
str.decrypt(this);
}
if (encDic.isIndirect()) {
cryptoRef = (PRIndirectReference) encDic;
xrefObj.set(cryptoRef.getNumber(), null);
}
encryptionError = false;
}
/**
* @param obj an object of {@link PdfObject}
* @return a PdfObject
*/
public static PdfObject getPdfObjectRelease(PdfObject obj) {
PdfObject obj2 = getPdfObject(obj);
releaseLastXrefPartial(obj);
return obj2;
}
/**
* If given object is instance of {@link PdfNull}, then {@code null} is returned. The provided object otherwise.
*
* @param obj object to convert
* @return provided object or null
*/
public static PdfObject convertPdfNull(PdfObject obj) {
if (obj == null || obj instanceof PdfNull) {
return null;
}
return obj;
}
/**
* Returns {@link #getPdfObjectRelease(PdfObject)} with applied {@link #convertPdfNull(PdfObject)}.
*/
public static PdfObject getPdfObjectReleaseNullConverting(PdfObject obj) {
return convertPdfNull(getPdfObjectRelease(obj));
}
/**
* Reads a PdfObject 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 parent object
* @return a PdfObject
*/
public static PdfObject getPdfObjectRelease(PdfObject obj, PdfObject parent) {
PdfObject obj2 = getPdfObject(obj, parent);
releaseLastXrefPartial(obj);
return obj2;
}
/**
* @param obj the PdfObject to read
* @param parent parent object
* @return a PdfObject
*/
public static PdfObject getPdfObject(PdfObject obj, PdfObject parent) {
if (obj == null)
return null;
if (!obj.isIndirect()) {
PRIndirectReference ref;
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);
}
/**
* Returns {@link #getPdfObject(PdfObject, PdfObject)} with applied {@link #convertPdfNull(PdfObject)}.
*/
public static PdfObject getPdfObjectNullConverting(PdfObject obj, PdfObject parent) {
return convertPdfNull(getPdfObject(obj, parent));
}
/**
* @param idx index
* @return a PdfObject
*/
public PdfObject getPdfObjectRelease(int idx) {
PdfObject obj = getPdfObject(idx);
releaseLastXrefPartial();
return obj;
}
/**
* @param idx index
* @return aPdfObject
*/
public PdfObject getPdfObject(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 an object of {@link PdfObject}
*/
public static void releaseLastXrefPartial(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(int idx, PdfObject obj) {
if (!partial || idx < 0)
return;
xrefObj.set(idx, obj);
}
/**
* @param obj an object of {@link PdfObject}
* @return an indirect reference
*/
public PRIndirectReference addPdfObject(PdfObject obj) {
xrefObj.add(obj);
return new PRIndirectReference(this, xrefObj.size() - 1);
}
protected void readPages() throws IOException {
catalog = trailer.getAsDict(PdfName.ROOT);
rootPages = catalog.getAsDict(PdfName.PAGES);
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) {
int[] keys = objStmToOffset.getKeys();
for (int n : keys) {
objStmToOffset.put(n, xref[n * 2]);
xref[n * 2] = -1;
}
}
}
protected PdfObject readSingleObject(int k) throws IOException {
strings.clear();
int k2 = k * 2;
int 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() != PRTokeniser.TK_NUMBER)
tokens.throwError(MessageLocalization
.getComposedMessage("invalid.object.number"));
objNum = tokens.intValue();
tokens.nextValidToken();
if (tokens.getTokenType() != PRTokeniser.TK_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 (PdfObject string : strings) {
PdfString str = (PdfString) string;
str.decrypt(this);
}
if (obj.isStream()) {
checkPRStreamLength((PRStream) obj);
}
} catch (Exception e) {
obj = null;
}
if (xref[k2 + 1] > 0) {
obj = readOneObjStm((PRStream) obj, xref[k2]);
}
xrefObj.set(k, obj);
return obj;
}
protected PdfObject readOneObjStm(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(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() != PRTokeniser.TK_NUMBER) {
ok = false;
break;
}
ok = tokens.nextToken();
if (!ok)
break;
if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
ok = false;
break;
}
address = tokens.intValue() + first;
}
if (!ok)
throw new InvalidPdfException(
MessageLocalization.getComposedMessage("error.reading.objstm"));
tokens.seek(address);
return readPRObject();
} finally {
tokens = saveTokens;
}
}
/**
* @return the percentage of the cross reference table that has been read
*/
public double dumpPerc() {
int total = 0;
for (PdfObject aXrefObj : xrefObj) {
if (aXrefObj != null)
++total;
}
return (total * 100.0 / xrefObj.size());
}
protected void readDocObj() throws IOException {
List 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) {
int pos = xref[k];
if (pos <= 0 || ((xref.length > k + 1) && (xref[k + 1] > 0))) {
continue;
}
tokens.seek(pos);
tokens.nextValidToken();
if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
tokens.throwError(MessageLocalization
.getComposedMessage("invalid.object.number"));
objNum = tokens.intValue();
tokens.nextValidToken();
if (tokens.getTokenType() != PRTokeniser.TK_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(obj);
}
} catch (Exception e) {
obj = null;
}
xrefObj.set(k / 2, obj);
}
for (PdfObject stream : streams) {
checkPRStreamLength((PRStream) stream);
}
readDecryptedDocObj();
if (objStmMark != null) {
for (Object o : objStmMark.entrySet()) {
Map.Entry entry = (Map.Entry) o;
int n = (Integer) entry.getKey();
IntHashtable h = (IntHashtable) entry.getValue();
readObjStm((PRStream) xrefObj.get(n), h);
xrefObj.set(n, null);
}
objStmMark = null;
}
xref = null;
}
private void checkPRStreamLength(PRStream stream) throws IOException {
int fileLength = tokens.length();
int start = stream.getOffset();
boolean calc = false;
int 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);
while (true) {
int pos = tokens.getFilePointer();
if (!tokens.readLineSegment(tline))
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;
}
}
}
stream.setLength(streamLength);
}
protected void readObjStm(PRStream stream, IntHashtable map)
throws IOException {
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(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() != PRTokeniser.TK_NUMBER) {
ok = false;
break;
}
objNumber[k] = tokens.intValue();
ok = tokens.nextToken();
if (!ok)
break;
if (tokens.getTokenType() != PRTokeniser.TK_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]);
PdfObject 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(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(int size) {
if (size == 0)
return;
if (xref == null)
xref = new int[size];
else {
if (xref.length < size) {
int[] xref2 = new int[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() != PRTokeniser.TK_NUMBER)
throw new InvalidPdfException(
MessageLocalization
.getComposedMessage("startxref.is.not.followed.by.a.number"));
int startxref = tokens.intValue();
lastXref = startxref;
eofPos = tokens.getFilePointer();
try {
if (readXRefStream(startxref)) {
newXrefType = true;
return;
}
} catch (Exception ignored) {
}
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.intValue() == startxref)
throw new InvalidPdfException(
MessageLocalization
.getComposedMessage("xref.infinite.loop"));
tokens.seek(prev.intValue());
trailer2 = readXrefSection();
}
}
protected PdfDictionary readXrefSection() throws IOException {
tokens.nextValidToken();
if (!tokens.getStringValue().equals("xref"))
tokens.throwError(MessageLocalization
.getComposedMessage("xref.subsection.not.found"));
int start;
int end;
int pos;
int gen;
while (true) {
tokens.nextValidToken();
if (tokens.getStringValue().equals("trailer"))
break;
if (tokens.getTokenType() != PRTokeniser.TK_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() != PRTokeniser.TK_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
int back = tokens.getFilePointer();
tokens.nextValidToken();
pos = tokens.intValue();
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.intValue();
tokens.nextValidToken();
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(int ptr) throws IOException {
tokens.seek(ptr);
int thisStream;
if (!tokens.nextToken())
return false;
if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
return false;
thisStream = tokens.intValue();
if (!tokens.nextToken() || tokens.getTokenType() != PRTokeniser.TK_NUMBER)
return false;
if (!tokens.nextToken() || !tokens.getStringValue().equals("obj"))
return false;
PdfObject object = readPRObject();
PRStream stm;
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);
int prev = -1;
obj = stm.get(PdfName.PREV);
if (obj != null)
prev = ((PdfNumber) obj).intValue();
// 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 IntHashtable();
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);
}
int 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 = 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 + 1] == 0 && xref[thisStream] == 0)
xref[thisStream] = -1;
if (prev == -1)
return true;
return readXRefStream(prev);
}
protected void rebuildXref() throws IOException {
hybridXref = false;
newXrefType = false;
tokens.seek(0);
int[][] xr = new int[1024][];
int top = 0;
trailer = null;
byte[] line = new byte[64];
for (;;) {
int pos = tokens.getFilePointer();
if (!tokens.readLineSegment(line))
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') {
int[] obj = PRTokeniser.checkObjectStart(line);
if (obj == null)
continue;
int num = obj[0];
int gen = obj[1];
if (num >= xr.length) {
int newLength = num * 2;
int[][] xr2 = new int[newLength][];
System.arraycopy(xr, 0, xr2, 0, top);
xr = xr2;
}
if (num >= top)
top = num + 1;
if (xr[num] == null || gen >= xr[num][1]) {
obj[0] = pos;
xr[num] = obj;
}
}
}
if (trailer == null)
throw new InvalidPdfException(MessageLocalization.getComposedMessage("trailer.not.found"));
xref = new int[top * 2];
for (int k = 0; k < top; ++k) {
int[] 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() == PRTokeniser.TK_END_DIC)
break;
if (tokens.getTokenType() != PRTokeniser.TK_NAME)
tokens.throwError(MessageLocalization
.getComposedMessage("dictionary.key.is.not.a.name"));
PdfName name = new PdfName(tokens.getStringValue(), false);
PdfObject obj = readPRObject();
int type = obj.type();
if (-type == PRTokeniser.TK_END_DIC)
tokens.throwError(MessageLocalization
.getComposedMessage("unexpected.gt.gt"));
if (-type == PRTokeniser.TK_END_ARRAY)
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 == PRTokeniser.TK_END_ARRAY)
break;
if (-type == PRTokeniser.TK_END_DIC)
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();
int type = tokens.getTokenType();
switch (type) {
case PRTokeniser.TK_START_DIC: {
++readDepth;
PdfDictionary dic = readDictionary();
--readDepth;
int pos = tokens.getFilePointer();
// be careful in the trailer. May not be a "next" token.
boolean hasNext;
do {
hasNext = tokens.nextToken();
} while (hasNext && tokens.getTokenType() == PRTokeniser.TK_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 PRTokeniser.TK_START_ARRAY: {
++readDepth;
PdfArray arr = readArray();
--readDepth;
return arr;
}
case PRTokeniser.TK_NUMBER:
return new PdfNumber(tokens.getStringValue());
case PRTokeniser.TK_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 PRTokeniser.TK_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 PRTokeniser.TK_REF:
int num = tokens.getReference();
PRIndirectReference ref = new PRIndirectReference(this, num,
tokens.getGeneration());
return ref;
case PRTokeniser.TK_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, tokens.getStringValue());
}
}
/**
* Decodes a stream that has the FlateDecode filter.
*
* @param in
* the input data
* @return the decoded data
*/
public static byte[] FlateDecode(byte[] in) {
byte[] b = FlateDecode(in, true);
if (b == null)
return FlateDecode(in, false);
return b;
}
/**
* @param in the input data
* @param dicPar an object of {@link PdfObject}
* @return a byte array
*/
public static byte[] decodePredictor(byte[] in, 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)
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];
// Decode the (sub)image row-by-row
while (true) {
// Read the filter type byte and a row of data
int filter;
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] / (byte) 2;
}
for (int i = bytesPerPixel; i < bytesPerRow; i++) {
curr[i] += ((curr[i - bytesPerPixel] & 0xff) + (prior[i] & 0xff)) / (byte) 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(byte[] in, 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(byte[] in) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
boolean first = true;
int n1 = 0;
for (byte b : in) {
int ch = b & 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(byte[] in) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
int state = 0;
int[] chn = new int[5];
for (byte b : in) {
int ch = b & 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;
// 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(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 or null when the page does not exist
*/
public PdfDictionary getPageN(int pageNum) {
PdfDictionary dic = pageRefs.getPageN(pageNum);
if (dic == null)
return null;
if (appendable)
dic.setIndRef(pageRefs.getPageOrigRef(pageNum));
return dic;
}
/**
* @param pageNum page number
* @return a Dictionary object
*/
public PdfDictionary getPageNRelease(int pageNum) {
PdfDictionary dic = getPageN(pageNum);
pageRefs.releasePage(pageNum);
return dic;
}
/**
* @param pageNum page number
*/
public void releasePage(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(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(int pageNum, 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;
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 contents of the page.
*
* @param pageNum
* the page number. 1 is the first
* @throws IOException
* on error
* @return the content
*/
public byte[] getPageContent(int pageNum) throws IOException {
RandomAccessFileOrArray rf = getSafeFile();
try {
rf.reOpen();
return getPageContent(pageNum, rf);
} finally {
try {
rf.close();
} catch (Exception ignored) {
}
}
}
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 o : dic.getKeys()) {
killXref(dic.get((PdfName) o));
}
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(int pageNum, 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 compression level
* @since 2.1.3 (the method already existed without param compressionLevel)
*/
public void setPageContent(int pageNum, byte[] content, 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));
}
/**
* 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(PRStream stream,
RandomAccessFileOrArray file) throws IOException {
PdfObject filter = getPdfObjectRelease(stream.get(PdfName.FILTER));
byte[] b = getStreamBytesRaw(stream, file);
List filters = new ArrayList<>();
filters = addFilters(filters, filter);
List dp = new ArrayList<>();
PdfObject dpo = getPdfObjectRelease(stream.get(PdfName.DECODEPARMS));
if (dpo == null || (!dpo.isDictionary() && !dpo.isArray()))
dpo = getPdfObjectRelease(stream.get(PdfName.DP));
if (dpo != null) {
if (dpo.isDictionary())
dp.add(dpo);
else if (dpo.isArray())
dp = ((PdfArray) dpo).getElements();
}
String name;
for (int j = 0; j < filters.size(); ++j) {
name = getPdfObjectRelease(filters.get(j))
.toString();
switch (name) {
case "/FlateDecode":
case "/Fl": {
b = FlateDecode(b);
PdfObject dicParam;
if (j < dp.size()) {
dicParam = dp.get(j);
b = decodePredictor(b, dicParam);
}
break;
}
case "/ASCIIHexDecode":
case "/AHx":
b = ASCIIHexDecode(b);
break;
case "/ASCII85Decode":
case "/A85":
b = ASCII85Decode(b);
break;
case "/LZWDecode": {
b = LZWDecode(b);
PdfObject dicParam;
if (j < dp.size()) {
dicParam = dp.get(j);
b = decodePredictor(b, dicParam);
}
break;
}
case "/Crypt":
break;
default:
throw new UnsupportedPdfException(
MessageLocalization.getComposedMessage(
"the.filter.1.is.not.supported", name));
}
}
return b;
}
/**
* 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(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(PRStream stream,
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));
List filters = new ArrayList<>();
filters = addFilters(filters, filter);
boolean skip = false;
for (PdfObject filter1 : filters) {
PdfObject obj = getPdfObjectRelease(filter1);
if (obj != null && obj.toString().equals("/Crypt")) {
skip = true;
break;
}
}
if (!skip) {
decrypt.setHashKey(stream.getObjNum(), stream.getObjGen());
b = decrypt.decryptByteArray(b);
}
}
}
return b;
}
private static List addFilters(List filters, PdfObject filter) {
if (filter != null) {
if (filter.isName())
filters.add(filter);
else if (filter.isArray())
filters = ((PdfArray) filter).getElements();
}
return filters;
}
/**
* 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(PRStream stream) throws IOException {
RandomAccessFileOrArray rf = stream.getReader().getSafeFile();
try {
rf.reOpen();
return getStreamBytesRaw(stream, rf);
} finally {
try {
rf.close();
} catch (Exception ignored) {
}
}
}
/** Eliminates shared streams if they exist. */
public void eliminateSharedStreams() {
if (!sharedStreams)
return;
sharedStreams = false;
if (pageRefs.size() == 1)
return;
List newRefs = new ArrayList<>();
List 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 = (PRIndirectReference) 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(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;
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 int 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 int 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;
}
/**
* Returns true if the owner password has been used to open the document.
*
* @return true if the owner password has been used to open the document.
*/
public boolean isOwnerPasswordUsed() {
return ownerPasswordUsed;
}
/**
* Gets the encryption permissions. It can be used directly in
* PdfWriter.setEncryption().
*
* @return the encryption permissions
*/
public int getPermissions() {
return pValue;
}
public void setPermissions(int permissionValue) {
this.pValue=permissionValue;
}
/**
* 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;
}
private static boolean equalsn(byte[] a1, byte[] a2) {
int length = a2.length;
for (int k = 0; k < length; ++k) {
if (a1[k] != a2[k])
return false;
}
return true;
}
private static boolean existsName(PdfDictionary dic, PdfName key, 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 getFontNameFromDescriptor(PdfDictionary dic) {
return getFontName(dic, PdfName.FONTNAME);
}
private static String getFontName(PdfDictionary dic) {
return getFontName(dic, PdfName.BASEFONT);
}
private static String getFontName(PdfDictionary dic, PdfName property) {
if (dic == null)
return null;
PdfObject type = getPdfObjectRelease(dic.get(property));
if (type == null || !type.isName())
return null;
return PdfName.decodeName(type.toString());
}
static boolean isFontSubset(String fontName) {
return fontName != null && fontName.length() >= 8
&& fontName.charAt(6) == '+';
}
private static String getSubsetPrefix(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 = createRandomSubsetPrefix() + 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 = createRandomSubsetPrefix();
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;
}
/**
* Creates a unique subset prefix to be added to the font name when the font
* is embedded and subset.
*
* @return the subset prefix
*/
private String createRandomSubsetPrefix() {
String s = "";
for (int k = 0; k < 6; ++k) {
s += (char) (Math.random() * 26 + 'A');
}
return s + "+";
}
/**
* 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 = createRandomSubsetPrefix() + 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