Author: tilman
Date: Tue Mar 24 10:55:13 2026
New Revision: 1932499
Log:
PDFBOX-5660: optimize / refactor, as suggested by Valery Bokov; closes #424
Modified:
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java
Modified:
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java
==============================================================================
---
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java
Tue Mar 24 09:39:55 2026 (r1932498)
+++
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java
Tue Mar 24 10:55:13 2026 (r1932499)
@@ -427,6 +427,28 @@ public abstract class SecurityHandler
}
/**
+ * This will decrypt a string if it is not in set of the objects.
+ *
+ * @param string The string to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation Number.
+ *
+ * @return the encrypted/decrypted COS object
+ */
+ private COSBase decryptStringIfAbsent(COSString string, long objNum, long
genNum)
+ {
+ // PDFBOX-4477: only cache strings and streams, this improves speed
and memory footprint
+ if (objects.contains(string))
+ {
+ return string;
+ }
+ // replace the given COSString object with the encrypted/decrypted
version
+ COSBase decryptedString = decryptString(string, objNum, genNum);
+ objects.add(decryptedString);
+ return decryptedString;
+ }
+
+ /**
* This will dispatch to the correct method.
*
* @param obj The object to decrypt.
@@ -435,35 +457,46 @@ public abstract class SecurityHandler
*
* @throws IOException If there is an error getting the stream data.
*/
- public void decrypt(COSBase obj, long objNum, long genNum) throws
IOException
+ public COSBase decrypt(COSBase obj, long objNum, long genNum) throws
IOException
{
// PDFBOX-4477: only cache strings and streams, this improves speed
and memory footprint
if (obj instanceof COSString)
{
- if (objects.contains(obj))
- {
- return;
- }
- objects.add(obj);
- decryptString((COSString) obj, objNum, genNum);
+ return decryptStringIfAbsent((COSString)obj, objNum, genNum);
}
else if (obj instanceof COSStream)
{
- if (objects.contains(obj))
- {
- return;
- }
- objects.add(obj);
- decryptStream((COSStream) obj, objNum, genNum);
+ return decryptStreamIfAbsent((COSStream)obj, objNum, genNum);
}
else if (obj instanceof COSDictionary)
{
- decryptDictionary((COSDictionary) obj, objNum, genNum);
+ return decryptDictionary((COSDictionary) obj, objNum, genNum);
}
else if (obj instanceof COSArray)
{
- decryptArray((COSArray) obj, objNum, genNum);
+ return decryptArray((COSArray) obj, objNum, genNum);
}
+ return obj;
+ }
+
+ /**
+ * This will decrypt a stream if it is not in set of the objects.
+ *
+ * @param stream The stream to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation Number.
+ *
+ * @return the encrypted/decrypted COS object
+ */
+ private COSBase decryptStreamIfAbsent(COSStream stream, long objNum, long
genNum) throws IOException
+ {
+ if (!objects.contains(stream))
+ {
+ objects.add(stream);
+ decryptStream(stream, objNum, genNum);
+ }
+
+ return stream;
}
/**
@@ -562,14 +595,16 @@ public abstract class SecurityHandler
* @param objNum The object number.
* @param genNum The object generation number.
*
+ * @return the encrypted/decrypted COS object
+ *
* @throws IOException If there is an error creating a new string.
*/
- private void decryptDictionary(COSDictionary dictionary, long objNum, long
genNum) throws IOException
+ private COSBase decryptDictionary(COSDictionary dictionary, long objNum,
long genNum) throws IOException
{
if (dictionary.getItem(COSName.CF) != null)
{
// PDFBOX-2936: avoid orphan /CF dictionaries found in US govt
"I-" files
- return;
+ return dictionary;
}
COSBase type = dictionary.getDictionaryObject(COSName.TYPE);
boolean isSignature = COSName.SIG.equals(type) ||
COSName.DOC_TIME_STAMP.equals(type) ||
@@ -586,11 +621,25 @@ public abstract class SecurityHandler
}
COSBase value = entry.getValue();
// within a dictionary only the following kind of COS objects have
to be decrypted
- if (value instanceof COSString || value instanceof COSArray ||
value instanceof COSDictionary)
+ if (value instanceof COSString)
{
- decrypt(value, objNum, genNum);
+ entry.setValue(decryptStringIfAbsent((COSString)value, objNum,
genNum));
+ }
+ else if (value instanceof COSArray)
+ {
+ entry.setValue(decryptArray((COSArray) value, objNum, genNum));
+ }
+ else if (value instanceof COSStream)
+ {
+ entry.setValue(decryptStreamIfAbsent((COSStream)value, objNum,
genNum));
+ }
+ else if (value instanceof COSDictionary)
+ {
+ entry.setValue(decryptDictionary((COSDictionary)value, objNum,
genNum));
}
}
+
+ return dictionary;
}
/**
@@ -599,14 +648,16 @@ public abstract class SecurityHandler
* @param string the string to decrypt.
* @param objNum The object number.
* @param genNum The object generation number.
+ *
+ * @return the decrypted COSString
*
*/
- private void decryptString(COSString string, long objNum, long genNum)
+ private COSBase decryptString(COSString string, long objNum, long genNum)
{
// String encrypted with identity filter
if (COSName.IDENTITY.equals(stringFilterName))
{
- return;
+ return string;
}
ByteArrayInputStream data = new
ByteArrayInputStream(string.getBytes());
@@ -614,12 +665,13 @@ public abstract class SecurityHandler
try
{
encryptData(objNum, genNum, data, outputStream, true /* decrypt
*/);
- string.setValue(outputStream.toByteArray());
+ return new COSString(outputStream.toByteArray());
}
catch (IOException ex)
{
LOG.error("Failed to decrypt COSString of length " +
string.getBytes().length +
" in object " + objNum + ": " + ex.getMessage());
+ return string;
}
}
@@ -647,14 +699,18 @@ public abstract class SecurityHandler
* @param objNum The object number.
* @param genNum The object generation number.
*
+ * @return the encrypted/decrypted COS object
+ *
* @throws IOException If there is an error accessing the data.
*/
- private void decryptArray(COSArray array, long objNum, long genNum) throws
IOException
+ private COSBase decryptArray(COSArray array, long objNum, long genNum)
throws IOException
{
for (int i = 0; i < array.size(); i++)
{
- decrypt(array.get(i), objNum, genNum);
+ array.set(i, decrypt(array.get(i), objNum, genNum));
}
+
+ return array;
}
/**