This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-fileupload.git
The following commit(s) were added to refs/heads/master by this push:
new 571f4aa7 Create temporary upload file with owner-only permissions
(#473)
571f4aa7 is described below
commit 571f4aa7093cabba167e446ccb85a394105addc5
Author: alhuda <[email protected]>
AuthorDate: Sun Jun 21 17:23:40 2026 +0530
Create temporary upload file with owner-only permissions (#473)
* create temporary upload file with owner-only permissions
* Add test for owner-only temporary upload file permissions
Open the persisted temporary file via a byte channel with the posix
permission attribute so the rw------- mode is applied atomically at
creation, and add a test asserting the mode on POSIX file systems.
* Use identical open options on both file-system paths in persist()
Share one CREATE/TRUNCATE_EXISTING/WRITE option set across the posix and
non-posix branches so both truncate an existing target, matching the prior
Files.newOutputStream behavior. Add a test that persisting over an existing
file overwrites it.
---
.../fileupload2/core/DeferrableOutputStream.java | 15 +++++++++-
.../core/DeferrableOutputStreamTest.java | 35 ++++++++++++++++++++++
2 files changed, 49 insertions(+), 1 deletion(-)
diff --git
a/commons-fileupload2-core/src/main/java/org/apache/commons/fileupload2/core/DeferrableOutputStream.java
b/commons-fileupload2-core/src/main/java/org/apache/commons/fileupload2/core/DeferrableOutputStream.java
index a87625fc..c75ad5d0 100644
---
a/commons-fileupload2-core/src/main/java/org/apache/commons/fileupload2/core/DeferrableOutputStream.java
+++
b/commons-fileupload2-core/src/main/java/org/apache/commons/fileupload2/core/DeferrableOutputStream.java
@@ -21,8 +21,12 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.channels.Channels;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.EnumSet;
import java.util.function.Supplier;
/**
@@ -388,7 +392,16 @@ public class DeferrableOutputStream extends OutputStream {
if (dir != null) {
Files.createDirectories(dir);
}
- final OutputStream os = Files.newOutputStream(p);
+ // Restrict the temporary file to its owner where the file system
supports it. The default repository is the shared system temporary directory, so
+ // creating the file with default permissions would expose the
uploaded data to other local users.
+ final EnumSet<StandardOpenOption> options =
EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE);
+ final OutputStream os;
+ if (p.getFileSystem().supportedFileAttributeViews().contains("posix"))
{
+ os = Channels.newOutputStream(Files.newByteChannel(p, options,
+
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-------"))));
+ } else {
+ os = Channels.newOutputStream(Files.newByteChannel(p, options));
+ }
if (baos != null) {
baos.writeTo(os);
}
diff --git
a/commons-fileupload2-core/src/test/java/org/apache/commons/fileupload2/core/DeferrableOutputStreamTest.java
b/commons-fileupload2-core/src/test/java/org/apache/commons/fileupload2/core/DeferrableOutputStreamTest.java
index 0d457b20..e0c5f9fc 100644
---
a/commons-fileupload2-core/src/test/java/org/apache/commons/fileupload2/core/DeferrableOutputStreamTest.java
+++
b/commons-fileupload2-core/src/test/java/org/apache/commons/fileupload2/core/DeferrableOutputStreamTest.java
@@ -20,8 +20,10 @@ import static
org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -29,9 +31,11 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermissions;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -240,4 +244,35 @@ class DeferrableOutputStreamTest {
}
});
}
+
+ /**
+ * Tests that the temporary file is created with owner-only permissions
on POSIX file systems,
+ * so that uploaded data in the shared system temporary directory is
not readable by other users.
+ */
+ @Test
+ void testPersistedFileIsOwnerOnlyOnPosix() throws IOException {
+
assumeTrue(FileSystems.getDefault().supportedFileAttributeViews().contains("posix"));
+ // Mirror production, where the supplier only resolves a
not-yet-existing path.
+ final Path target = tempTestDir.resolve("posixPerms.bin");
+ try (final DeferrableOutputStream dos = new
DeferrableOutputStream(-1, () -> target, null)) {
+ dos.write("secret".getBytes(StandardCharsets.UTF_8));
+ }
+ assertTrue(Files.isRegularFile(target));
+ assertEquals("rw-------",
PosixFilePermissions.toString(Files.getPosixFilePermissions(target)));
+ }
+
+ /**
+ * Tests that persisting over an already existing path truncates and
overwrites it, matching the prior
+ * {@link Files#newOutputStream} behavior. Guards against a regression
if {@code TRUNCATE_EXISTING} is dropped.
+ */
+ @Test
+ void testPersistedFileOverwritesExisting() throws IOException {
+ final Path target = tempTestDir.resolve("overwrite.bin");
+ Files.write(target,
"stale-and-longer".getBytes(StandardCharsets.UTF_8));
+ try (final DeferrableOutputStream dos = new
DeferrableOutputStream(-1, () -> target, null)) {
+ dos.write("new".getBytes(StandardCharsets.UTF_8));
+ }
+ assertTrue(Files.isRegularFile(target));
+ assertArrayEquals("new".getBytes(StandardCharsets.UTF_8),
Files.readAllBytes(target));
+ }
}