Skip to content

Commit 076595c

Browse files
committed
Add Strictness level for PdfReader
DEVSIX-5094
1 parent 923408c commit 076595c

File tree

4 files changed

+143
-26
lines changed

4 files changed

+143
-26
lines changed

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfDocument.java

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ This file is part of the iText (R) project.
6767
import com.itextpdf.kernel.log.ICounter;
6868
import com.itextpdf.kernel.numbering.EnglishAlphabetNumbering;
6969
import com.itextpdf.kernel.numbering.RomanNumbering;
70+
import com.itextpdf.kernel.pdf.PdfReader.StrictnessLevel;
7071
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
7172
import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation;
7273
import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation;
@@ -1923,34 +1924,10 @@ protected void open(PdfVersion newPdfVersion) {
19231924
pdfVersion = reader.headerPdfVersion;
19241925
trailer = new PdfDictionary(reader.trailer);
19251926

1926-
PdfArray id = reader.trailer.getAsArray(PdfName.ID);
1927-
1928-
if (id != null) {
1929-
if (id.size() == 2) {
1930-
originalDocumentId = id.getAsString(0);
1931-
modifiedDocumentId = id.getAsString(1);
1932-
}
1933-
1934-
if (originalDocumentId == null || modifiedDocumentId == null) {
1935-
Logger logger = LoggerFactory.getLogger(PdfDocument.class);
1936-
logger.error(LogMessageConstant.DOCUMENT_IDS_ARE_CORRUPTED);
1937-
}
1938-
}
1927+
readDocumentIds();
19391928

19401929
catalog = new PdfCatalog((PdfDictionary) trailer.get(PdfName.Root, true));
1941-
if (catalog.getPdfObject().containsKey(PdfName.Version)) {
1942-
// The version of the PDF specification to which the document conforms (for example, 1.4)
1943-
// if later than the version specified in the file's header
1944-
try {
1945-
PdfVersion catalogVersion = PdfVersion.fromPdfName(catalog.getPdfObject().getAsName(PdfName.Version));
1946-
if (catalogVersion.compareTo(pdfVersion) > 0) {
1947-
pdfVersion = catalogVersion;
1948-
}
1949-
} catch (IllegalArgumentException e) {
1950-
LoggerFactory.getLogger(PdfDocument.class)
1951-
.error(LogMessageConstant.DOCUMENT_VERSION_IN_CATALOG_CORRUPTED);
1952-
}
1953-
}
1930+
updatePdfVersionFromCatalog();
19541931
PdfStream xmpMetadataStream = catalog.getPdfObject().getAsStream(PdfName.Metadata);
19551932
if (xmpMetadataStream != null) {
19561933
xmpMetadata = xmpMetadataStream.getBytes();
@@ -2495,6 +2472,45 @@ private String addModifiedPostfix(String producer) {
24952472
}
24962473
}
24972474

2475+
private void updatePdfVersionFromCatalog() {
2476+
if (catalog.getPdfObject().containsKey(PdfName.Version)) {
2477+
// The version of the PDF specification to which the document conforms (for example, 1.4)
2478+
// if later than the version specified in the file's header
2479+
try {
2480+
PdfVersion catalogVersion = PdfVersion.fromPdfName(catalog.getPdfObject().getAsName(PdfName.Version));
2481+
if (catalogVersion.compareTo(pdfVersion) > 0) {
2482+
pdfVersion = catalogVersion;
2483+
}
2484+
} catch (IllegalArgumentException e) {
2485+
processReadingError(LogMessageConstant.DOCUMENT_VERSION_IN_CATALOG_CORRUPTED);
2486+
}
2487+
}
2488+
}
2489+
2490+
private void readDocumentIds() {
2491+
PdfArray id = reader.trailer.getAsArray(PdfName.ID);
2492+
2493+
if (id != null) {
2494+
if (id.size() == 2) {
2495+
originalDocumentId = id.getAsString(0);
2496+
modifiedDocumentId = id.getAsString(1);
2497+
}
2498+
2499+
if (originalDocumentId == null || modifiedDocumentId == null) {
2500+
processReadingError(LogMessageConstant.DOCUMENT_IDS_ARE_CORRUPTED);
2501+
}
2502+
}
2503+
}
2504+
2505+
private void processReadingError(String errorMessage) {
2506+
if (StrictnessLevel.CONSERVATIVE.isStricter(reader.getStrictnessLevel())) {
2507+
Logger logger = LoggerFactory.getLogger(PdfDocument.class);
2508+
logger.error(errorMessage);
2509+
} else {
2510+
throw new PdfException(errorMessage);
2511+
}
2512+
}
2513+
24982514
private static void overrideFullCompressionInWriterProperties(WriterProperties properties, boolean readerHasXrefStream) {
24992515
if (Boolean.TRUE == properties.isFullCompression && !readerHasXrefStream) {
25002516
Logger logger = LoggerFactory.getLogger(PdfDocument.class);

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ This file is part of the iText (R) project.
7272
*/
7373
public class PdfReader implements Closeable, Serializable {
7474

75+
/**
76+
* The default {@link StrictnessLevel} to be used.
77+
*/
78+
public static final StrictnessLevel DEFAULT_STRICTNESS_LEVEL = StrictnessLevel.LENIENT;
79+
7580
private static final long serialVersionUID = -3584187443691964939L;
7681

7782
private static final String endstream1 = "endstream";
@@ -87,6 +92,8 @@ public class PdfReader implements Closeable, Serializable {
8792

8893
private boolean memorySavingMode;
8994

95+
private StrictnessLevel strictnessLevel = DEFAULT_STRICTNESS_LEVEL;
96+
9097
//indicate nearest first Indirect reference object which includes current reading the object, using for PdfString decrypt
9198
private PdfIndirectReference currentIndirectReference;
9299

@@ -223,6 +230,28 @@ public PdfReader setMemorySavingMode(boolean memorySavingMode) {
223230
return this;
224231
}
225232

233+
/**
234+
* Get the current {@link StrictnessLevel} of the reader.
235+
*
236+
* @return the current {@link StrictnessLevel}
237+
*/
238+
public StrictnessLevel getStrictnessLevel() {
239+
return strictnessLevel;
240+
}
241+
242+
/**
243+
* Set the {@link StrictnessLevel} for the reader. If the argument is {@code null}, then
244+
* the {@link PdfReader#DEFAULT_STRICTNESS_LEVEL} will be used.
245+
*
246+
* @param strictnessLevel the {@link StrictnessLevel} to set
247+
*
248+
* @return this {@link PdfReader} instance
249+
*/
250+
public PdfReader setStrictnessLevel(StrictnessLevel strictnessLevel) {
251+
this.strictnessLevel = strictnessLevel == null ? DEFAULT_STRICTNESS_LEVEL : strictnessLevel;
252+
return this;
253+
}
254+
226255
/**
227256
* Gets whether {@link #close()} method shall close input stream.
228257
*
@@ -1457,4 +1486,39 @@ public void close() throws IOException {
14571486
buffer = null;
14581487
}
14591488
}
1489+
1490+
/**
1491+
* Enumeration representing the strictness level for reading.
1492+
*/
1493+
public enum StrictnessLevel {
1494+
/**
1495+
* The reading strictness level at which iText fails (throws an exception) in case of
1496+
* contradiction with PDF specification, but still recovers from mild parsing errors
1497+
* and ambiguities.
1498+
*/
1499+
CONSERVATIVE(5000),
1500+
/**
1501+
* The reading strictness level at which iText tries to recover from parsing
1502+
* errors if possible.
1503+
*/
1504+
LENIENT(3000);
1505+
1506+
private final int levelValue;
1507+
1508+
StrictnessLevel(int levelValue) {
1509+
this.levelValue = levelValue;
1510+
}
1511+
1512+
/**
1513+
* Checks whether the current instance represents more strict reading level than
1514+
* the provided one. Note that the {@code null} is less strict than any other value.
1515+
*
1516+
* @param compareWith the {@link StrictnessLevel} to compare with
1517+
*
1518+
* @return {@code true} if the current level is stricter than the provided one
1519+
*/
1520+
public boolean isStricter(StrictnessLevel compareWith) {
1521+
return compareWith == null || this.levelValue > compareWith.levelValue;
1522+
}
1523+
}
14601524
}

kernel/src/test/java/com/itextpdf/kernel/pdf/PdfDocumentIdTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,18 @@ This file is part of the iText (R) project.
5050
import com.itextpdf.io.LogMessageConstant;
5151
import com.itextpdf.io.source.RandomAccessSourceFactory;
5252
import com.itextpdf.kernel.PdfException;
53+
import com.itextpdf.kernel.pdf.PdfReader.StrictnessLevel;
5354
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
5455
import com.itextpdf.test.ExtendedITextTest;
5556
import com.itextpdf.test.annotations.LogMessage;
5657
import com.itextpdf.test.annotations.LogMessages;
5758
import com.itextpdf.test.annotations.type.IntegrationTest;
5859
import org.junit.Assert;
5960
import org.junit.BeforeClass;
61+
import org.junit.Rule;
6062
import org.junit.Test;
6163
import org.junit.experimental.categories.Category;
64+
import org.junit.rules.ExpectedException;
6265

6366
/**
6467
* @author Michael Demey
@@ -68,6 +71,9 @@ public class PdfDocumentIdTest extends ExtendedITextTest {
6871
public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/pdf/PdfDocumentTestID/";
6972
public static final String destinationFolder = "./target/test/com/itextpdf/kernel/pdf/PdfDocumentTestID/";
7073

74+
@Rule
75+
public ExpectedException junitExpectedException = ExpectedException.none();
76+
7177
@BeforeClass
7278
public static void beforeClass() {
7379
createOrClearDestinationFolder(destinationFolder);
@@ -375,6 +381,17 @@ public void readPdfWithNoIdTest() throws IOException{
375381
Assert.assertEquals(0, reader.getModifiedFileId().length);
376382
}
377383

384+
@Test
385+
public void readPdfWithNoIdAndConservativeReadingTest() throws IOException{
386+
try (PdfReader reader = new PdfReader(sourceFolder + "pdfWithNoId.pdf")
387+
.setStrictnessLevel(StrictnessLevel.CONSERVATIVE)) {
388+
389+
junitExpectedException.expect(PdfException.class);
390+
junitExpectedException.expectMessage(LogMessageConstant.DOCUMENT_IDS_ARE_CORRUPTED);
391+
new PdfDocument(reader);
392+
}
393+
}
394+
378395

379396
// @Test
380397
// public void appendModeTest() {

kernel/src/test/java/com/itextpdf/kernel/pdf/PdfDocumentTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,12 @@ This file is part of the iText (R) project.
4545
import com.itextpdf.io.LogMessageConstant;
4646
import com.itextpdf.io.image.ImageDataFactory;
4747
import com.itextpdf.io.source.DeflaterOutputStream;
48+
import com.itextpdf.io.util.MessageFormatUtil;
49+
import com.itextpdf.kernel.PdfException;
4850
import com.itextpdf.kernel.colors.ColorConstants;
51+
import com.itextpdf.kernel.crypto.securityhandler.UnsupportedSecurityHandlerException;
4952
import com.itextpdf.kernel.geom.Rectangle;
53+
import com.itextpdf.kernel.pdf.PdfReader.StrictnessLevel;
5054
import com.itextpdf.kernel.pdf.annot.PdfTextAnnotation;
5155
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
5256
import com.itextpdf.kernel.pdf.filespec.PdfFileSpec;
@@ -70,15 +74,20 @@ This file is part of the iText (R) project.
7074

7175
import org.junit.Assert;
7276
import org.junit.BeforeClass;
77+
import org.junit.Rule;
7378
import org.junit.Test;
7479
import org.junit.experimental.categories.Category;
80+
import org.junit.rules.ExpectedException;
7581

7682
@Category(IntegrationTest.class)
7783
public class PdfDocumentTest extends ExtendedITextTest {
7884

7985
public static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/kernel/pdf/PdfDocumentTest/";
8086
public static final String DESTINATION_FOLDER = "./target/test/com/itextpdf/kernel/pdf/PdfDocumentTest/";
8187

88+
@Rule
89+
public ExpectedException junitExpectedException = ExpectedException.none();
90+
8291
@BeforeClass
8392
public static void beforeClass() {
8493
createOrClearDestinationFolder(DESTINATION_FOLDER);
@@ -536,6 +545,17 @@ public void openDocumentWithInvalidCatalogVersionTest() throws IOException {
536545
}
537546
}
538547

548+
@Test
549+
public void openDocumentWithInvalidCatalogVersionAndConservativeStrictnessReadingTest() throws IOException {
550+
try (PdfReader reader = new PdfReader(SOURCE_FOLDER + "sample-with-invalid-catalog-version.pdf")
551+
.setStrictnessLevel(StrictnessLevel.CONSERVATIVE)) {
552+
553+
junitExpectedException.expect(PdfException.class);
554+
junitExpectedException.expectMessage(LogMessageConstant.DOCUMENT_VERSION_IN_CATALOG_CORRUPTED);
555+
new PdfDocument(reader);
556+
}
557+
}
558+
539559

540560
private static class IgnoreTagStructurePdfDocument extends PdfDocument {
541561

0 commit comments

Comments
 (0)