I have the below method that merges my PDF documents. However, in some cases where the document has a watermark, the method throws me an error.
public void doMergeUsingItext7(List<InputStream> list, OutputStream outputStream) throws SSException {
try (com.itextpdf.kernel.pdf.PdfWriter writer = new com.itextpdf.kernel.pdf.PdfWriter(outputStream);) {
writer.setSmartMode(Boolean.TRUE);
try (com.itextpdf.kernel.pdf.PdfDocument pdfDoc = new com.itextpdf.kernel.pdf.PdfDocument(writer)) {
pdfDoc.initializeOutlines();
list.forEach((in) -> {
try (com.itextpdf.kernel.pdf.PdfReader reader = new com.itextpdf.kernel.pdf.PdfReader(in);) {
reader.setUnethicalReading(Boolean.TRUE);
try (com.itextpdf.kernel.pdf.PdfDocument addedDoc = new com.itextpdf.kernel.pdf.PdfDocument(reader)) { //ERROR IS THROWN ON THIS LINE
addedDoc.copyPagesTo(1, addedDoc.getNumberOfPages(), pdfDoc);
logger.log(Level.INFO, "Successfully Added the Document to PDF");
} catch (Exception e) {
ExceptionUtils.printRootCauseStackTrace(e);
}
} catch (IOException ex) {
ExceptionUtils.printRootCauseStackTrace(ex);
} catch (Exception e) {
ExceptionUtils.printRootCauseStackTrace(e);
}
});
}
} catch (Exception ex) {
throw new SSException(ex, "Print Version Failed");
}
}
The following error is thrown when the document has a watermark...
com.itextpdf.kernel.PdfException: Illegal length value.
at com.itextpdf.kernel.pdf.PdfEncryption.readAndSetCryptoModeForStdHandler(PdfEncryption.java:523)
at com.itextpdf.kernel.pdf.PdfEncryption.<init>(PdfEncryption.java:229)
at com.itextpdf.kernel.pdf.PdfReader.readDecryptObj(PdfReader.java:1251)
at com.itextpdf.kernel.pdf.PdfReader.readPdf(PdfReader.java:685)
at com.itextpdf.kernel.pdf.PdfDocument.open(PdfDocument.java:1871)
at com.itextpdf.kernel.pdf.PdfDocument.<init>(PdfDocument.java:252)
at com.itextpdf.kernel.pdf.PdfDocument.<init>(PdfDocument.java:234)
at gov.ca.lc.util.PdfUtilFuntions.lambda$doMergeUsingItext7$0(PdfUtilFuntions.java:180)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at gov.ca.lc.util.PdfUtilFuntions.doMergeUsingItext7(PdfUtilFuntions.java:176)
I am not sure what exactly within the document is failing to merge. Any help on fixing this issue is highly appreciated. Thanks.
CodePudding user response:
This is a bug in iText.
The PDF in question is encrypted. Its encryption dictionary has a V value of 1 ("RC4 or AES algorithms with an encryption key length of 40 bits") and an R value of 3 ("for files encrypted with a V value of 2 or 3, or with any “Security handlers of revision 3 or greater” access permissions set to 0").
A Length value for the key length is specified only for V 2 or 3, for V 1 the key length is fixed to 40. Nonetheless, iText in case of R 3 requires a Length value.
As there is no Length value in the encryption dictionary of your document, iText fails reading that file.
As a side note: 40 bit key length security nowadays is no security. So most likely that issue did not pop up earlier because using V 1 security (or 40 bit key length security in general) is useless and, therefore, no use case taken seriously.
A Quick Fix
If you don't shy away from recompiling iText, fixing the bug is easy. Simply edit the iText kernel class com.itextpdf.kernel.pdf.PdfEncryption
. Its method readAndSetCryptoModeForStdHandler
starts with
int cryptoMode;
int length = 0;
PdfNumber rValue = encDict.getAsNumber(PdfName.R);
if (rValue == null)
throw new PdfException(KernelExceptionMessageConstant.ILLEGAL_R_VALUE);
int revision = rValue.intValue();
boolean embeddedFilesOnlyMode = readEmbeddedFilesOnlyFromEncryptDictionary(encDict);
switch (revision) {
case 2:
cryptoMode = EncryptionConstants.STANDARD_ENCRYPTION_40;
break;
case 3:
PdfNumber lengthValue = encDict.getAsNumber(PdfName.Length);
if (lengthValue == null)
throw new PdfException(KernelExceptionMessageConstant.ILLEGAL_LENGTH_VALUE);
length = lengthValue.intValue();
if (length > 128 || length < 40 || length % 8 != 0)
throw new PdfException(KernelExceptionMessageConstant.ILLEGAL_LENGTH_VALUE);
cryptoMode = EncryptionConstants.STANDARD_ENCRYPTION_128;
break;
Here simply replace
if (lengthValue == null)
throw new PdfException(KernelExceptionMessageConstant.ILLEGAL_LENGTH_VALUE);
length = lengthValue.intValue();
by
length = 40;
if (lengthValue != null)
length = lengthValue.intValue();
(I used a replacement with an identical number of lines to make interpreting later stack traces easier.)