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));
+ }
+ }
}