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;
     }
 
     /**

Reply via email to