Author: msahyoun
Date: Tue Mar 31 16:49:45 2026
New Revision: 1932684

Log:
PDFBOX-6185: move temp directory creation to IOUtils; create only when needed

Modified:
   pdfbox/trunk/debugger/src/main/java/org/apache/pdfbox/debugger/ui/Tree.java
   pdfbox/trunk/io/src/main/java/org/apache/pdfbox/io/IOUtils.java

Modified: 
pdfbox/trunk/debugger/src/main/java/org/apache/pdfbox/debugger/ui/Tree.java
==============================================================================
--- pdfbox/trunk/debugger/src/main/java/org/apache/pdfbox/debugger/ui/Tree.java 
Tue Mar 31 16:09:07 2026        (r1932683)
+++ pdfbox/trunk/debugger/src/main/java/org/apache/pdfbox/debugger/ui/Tree.java 
Tue Mar 31 16:49:45 2026        (r1932684)
@@ -22,6 +22,7 @@ import org.apache.pdfbox.cos.COSBase;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSStream;
 import org.apache.pdfbox.debugger.treestatus.TreeStatus;
+import org.apache.pdfbox.io.IOUtils;
 import org.apache.pdfbox.pdmodel.common.PDStream;
 
 import javax.swing.JMenuItem;
@@ -39,22 +40,12 @@ import java.awt.event.MouseEvent;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclEntryPermission;
-import java.nio.file.attribute.AclEntryType;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.PosixFilePermissions;
-import java.nio.file.attribute.UserPrincipal;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import java.util.StringJoiner;
-import java.util.stream.Stream;
 
 import org.apache.pdfbox.debugger.PDFDebugger;
 
@@ -73,81 +64,8 @@ public class Tree extends JTree
     // Temporary files are stored in a private temp directory with restricted 
permissions,
     // which is deleted on exit using a shutdown hook.
     // PDFBOX-6185
-    private static final Path TEMP_DIR = createTempDir();
-
-    private static Path createTempDir()
-    {
-        try
-        {
-            Path tempDir = Files.createTempDirectory("pdfbox-");
-            applyOwnerOnlyPermissions(tempDir);
-
-            // use shutdown hook instead of deleteOnExit() to ensure deletion
-            // of the entire directory in case of not automatically deleted on 
-            // JVM exit (e.g. due to hard crash or kill -9)
-            Runtime.getRuntime().addShutdownHook(new Thread(() ->
-            {
-                try (Stream<Path> entries = Files.walk(tempDir))
-                {
-                    entries.sorted(Comparator.reverseOrder())
-                        .forEach(p -> p.toFile().delete());
-                }
-                catch (IOException ignored) {}
-            }));
-
-            return tempDir;
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException("Failed to create temporary directory 
for PDFDebugger", e);
-        }
-    }
-
-    private static void applyOwnerOnlyPermissions(Path dir) throws IOException
-    {
-        if 
(FileSystems.getDefault().supportedFileAttributeViews().contains("posix"))
-        {
-            // Unix/macOS — rwx------
-            Files.setPosixFilePermissions(dir, 
PosixFilePermissions.fromString("rwx------"));
-        }
-        else
-        {
-            // Windows — replace the entire ACL with a single owner-only ALLOW 
entry
-            AclFileAttributeView aclView =
-                Files.getFileAttributeView(dir, AclFileAttributeView.class);
-
-            if (aclView == null)
-            {
-                throw new IOException("Neither posix nor ACL view is supported 
on this file system");
-            }
-
-            UserPrincipal owner = aclView.getOwner();
-
-            AclEntry ownerFullControl = AclEntry.newBuilder()
-                .setType(AclEntryType.ALLOW)
-                .setPrincipal(owner)
-                .setPermissions(
-                    AclEntryPermission.READ_DATA,
-                    AclEntryPermission.WRITE_DATA,
-                    AclEntryPermission.APPEND_DATA,
-                    AclEntryPermission.READ_NAMED_ATTRS,
-                    AclEntryPermission.WRITE_NAMED_ATTRS,
-                    AclEntryPermission.EXECUTE,
-                    AclEntryPermission.DELETE_CHILD,
-                    AclEntryPermission.READ_ATTRIBUTES,
-                    AclEntryPermission.WRITE_ATTRIBUTES,
-                    AclEntryPermission.DELETE,
-                    AclEntryPermission.READ_ACL,
-                    AclEntryPermission.WRITE_ACL,
-                    AclEntryPermission.SYNCHRONIZE
-                )
-                .build();
-
-            // Set so that only the owner has permissions, and remove any 
inherited ACL entries
-            aclView.setAcl(Collections.singletonList(ownerFullControl));
-        }
-    }
-
+    private Path tempDir;
+    
     /**
      * Constructor.
      */
@@ -385,7 +303,11 @@ public class Tree extends JTree
         {
             try
             {
-                File temp = Files.createTempFile(TEMP_DIR, "pdfbox", "." + 
extension).toFile();
+                if (tempDir == null)
+                {
+                    tempDir = IOUtils.createProtectedTempDir();
+                }
+                File temp = Files.createTempFile(tempDir, "pdfbox", "." + 
extension).toFile();
 
                 try (InputStream is = cosStream.createInputStream())
                 {

Modified: pdfbox/trunk/io/src/main/java/org/apache/pdfbox/io/IOUtils.java
==============================================================================
--- pdfbox/trunk/io/src/main/java/org/apache/pdfbox/io/IOUtils.java     Tue Mar 
31 16:09:07 2026        (r1932683)
+++ pdfbox/trunk/io/src/main/java/org/apache/pdfbox/io/IOUtils.java     Tue Mar 
31 16:49:45 2026        (r1932684)
@@ -28,6 +28,7 @@ import static java.lang.invoke.MethodTyp
 import static java.util.Objects.nonNull;
 
 import java.io.Closeable;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -37,11 +38,23 @@ import java.lang.invoke.MethodHandles.Lo
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.nio.ByteBuffer;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclEntryPermission;
+import java.nio.file.attribute.AclEntryType;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipal;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.LogManager;
@@ -319,4 +332,88 @@ public final class IOUtils
     {
         return MemoryUsageSetting.setupTempFileOnly().streamCache;
     }
+
+    /**
+     * Creates a temporary directory in the default temporary-file directory 
+     * with owner-only permissions and registers a shutdown hook to delete it 
on JVM exit.
+     * 
+     * <p>Note: This method is designed to be used for storing temporary files 
that may contain sensitive data
+     * in a temporary directories with restricted permissions, to mitigate the 
risk of unauthorized access by
+     * other users or processes on the same system. Used e.g. by 
PDFDebugger.</p>
+     * 
+     * @return the path to the created temporary directory
+     * @throws IOException 
+     */
+    public static Path createProtectedTempDir() throws IOException
+    {
+        // S5443: permissions are immediately restricted to owner-only by
+        // applyOwnerOnlyPermissions(), mitigating the default-permission risk.
+        @SuppressWarnings("java:S5443")
+        Path tempPath = Files.createTempDirectory("pdfbox-");
+        applyOwnerOnlyPermissions(tempPath);
+
+        // use shutdown hook instead of deleteOnExit() to ensure deletion
+        // of the entire directory in case of not automatically deleted on 
+        // JVM exit (e.g. due to open file handles or when the temp directory 
is not empty)
+        Runtime.getRuntime().addShutdownHook(new Thread(() ->
+        {
+            try (Stream<Path> entries = Files.walk(tempPath))
+            {
+                entries.sorted(Comparator.reverseOrder())
+                    .forEach(p -> p.toFile().delete());
+            }
+            catch (IOException ignored) {}
+        }));
+
+        return tempPath;
+    }
+
+    private static void applyOwnerOnlyPermissions(Path dir) throws IOException
+    {
+        if 
(FileSystems.getDefault().supportedFileAttributeViews().contains("posix"))
+        {
+            // Unix/macOS — rwx------
+            Files.setPosixFilePermissions(dir, 
PosixFilePermissions.fromString("rwx------"));
+        }
+        else
+        {
+            // Windows — replace the entire ACL with a single owner-only ALLOW 
entry
+            AclFileAttributeView aclView =
+            Files.getFileAttributeView(dir, AclFileAttributeView.class);
+
+            if (aclView == null)
+            {
+                File tempDir = dir.toFile();
+                tempDir.setReadable(true, true);
+                tempDir.setWritable(true, true);
+                tempDir.setExecutable(true, true);
+                return;
+            }
+
+            UserPrincipal owner = aclView.getOwner();
+
+            AclEntry ownerFullControl = AclEntry.newBuilder()
+                .setType(AclEntryType.ALLOW)
+                .setPrincipal(owner)
+                .setPermissions(
+                    AclEntryPermission.READ_DATA,
+                    AclEntryPermission.WRITE_DATA,
+                    AclEntryPermission.APPEND_DATA,
+                    AclEntryPermission.READ_NAMED_ATTRS,
+                    AclEntryPermission.WRITE_NAMED_ATTRS,
+                    AclEntryPermission.EXECUTE,
+                    AclEntryPermission.DELETE_CHILD,
+                    AclEntryPermission.READ_ATTRIBUTES,
+                    AclEntryPermission.WRITE_ATTRIBUTES,
+                    AclEntryPermission.DELETE,
+                    AclEntryPermission.READ_ACL,
+                    AclEntryPermission.WRITE_ACL,
+                    AclEntryPermission.SYNCHRONIZE
+                )
+                .build();
+
+            // Set so that only the owner has permissions, and remove any 
inherited ACL entries
+            aclView.setAcl(Collections.singletonList(ownerFullControl));
+        }
+    }
 }

Reply via email to