diff --git a/io/src/main/java/com/itextpdf/io/exceptions/IoExceptionMessage.java b/io/src/main/java/com/itextpdf/io/exceptions/IoExceptionMessage.java index 5512e47214..09dbbc76df 100644 --- a/io/src/main/java/com/itextpdf/io/exceptions/IoExceptionMessage.java +++ b/io/src/main/java/com/itextpdf/io/exceptions/IoExceptionMessage.java @@ -66,4 +66,5 @@ public final class IoExceptionMessage { public static final String IMAGE_MAGICK_OUTPUT_IS_NULL = "ImageMagick process output is null."; public static final String IMAGE_MAGICK_PROCESS_EXECUTION_FAILED = "ImageMagick process execution finished with errors: "; + public static final String ALREADY_CLOSED = "Already closed"; } diff --git a/io/src/main/java/com/itextpdf/io/source/ArrayRandomAccessSource.java b/io/src/main/java/com/itextpdf/io/source/ArrayRandomAccessSource.java index 3c83968cd9..9b216cae66 100644 --- a/io/src/main/java/com/itextpdf/io/source/ArrayRandomAccessSource.java +++ b/io/src/main/java/com/itextpdf/io/source/ArrayRandomAccessSource.java @@ -44,6 +44,8 @@ This file is part of the iText (R) project. package com.itextpdf.io.source; +import com.itextpdf.io.exceptions.IoExceptionMessage; + /** * A RandomAccessSource that is based on an underlying byte array */ @@ -53,30 +55,41 @@ class ArrayRandomAccessSource implements IRandomAccessSource { private byte[] array; public ArrayRandomAccessSource(byte[] array) { - if(array == null) throw new IllegalArgumentException("Passed byte array can not be null."); + if(array == null) { + throw new IllegalArgumentException("Passed byte array can not be null."); + } this.array = array; } public int get(long offset) { - if (offset >= array.length) return -1; + if (array == null) { + throw new IllegalStateException(IoExceptionMessage.ALREADY_CLOSED); + } + if (offset >= array.length) { + return -1; + } return 0xff & array[(int)offset]; } public int get(long offset, byte[] bytes, int off, int len) { - if (array == null) throw new IllegalStateException("Already closed"); - - if (offset >= array.length) + if (array == null) { + throw new IllegalStateException(IoExceptionMessage.ALREADY_CLOSED); + } + if (offset >= array.length) { return -1; - - if (offset + len > array.length) + } + if (offset + len > array.length) { len = (int)(array.length - offset); - + } System.arraycopy(array, (int)offset, bytes, off, len); return len; } public long length() { + if (array == null) { + throw new IllegalStateException(IoExceptionMessage.ALREADY_CLOSED); + } return array.length; } diff --git a/io/src/main/java/com/itextpdf/io/source/RASInputStream.java b/io/src/main/java/com/itextpdf/io/source/RASInputStream.java index b4d9500867..3e9b5b7e86 100644 --- a/io/src/main/java/com/itextpdf/io/source/RASInputStream.java +++ b/io/src/main/java/com/itextpdf/io/source/RASInputStream.java @@ -69,6 +69,15 @@ public RASInputStream(IRandomAccessSource source){ this.source = source; } + /** + * Gets the source + * + * @return an instance of {@link IRandomAccessSource} + */ + public IRandomAccessSource getSource() { + return source; + } + /** * {@inheritDoc} */ diff --git a/io/src/main/java/com/itextpdf/io/source/RandomAccessSourceFactory.java b/io/src/main/java/com/itextpdf/io/source/RandomAccessSourceFactory.java index cc4331468c..86c4f3acdc 100644 --- a/io/src/main/java/com/itextpdf/io/source/RandomAccessSourceFactory.java +++ b/io/src/main/java/com/itextpdf/io/source/RandomAccessSourceFactory.java @@ -140,10 +140,37 @@ public IRandomAccessSource createSource(URL url) throws java.io.IOException{ } /** - * Creates a {@link IRandomAccessSource} based on an {@link InputStream}. The full content of the InputStream is read into memory and used + * Creates or extracts a {@link IRandomAccessSource} based on an {@link InputStream}. + * + *
+ * If the InputStream is an instance of {@link RASInputStream} then extracts the source from it. + * Otherwise The full content of the InputStream is read into memory and used * as the source for the {@link IRandomAccessSource} + * * @param inputStream the stream to read from + * + * @return the newly created or extracted {@link IRandomAccessSource} + * + * @throws java.io.IOException in case of any I/O error. + */ + public IRandomAccessSource extractOrCreateSource(InputStream inputStream) throws java.io.IOException { + if (inputStream instanceof RASInputStream) { + return ((RASInputStream) inputStream).getSource(); + } + return createSource(StreamUtil.inputStreamToArray(inputStream)); + } + + /** + * Creates a {@link IRandomAccessSource} based on an {@link InputStream}. + * + *
+ * The full content of the InputStream is read into memory and used + * as the source for the {@link IRandomAccessSource} + * + * @param inputStream the stream to read from + * * @return the newly created {@link IRandomAccessSource} + * * @throws java.io.IOException in case of any I/O error. */ public IRandomAccessSource createSource(InputStream inputStream) throws java.io.IOException{ @@ -169,7 +196,6 @@ public IRandomAccessSource createBestSource(String filename) throws java.io.IOEx || filename.startsWith("https://") || filename.startsWith("jar:") || filename.startsWith("wsjar:") - || filename.startsWith("wsjar:") || filename.startsWith("vfszip:")) { return createSource(new URL(filename)); } else { diff --git a/io/src/test/java/com/itextpdf/io/source/RandomAccessSourceFactoryTest.java b/io/src/test/java/com/itextpdf/io/source/RandomAccessSourceFactoryTest.java new file mode 100644 index 0000000000..e6e62a3367 --- /dev/null +++ b/io/src/test/java/com/itextpdf/io/source/RandomAccessSourceFactoryTest.java @@ -0,0 +1,104 @@ +/* + This file is part of the iText (R) project. + Copyright (c) 1998-2022 iText Group NV + Authors: iText Software. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License version 3 + as published by the Free Software Foundation with the addition of the + following permission added to Section 15 as permitted in Section 7(a): + FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY + ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT + OF THIRD PARTY RIGHTS + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses or write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA, 02110-1301 USA, or download the license from the following URL: + http://itextpdf.com/terms-of-use/ + + The interactive user interfaces in modified source and object code versions + of this program must display Appropriate Legal Notices, as required under + Section 5 of the GNU Affero General Public License. + + In accordance with Section 7(b) of the GNU Affero General Public License, + a covered work must retain the producer line in every PDF that is created + or manipulated using iText. + + You can be released from the requirements of the license by purchasing + a commercial license. Buying such a license is mandatory as soon as you + develop commercial activities involving the iText software without + disclosing the source code of your own applications. + These activities include: offering paid services to customers as an ASP, + serving PDFs on the fly in a web application, shipping iText with a closed + source product. + + For more information, please contact iText Software Corp. at this + address: sales@itextpdf.com + */ +package com.itextpdf.io.source; + +import com.itextpdf.io.exceptions.IoExceptionMessage; +import com.itextpdf.test.ExtendedITextTest; +import com.itextpdf.test.annotations.type.UnitTest; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(UnitTest.class) +public class RandomAccessSourceFactoryTest extends ExtendedITextTest { + + private final static String SOURCE_FILE = "./src/test/resources/com/itextpdf/io/source/RAF.txt"; + + @Test + public void readRASInputStreamClosedTest() throws IOException { + String fileName = SOURCE_FILE; + try (InputStream pdfStream = new FileInputStream(fileName)) { + + IRandomAccessSource randomAccessSource = new RandomAccessSourceFactory() + .extractOrCreateSource(pdfStream); + RASInputStream rasInputStream = new RASInputStream(randomAccessSource); + IRandomAccessSource extractedRandomAccessSource = new RandomAccessSourceFactory() + .extractOrCreateSource(rasInputStream); + + extractedRandomAccessSource.close(); + + Exception e = Assert.assertThrows(IllegalStateException.class, () -> rasInputStream.read()); + Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage()); + + e = Assert.assertThrows(IllegalStateException.class, + () -> randomAccessSource.get(0)); + Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage()); + e = Assert.assertThrows(IllegalStateException.class, + () -> randomAccessSource.get(0, new byte[10], 0, 10)); + Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage()); + e = Assert.assertThrows(IllegalStateException.class, + () -> randomAccessSource.length()); + Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage()); + } + } + + @Test + public void readRASInputStreamTest() throws IOException { + String fileName = SOURCE_FILE; + try (InputStream pdfStream = new FileInputStream(fileName)) { + IRandomAccessSource randomAccessSource = new RandomAccessSourceFactory() + .extractOrCreateSource(pdfStream); + RASInputStream rasInputStream = new RASInputStream(randomAccessSource); + IRandomAccessSource extractedRandomAccessSource = new RandomAccessSourceFactory() + .extractOrCreateSource(rasInputStream); + + Assert.assertEquals(72, rasInputStream.read()); + Assert.assertEquals(72, extractedRandomAccessSource.get(0)); + Assert.assertEquals(extractedRandomAccessSource, rasInputStream.getSource()); + } + } +} diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java index a5f9d28c8e..69d330add9 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java @@ -48,6 +48,7 @@ This file is part of the iText (R) project. import com.itextpdf.io.source.ByteUtils; import com.itextpdf.io.source.IRandomAccessSource; import com.itextpdf.io.source.PdfTokenizer; +import com.itextpdf.io.source.RASInputStream; import com.itextpdf.io.source.RandomAccessFileOrArray; import com.itextpdf.io.source.RandomAccessSourceFactory; import com.itextpdf.io.source.WindowRandomAccessSource; @@ -138,13 +139,15 @@ public PdfReader(IRandomAccessSource byteSource, ReaderProperties properties) th /** * Reads and parses a PDF document. * - * @param is the {@code InputStream} containing the document. The stream is read to the - * end but is not closed. + * @param is the {@code InputStream} containing the document. If the inputStream is an instance of + * {@link RASInputStream} then the {@link IRandomAccessSource} would be extracted. Otherwise the stream + * is read to the end but is not closed. * @param properties properties of the created reader + * * @throws IOException on error */ public PdfReader(InputStream is, ReaderProperties properties) throws IOException { - this(new RandomAccessSourceFactory().createSource(is), properties, true); + this(new RandomAccessSourceFactory().extractOrCreateSource(is), properties, true); } /** @@ -161,8 +164,10 @@ public PdfReader(java.io.File file) throws FileNotFoundException, IOException { /** * Reads and parses a PDF document. * - * @param is the {@code InputStream} containing the document. the {@code InputStream} containing the document. The stream is read to the - * end but is not closed. + * @param is the {@code InputStream} containing the document. If the inputStream is an instance of + * {@link RASInputStream} then the {@link IRandomAccessSource} would be extracted. Otherwise the stream + * is read to the end but is not closed. + * * @throws IOException on error */ public PdfReader(InputStream is) throws IOException { diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java index 256a4b4616..77c9b1cd66 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java @@ -44,11 +44,13 @@ This file is part of the iText (R) project. import com.itextpdf.commons.utils.FileUtil; import com.itextpdf.commons.utils.MessageFormatUtil; +import com.itextpdf.io.exceptions.IoExceptionMessage; import com.itextpdf.io.font.PdfEncodings; import com.itextpdf.io.logs.IoLogMessageConstant; import com.itextpdf.io.source.ByteArrayOutputStream; import com.itextpdf.io.source.ByteUtils; import com.itextpdf.io.source.IRandomAccessSource; +import com.itextpdf.io.source.RASInputStream; import com.itextpdf.io.source.RandomAccessSourceFactory; import com.itextpdf.kernel.exceptions.InvalidXRefPrevException; import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant; @@ -2223,6 +2225,53 @@ public void closingArrayBracketMissingConservativeTest() throws IOException { exception.getCause().getMessage()); } + @Test + public void readRASInputStreamClosedTest() throws IOException { + String fileName = SOURCE_FOLDER + "hello.pdf"; + try (InputStream pdfStream = new FileInputStream(fileName)) { + + IRandomAccessSource randomAccessSource = new RandomAccessSourceFactory() + .extractOrCreateSource(pdfStream); + RASInputStream rasInputStream = new RASInputStream(randomAccessSource); + + randomAccessSource.close(); + + Exception e = Assert.assertThrows(IllegalStateException.class, + () -> new PdfReader(rasInputStream)); + Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage()); + } + } + + @Test + public void readRASInputStreamTest() throws IOException { + String fileName = SOURCE_FOLDER + "hello.pdf"; + try (InputStream pdfStream = new FileInputStream(fileName)) { + IRandomAccessSource randomAccessSource = new RandomAccessSourceFactory() + .extractOrCreateSource(pdfStream); + RASInputStream rasInputStream = new RASInputStream(randomAccessSource); + + try (PdfReader reader = new PdfReader(rasInputStream)) { + randomAccessSource.close(); + Exception e = Assert.assertThrows(IllegalStateException.class, () -> new PdfDocument(reader)); + Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage()); + } + } + } + + @Test + public void readRASInputStreamValidTest() throws IOException { + String fileName = SOURCE_FOLDER + "hello.pdf"; + try (InputStream pdfStream = new FileInputStream(fileName)) { + IRandomAccessSource randomAccessSource = new RandomAccessSourceFactory() + .extractOrCreateSource(pdfStream); + RASInputStream rasInputStream = new RASInputStream(randomAccessSource); + + try (PdfReader reader = new PdfReader(rasInputStream)) { + AssertUtil.doesNotThrow(() -> new PdfDocument(reader)); + } + } + } + private static File copyFileForTest(String fileName, String copiedFileName) throws IOException { File copiedFile = new File(copiedFileName); Files.copy(Paths.get(fileName), Paths.get(copiedFileName)); diff --git a/sign/src/test/java/com/itextpdf/signatures/LtvVerifierIntegrationTest.java b/sign/src/test/java/com/itextpdf/signatures/LtvVerifierIntegrationTest.java index 8be3b60832..48f3926f0d 100644 --- a/sign/src/test/java/com/itextpdf/signatures/LtvVerifierIntegrationTest.java +++ b/sign/src/test/java/com/itextpdf/signatures/LtvVerifierIntegrationTest.java @@ -45,6 +45,7 @@ This file is part of the iText (R) project. public class LtvVerifierIntegrationTest extends ExtendedITextTest { private static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/signatures/LtvVerifierIntegrationTest/"; + @BeforeClass public static void before() { Security.addProvider(new BouncyCastleProvider()); @@ -262,4 +263,23 @@ public void notTrustedRootCertificateInLatestRevisionTest() Assert.assertEquals("Root certificate passed without checking", verificationOK.message); } } + + @Test + public void switchBetweenSeveralRevisionsTest() throws IOException, GeneralSecurityException { + String testInput = SOURCE_FOLDER + "severalConsequentSignatures.pdf"; + + try(PdfReader pdfReader = new PdfReader(testInput); PdfDocument pdfDoc = new PdfDocument(pdfReader)) { + + LtvVerifier ltvVerifier = new LtvVerifier(pdfDoc); + + Assert.assertEquals("timestampSig2", ltvVerifier.signatureName); + ltvVerifier.switchToPreviousRevision(); + Assert.assertEquals("Signature2", ltvVerifier.signatureName); + ltvVerifier.switchToPreviousRevision(); + Assert.assertEquals("timestampSig1", ltvVerifier.signatureName); + ltvVerifier.switchToPreviousRevision(); + Assert.assertEquals("Signature1", ltvVerifier.signatureName); + ltvVerifier.switchToPreviousRevision(); + } + } } diff --git a/sign/src/test/resources/com/itextpdf/signatures/LtvVerifierIntegrationTest/severalConsequentSignatures.pdf b/sign/src/test/resources/com/itextpdf/signatures/LtvVerifierIntegrationTest/severalConsequentSignatures.pdf new file mode 100644 index 0000000000..f161581d43 Binary files /dev/null and b/sign/src/test/resources/com/itextpdf/signatures/LtvVerifierIntegrationTest/severalConsequentSignatures.pdf differ