All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.europa.esig.dss.pdf.pdfbox.PdfBoxDocumentReader Maven / Gradle / Ivy

The newest version!
/**
 * DSS - Digital Signature Services
 * Copyright (C) 2015 European Commission, provided under the CEF programme
 * 
 * This file is part of the "DSS - Digital Signature Services" project.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package eu.europa.esig.dss.pdf.pdfbox;

import eu.europa.esig.dss.enumerations.CertificationPermission;
import eu.europa.esig.dss.model.DSSDocument;
import eu.europa.esig.dss.model.InMemoryDocument;
import eu.europa.esig.dss.pades.PAdESCommonParameters;
import eu.europa.esig.dss.pades.validation.ByteRange;
import eu.europa.esig.dss.pades.validation.PdfSignatureDictionary;
import eu.europa.esig.dss.pades.validation.PdfSignatureField;
import eu.europa.esig.dss.pdf.AnnotationBox;
import eu.europa.esig.dss.pdf.PAdESConstants;
import eu.europa.esig.dss.pdf.PdfAnnotation;
import eu.europa.esig.dss.pdf.PdfArray;
import eu.europa.esig.dss.pdf.PdfDict;
import eu.europa.esig.dss.pdf.PdfDocumentReader;
import eu.europa.esig.dss.pdf.PdfDssDict;
import eu.europa.esig.dss.pdf.PdfSigDictWrapper;
import eu.europa.esig.dss.pdf.SingleDssDict;
import eu.europa.esig.dss.pdf.visible.ImageRotationUtils;
import eu.europa.esig.dss.pdf.visible.ImageUtils;
import eu.europa.esig.dss.spi.DSSUtils;
import eu.europa.esig.dss.utils.Utils;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * The PDFBox implementation of {@code PdfDocumentReader}
 *
 */
public class PdfBoxDocumentReader implements PdfDocumentReader {

	private static final Logger LOG = LoggerFactory.getLogger(PdfBoxDocumentReader.class);

	/** The PDFBox implementation of the document */
	private final PDDocument pdDocument;

	/** The PDF document */
	private DSSDocument dssDocument;

	/** The map of signature dictionaries and corresponding signature fields */
	private Map> signatureDictionaryMap;

	/**
	 * Default constructor of the PDFBox implementation of the Reader
	 * 
	 * @param dssDocument               {@link DSSDocument} to read
	 * @throws IOException              if an exception occurs
	 * @throws eu.europa.esig.dss.pades.exception.InvalidPasswordException if the password is not provided or
	 *                                  invalid for a protected document
	 */
	public PdfBoxDocumentReader(DSSDocument dssDocument)
			throws IOException, eu.europa.esig.dss.pades.exception.InvalidPasswordException {
		this(dssDocument, null);
	}

	/**
	 * The PDFBox implementation of the Reader
	 * 
	 * @param dssDocument        {@link DSSDocument} to read
	 * @param passwordProtection {@link String} a password to open a protected document
	 * @throws IOException       if an exception occurs
	 * @throws eu.europa.esig.dss.pades.exception.InvalidPasswordException if the password is not provided or
	 *                           invalid for a protected document
	 */
	public PdfBoxDocumentReader(DSSDocument dssDocument, String passwordProtection)
			throws IOException, eu.europa.esig.dss.pades.exception.InvalidPasswordException {
		Objects.requireNonNull(dssDocument, "The document must be defined!");
		this.dssDocument = dssDocument;
		try (InputStream is = dssDocument.openStream()) {
			this.pdDocument = PDDocument.load(is, passwordProtection);
		} catch (InvalidPasswordException e) {
			throw new eu.europa.esig.dss.pades.exception.InvalidPasswordException(
					String.format("Encrypted document : %s", e.getMessage()));
		}
	}

	/**
	 * The PDFBox implementation of the Reader
	 * 
	 * @param binaries           a byte array of a PDF to read
	 * @param passwordProtection {@link String} a password to open a protected
	 *                           document
	 * @throws IOException       if an exception occurs
	 * @throws eu.europa.esig.dss.pades.exception.InvalidPasswordException if the password is not provided or
	 *                           invalid for a protected document
	 */
	public PdfBoxDocumentReader(byte[] binaries, String passwordProtection)
			throws IOException, eu.europa.esig.dss.pades.exception.InvalidPasswordException {
		Objects.requireNonNull(binaries, "The document binaries must be defined!");
		this.dssDocument = new InMemoryDocument(binaries);
		try {
			this.pdDocument = PDDocument.load(binaries, passwordProtection);
		} catch (InvalidPasswordException e) {
			throw new eu.europa.esig.dss.pades.exception.InvalidPasswordException(
					String.format("Encrypted document : %s", e.getMessage()));
		}
	}

	/**
	 * The constructor to directly instantiate the {@code PdfBoxDocumentReader}
	 * 
	 * @param pdDocument {@link PDDocument}
	 */
	public PdfBoxDocumentReader(final PDDocument pdDocument) {
		this.pdDocument = pdDocument;
	}

	/**
	 * Returns the current instance of {@code PDDocument}
	 *
	 * @return {@link PDDocument}
	 */
	public PDDocument getPDDocument() {
		return pdDocument;
	}

	@Override
	public PdfDssDict getDSSDictionary() {
		PdfDict catalog = getCatalogDictionary();
		return SingleDssDict.extract(catalog);
	}

	@Override
	public Map> extractSigDictionaries() throws IOException {
		if (signatureDictionaryMap == null) {
			signatureDictionaryMap = new LinkedHashMap<>();
			Map pdfObjectDictMap = new LinkedHashMap<>();

			final List pdSignatureFields = pdDocument.getSignatureFields();
			if (Utils.isCollectionNotEmpty(pdSignatureFields)) {
				LOG.debug("{} signature(s) found", pdSignatureFields.size());

				for (PDSignatureField signatureField : pdSignatureFields) {
					final PdfBoxDict sigFieldDict = new PdfBoxDict(signatureField.getCOSObject(), pdDocument);
					final PdfSignatureField pdfSignatureField = new PdfSignatureField(sigFieldDict);

					COSObject sigDictObject = signatureField.getCOSObject().getCOSObject(COSName.V);
					if (sigDictObject == null || !(sigDictObject.getObject() instanceof COSDictionary)) {
						LOG.warn("Signature field with name '{}' does not contain a signature", pdfSignatureField.getFieldName());
						continue;
					}

					long sigDictNumber = sigDictObject.getObjectNumber();
					PdfSignatureDictionary signature = pdfObjectDictMap.get(sigDictNumber);
					if (signature == null) {
						try {
							PdfDict dictionary = new PdfBoxDict((COSDictionary) sigDictObject.getObject(), pdDocument);
							signature = new PdfSigDictWrapper(dictionary);
						} catch (Exception e) {
							LOG.warn("Unable to create a PdfSignatureDictionary for field with name '{}'",
									pdfSignatureField.getFieldName(), e);
							continue;
						}

						List fields = new ArrayList<>();
						fields.add(pdfSignatureField);
						signatureDictionaryMap.put(signature, fields);
						pdfObjectDictMap.put(sigDictNumber, signature);

					} else {
						List fieldList = signatureDictionaryMap.get(signature);
						fieldList.add(pdfSignatureField);
						LOG.warn("More than one field refers to the same signature dictionary: {}!", fieldList);

					}

				}
			}
		}
		return signatureDictionaryMap;
	}

	@Override
	public boolean isSignatureCoversWholeDocument(PdfSignatureDictionary signatureDictionary) {
		ByteRange byteRange = signatureDictionary.getByteRange();
		try (InputStream is = dssDocument.openStream()) {
			long originalBytesLength = Utils.getInputStreamSize(is);
			// /ByteRange [0 575649 632483 10206]
			long beforeSignatureLength = (long) byteRange.getFirstPartEnd() - byteRange.getFirstPartStart();
			long expectedCMSLength = (long) byteRange.getSecondPartStart() - byteRange.getFirstPartEnd()
					- byteRange.getFirstPartStart();
			long afterSignatureLength = byteRange.getSecondPartEnd();
			long totalCoveredByByteRange = beforeSignatureLength + expectedCMSLength + afterSignatureLength;

			return (originalBytesLength == totalCoveredByByteRange);
		} catch (IOException e) {
			LOG.warn("Cannot determine the original file size for the document. Reason : {}", e.getMessage());
			return false;
		}
	}

	@Override
	public void close() throws IOException {
		pdDocument.close();
	}

	@Override
	public int getNumberOfPages() {
		return pdDocument.getNumberOfPages();
	}

	@Override
	public AnnotationBox getPageBox(int page) {
		PDPage pdPage = getPDPage(page);
		PDRectangle mediaBox = pdPage.getMediaBox();
		return new AnnotationBox(mediaBox.getLowerLeftX(), mediaBox.getLowerLeftY(), mediaBox.getUpperRightX(),
				mediaBox.getUpperRightY());
	}

	@Override
	public int getPageRotation(int page) {
		PDPage pdPage = getPDPage(page);
		return pdPage.getRotation();
	}

	@Override
	public List getPdfAnnotations(int page) throws IOException {
		List annotations = new ArrayList<>();
		List pdAnnotations = getPageAnnotations(page);
		int pageRotation = getPageRotation(page);
		for (PDAnnotation pdAnnotation : pdAnnotations) {
			PdfAnnotation pdfAnnotation = toPdfAnnotation(pdAnnotation, pageRotation);
			if (pdfAnnotation != null) {
				annotations.add(pdfAnnotation);
			}
		}
		return annotations;
	}

	private List getPageAnnotations(int page) throws IOException {
		PDPage pdPage = getPDPage(page);
		return pdPage.getAnnotations();
	}

	/**
	 * Returns a {@code PDPage}
	 * 
	 * @param page number
	 * @return {@link PDPage}
	 */
	public PDPage getPDPage(int page) {
		return pdDocument.getPage(page - ImageUtils.DEFAULT_FIRST_PAGE);
	}

	private PdfAnnotation toPdfAnnotation(PDAnnotation pdAnnotation, int pageRotation) {
		PDRectangle pdRect = pdAnnotation.getRectangle();
		if (pdRect != null) {
			AnnotationBox annotationBox = new AnnotationBox(pdRect.getLowerLeftX(), pdRect.getLowerLeftY(),
					pdRect.getUpperRightX(), pdRect.getUpperRightY());
			if (pdAnnotation.isNoRotate()) {
				annotationBox = ImageRotationUtils.ensureNoRotate(annotationBox, pageRotation);
			}
			PdfAnnotation pdfAnnotation = new PdfAnnotation(annotationBox);
			pdfAnnotation.setName(getSignatureFieldName(pdAnnotation));
			pdfAnnotation.setSigned(isSignedField(pdAnnotation));
			return pdfAnnotation;
		}
		return null;
	}

	private String getSignatureFieldName(PDAnnotation pdAnnotation) {
		return pdAnnotation.getCOSObject().getString(COSName.T);
	}

	private boolean isSignedField(PDAnnotation pdAnnotation) {
		return pdAnnotation.getCOSObject().getDictionaryObject(COSName.V) != null;
	}

	@Override
	public BufferedImage generateImageScreenshot(int page) throws IOException {
		PDFRenderer renderer = new PDFRenderer(pdDocument);
		return renderer.renderImage(page - ImageUtils.DEFAULT_FIRST_PAGE);
	}

	@Override
	public BufferedImage generateImageScreenshotWithoutAnnotations(int page, List annotations)
			throws IOException {
		List pdAnnotations = getPageAnnotations(page);
		int pageRotation = getPageRotation(page);
		pdAnnotations = getMatchingPDAnnotations(pdAnnotations, annotations, pageRotation);
		List hiddenList = changeVisibility(pdAnnotations, true);
		try {
			return generateImageScreenshot(page);
		} finally {
			// restore the original state
			changeVisibility(hiddenList, false);
		}
	}

	private List getMatchingPDAnnotations(List pdAnnotations, List annotationsToExtract, int pageRotation) {
		List result = new ArrayList<>();
		for (PDAnnotation pdAnnotation : pdAnnotations) {
			PdfAnnotation pdfAnnotation = toPdfAnnotation(pdAnnotation, pageRotation);
			if (annotationsToExtract.contains(pdfAnnotation)) {
				result.add(pdAnnotation);
			}
		}
		return result;
	}

	/**
	 * Changes a "Hidden" flag for provided {@code PDAnnotation}s according to the given boolean parameter
	 *
	 * @param pdAnnotations a list of {@link PDAnnotation}s
	 * @param hide if true - hides an annotation, if false - show the annotation
	 * @return a list of changed {@link PDAnnotation}s
	 */
	private List changeVisibility(List pdAnnotations, boolean hide) {
		List modifiedList = new ArrayList<>();
		for (PDAnnotation pdAnnotation : pdAnnotations) {
			if (hide != pdAnnotation.isHidden()) {
				pdAnnotation.setHidden(hide);
				modifiedList.add(pdAnnotation);
			}
		}
		return modifiedList;
	}

	@Override
	public boolean isEncrypted() {
		return pdDocument.isEncrypted();
	}

	@Override
	public boolean isOpenWithOwnerAccess() {
		final AccessPermission accessPermission = getAccessPermission();
		return accessPermission.isOwnerPermission();
	}

	@Override
	public boolean canFillSignatureForm() {
		final AccessPermission accessPermission = getAccessPermission();
		return accessPermission.canModifyAnnotations() || accessPermission.canFillInForm();
	}

	@Override
	public boolean canCreateSignatureField() {
		final AccessPermission accessPermission = getAccessPermission();
		return accessPermission.canModify() && accessPermission.canModifyAnnotations();
	}

	private AccessPermission getAccessPermission() {
		return pdDocument.getCurrentAccessPermission();
	}

	@Override
	public CertificationPermission getCertificationPermission() {
		/*
		 * Origin: https://github.com/ETDA/PDFTimestamping/blob/master/src/main/java/SigUtils.java
		 */
		PDDocumentCatalog catalog = pdDocument.getDocumentCatalog();
		if (catalog != null) {
			COSBase base = catalog.getCOSObject().getDictionaryObject(COSName.PERMS);
			if (base instanceof COSDictionary) {
				COSDictionary permsDict = (COSDictionary) base;
				base = permsDict.getDictionaryObject(COSName.DOCMDP);
				if (base instanceof COSDictionary) {
					COSDictionary signatureDict = (COSDictionary) base;
					base = signatureDict.getDictionaryObject(COSName.REFERENCE);
					if (base instanceof COSArray) {
						COSArray refArray = (COSArray) base;
						for (int i = 0; i < refArray.size(); i++) {
							CertificationPermission certificationPermission = getPermissionFromReference(refArray.getObject(i));
							if (certificationPermission != null) {
								return certificationPermission;
							}
						}
					}
				}
			}
		}
		return null;
	}

	private CertificationPermission getPermissionFromReference(COSBase reference) {
		if (reference instanceof COSDictionary) {
			COSDictionary sigRefDict = (COSDictionary) reference;
			if (COSName.DOCMDP.equals(sigRefDict.getDictionaryObject(COSName.TRANSFORM_METHOD))) {
				COSBase transformParams = sigRefDict.getDictionaryObject(COSName.TRANSFORM_PARAMS);
				if (transformParams instanceof COSDictionary) {
					COSDictionary transformDict = (COSDictionary) transformParams;
					int accessPermissions = transformDict.getInt(COSName.P, 2);
					if (accessPermissions < 1 || accessPermissions > 3) {
						accessPermissions = 2;
					}
					return CertificationPermission.fromCode(accessPermissions);
				}
			}
		}
		return null;
	}

	@Override
	public boolean isUsageRightsSignaturePresent() {
		PDDocumentCatalog catalog = pdDocument.getDocumentCatalog();
		if (catalog != null) {
			COSBase base = catalog.getCOSObject().getDictionaryObject(COSName.PERMS);
			if (base instanceof COSDictionary) {
				COSDictionary permsDict = (COSDictionary) base;
				base = permsDict.getDictionaryObject(PAdESConstants.UR_NAME);
				if (base != null) {
					return true;
				}
				base = permsDict.getDictionaryObject(PAdESConstants.UR3_NAME);
				if (base != null) {
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public PdfDict getCatalogDictionary() {
		return new PdfBoxDict(pdDocument.getDocumentCatalog().getCOSObject(), pdDocument);
	}

	/**
	 * Computes a DocumentId in a deterministic way based on the given {@code parameters} and the document
	 *
	 * @param parameters {@link PAdESCommonParameters}
	 * @return deterministic identifier
	 */
	public long generateDocumentId(PAdESCommonParameters parameters) {
		/*
		 * Computation is according to "14.4 File identifiers"
		 */
		String deterministicId = parameters.getDeterministicId();
		if (dssDocument != null && dssDocument.getName() != null) {
			deterministicId = deterministicId + "-" + dssDocument.getName();
		}

		long documentId = dssDocument != null ? DSSUtils.getFileByteSize(dssDocument) : 0;
		// attach String to long value in a deterministic way
		for (int i = 0; i < deterministicId.length(); i++) {
			documentId += ((long) deterministicId.charAt(i) & 0xFF) << (8 * i);
		}
		return documentId;
	}

	@Override
	public float getPdfHeaderVersion() {
		return pdDocument.getDocument().getVersion();
	}

	@Override
	public float getVersion() {
		return pdDocument.getVersion();
	}

	@Override
	public void setVersion(float version) {
		pdDocument.getDocumentCatalog().setVersion(Float.toString(version));
	}

	@Override
	public PdfDict createPdfDict() {
		return new PdfBoxDict(pdDocument);
	}

	@Override
	public PdfArray createPdfArray() {
		return new PdfBoxArray(pdDocument);
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy