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 38bdc5a8 Create temporary upload file with owner-only permissions
(#473).
38bdc5a8 is described below
commit 38bdc5a82cc44b1d301771f97dcee88e1127e363
Author: Gary Gregory <[email protected]>
AuthorDate: Sun Jun 21 11:58:21 2026 +0000
Create temporary upload file with owner-only permissions (#473).
- Javadoc
- Formatting
---
.../fileupload2/core/DeferrableOutputStream.java | 7 +-
.../core/DeferrableOutputStreamTest.java | 437 ++++++++++-----------
src/changes/changes.xml | 3 +-
3 files changed, 222 insertions(+), 225 deletions(-)
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 c75ad5d0..3c03b6bb 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
@@ -397,18 +397,15 @@ public class DeferrableOutputStream extends OutputStream {
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-------"))));
+ 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);
}
-
/**
- * At this point, the output file has been successfully created,
- * and we can safely switch state.
+ * At this point, the output file has been successfully created, and
we can safely switch state.
*/
state = State.persisted;
wasPersisted = true;
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 e0c5f9fc..cfed3396 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
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.commons.fileupload2.core;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@@ -43,236 +44,234 @@ import
org.apache.commons.fileupload2.core.DeferrableOutputStream.State;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-
/**
- * Test suite for the {@link DeferrableOutputStream}.
+ * Tests {@link DeferrableOutputStream}.
*/
class DeferrableOutputStreamTest {
- private static final Path testDir =
Paths.get("target/unit-tests/DeferrableOutputStreamTest");
- private static Path tempTestDir;
- @BeforeAll
- static void setUpTestDirs() throws IOException {
- Files.createDirectories(testDir);
- tempTestDir = Files.createTempDirectory(testDir, "testDir");
- }
-
- private Supplier<Path> testFileSupplier = () -> {
- try {
- return Files.createTempFile(tempTestDir, "testFile",
".bin");
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- };
- protected byte[] read(InputStream pIs) throws IOException {
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- final byte[] buffer = new byte[8192];
- for (;;) {
- final int res = pIs.read(buffer);
- if (res == -1) {
- return baos.toByteArray();
- } else if (res > 0) {
- baos.write(buffer, 0, res);
- }
- }
- }
+ private static final Path testDir =
Paths.get("target/unit-tests/DeferrableOutputStreamTest");
- /**
- * Tests using the {@link DeferrableOutputStream} with a positive
threshold.
- */
- @Test
- void testExceedPositiveThreshold() {
- DeferrableOutputStream[] streams = new
DeferrableOutputStream[1];
- final Consumer<Consumer<OutputStream>> tester = (consumer) -> {
- try (final DeferrableOutputStream dos = new
DeferrableOutputStream(5, testFileSupplier, null)) {
- streams[0] = dos;
- assertTrue(dos.isInMemory());
- assertNull(dos.getPath());
- assertNull(dos.getBytes());
- assertSame(State.initialized, dos.getState());
- for (int i = 0; i < 4; i++) {
- try {
- dos.write('.');
- } catch (IOException ioe) {
- throw new
UncheckedIOException(ioe);
- }
- assertSame(State.opened,
dos.getState());
- assertTrue(dos.isInMemory());
- assertNull(dos.getPath());
- assertNull(dos.getBytes());
- }
- consumer.accept(dos);
- assertFalse(dos.isInMemory());
- assertNotNull(dos.getPath());
- assertNull(dos.getBytes());
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
+ private static Path tempTestDir;
- final DeferrableOutputStream dos = streams[0];
- assertFalse(dos.isInMemory());
- assertNotNull(dos.getPath());
- assertTrue(Files.isRegularFile(dos.getPath()));
- final byte[] actual;
- try (InputStream is = dos.getInputStream()) {
- actual = read(is);
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- final byte[] expect =
"....,".getBytes(StandardCharsets.UTF_8);
- assertArrayEquals(expect, actual);
- };
+ @BeforeAll
+ static void setUpTestDirs() throws IOException {
+ Files.createDirectories(testDir);
+ tempTestDir = Files.createTempDirectory(testDir, "testDir");
+ }
- // Break the threshold using OutputStream.write(int);
- tester.accept((os) -> {
- try {
- os.write(',');
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- });
- // Break the threshold using OutputStream.write(byte[]);
- tester.accept((os) -> {
- final byte[] buffer = new byte[] {','};
- try {
- os.write(buffer);
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- });
- // Break the threshold using OutputStream.write(byte[], int,
int);
- tester.accept((os) -> {
- final byte[] buffer = new byte[] {',', '-'};
- try {
- os.write(buffer, 0, 1);
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- });
- }
+ private Supplier<Path> testFileSupplier = () -> {
+ try {
+ return Files.createTempFile(tempTestDir, "testFile", ".bin");
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ };
- /**
- * Tests using the {@link DeferrableOutputStream} with threshold -1.
- */
- @Test
- void testThresholdMinusOne() {
- DeferrableOutputStream[] streams = new
DeferrableOutputStream[1];
- final Runnable tester = () -> {
- try (final DeferrableOutputStream dos = new
DeferrableOutputStream(-1, testFileSupplier, null)) {
- streams[0] = dos;
- assertFalse(dos.isInMemory());
- assertNotNull(dos.getPath());
- assertNull(dos.getBytes());
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
+ protected byte[] read(InputStream pIs) throws IOException {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final byte[] buffer = new byte[8192];
+ for (;;) {
+ final int res = pIs.read(buffer);
+ if (res == -1) {
+ return baos.toByteArray();
+ } else if (res > 0) {
+ baos.write(buffer, 0, res);
+ }
+ }
+ }
- final DeferrableOutputStream dos = streams[0];
- assertFalse(dos.isInMemory());
- assertNotNull(dos.getPath());
- assertTrue(Files.isRegularFile(dos.getPath()));
- final byte[] actual;
- try (InputStream is = dos.getInputStream()) {
- actual = read(is);
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- final byte[] expect =
"".getBytes(StandardCharsets.UTF_8);
- assertArrayEquals(expect, actual);
- };
- tester.run();
- }
+ /**
+ * Tests using the {@link DeferrableOutputStream} with a positive
threshold.
+ */
+ @Test
+ void testExceedPositiveThreshold() {
+ DeferrableOutputStream[] streams = new DeferrableOutputStream[1];
+ final Consumer<Consumer<OutputStream>> tester = (consumer) -> {
+ try (final DeferrableOutputStream dos = new
DeferrableOutputStream(5, testFileSupplier, null)) {
+ streams[0] = dos;
+ assertTrue(dos.isInMemory());
+ assertNull(dos.getPath());
+ assertNull(dos.getBytes());
+ assertSame(State.initialized, dos.getState());
+ for (int i = 0; i < 4; i++) {
+ try {
+ dos.write('.');
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ assertSame(State.opened, dos.getState());
+ assertTrue(dos.isInMemory());
+ assertNull(dos.getPath());
+ assertNull(dos.getBytes());
+ }
+ consumer.accept(dos);
+ assertFalse(dos.isInMemory());
+ assertNotNull(dos.getPath());
+ assertNull(dos.getBytes());
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ final DeferrableOutputStream dos = streams[0];
+ assertFalse(dos.isInMemory());
+ assertNotNull(dos.getPath());
+ assertTrue(Files.isRegularFile(dos.getPath()));
+ final byte[] actual;
+ try (InputStream is = dos.getInputStream()) {
+ actual = read(is);
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ final byte[] expect = "....,".getBytes(StandardCharsets.UTF_8);
+ assertArrayEquals(expect, actual);
+ };
+ // Break the threshold using OutputStream.write(int);
+ tester.accept((os) -> {
+ try {
+ os.write(',');
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ });
+ // Break the threshold using OutputStream.write(byte[]);
+ tester.accept((os) -> {
+ final byte[] buffer = new byte[] { ',' };
+ try {
+ os.write(buffer);
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ });
+ // Break the threshold using OutputStream.write(byte[], int, int);
+ tester.accept((os) -> {
+ final byte[] buffer = new byte[] { ',', '-' };
+ try {
+ os.write(buffer, 0, 1);
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ });
+ }
- /**
- * Tests using the {@link DeferrableOutputStream} with threshold 0.
- */
- @Test
- void testThresholdZero() {
- DeferrableOutputStream[] streams = new
DeferrableOutputStream[1];
- final Consumer<Consumer<OutputStream>> tester = (consumer) -> {
- try (final DeferrableOutputStream dos = new
DeferrableOutputStream(0, testFileSupplier, null)) {
- streams[0] = dos;
- assertTrue(dos.isInMemory());
- assertNull(dos.getPath());
- assertNull(dos.getBytes());
- assertSame(State.initialized, dos.getState());
- consumer.accept(dos);
- assertFalse(dos.isInMemory());
- assertNotNull(dos.getPath());
- assertNull(dos.getBytes());
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
+ /**
+ * 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)));
+ }
- final DeferrableOutputStream dos = streams[0];
- assertFalse(dos.isInMemory());
- assertNotNull(dos.getPath());
- assertTrue(Files.isRegularFile(dos.getPath()));
- final byte[] actual;
- try (InputStream is = dos.getInputStream()) {
- actual = read(is);
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- final byte[] expect =
",".getBytes(StandardCharsets.UTF_8);
- assertArrayEquals(expect, actual);
- };
- // Break the threshold using OutputStream.write(int);
- tester.accept((os) -> {
- try {
- os.write(',');
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- });
- // Break the threshold using OutputStream.write(byte[]);
- tester.accept((os) -> {
- final byte[] buffer = new byte[] {','};
- try {
- os.write(buffer);
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- });
- // Break the threshold using OutputStream.write(byte[], int,
int);
- tester.accept((os) -> {
- final byte[] buffer = new byte[] {',', '-'};
- try {
- os.write(buffer, 0, 1);
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- });
- }
+ /**
+ * 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));
+ }
- /**
- * 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 using the {@link DeferrableOutputStream} with threshold -1.
+ */
+ @Test
+ void testThresholdMinusOne() {
+ DeferrableOutputStream[] streams = new DeferrableOutputStream[1];
+ final Runnable tester = () -> {
+ try (final DeferrableOutputStream dos = new
DeferrableOutputStream(-1, testFileSupplier, null)) {
+ streams[0] = dos;
+ assertFalse(dos.isInMemory());
+ assertNotNull(dos.getPath());
+ assertNull(dos.getBytes());
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ final DeferrableOutputStream dos = streams[0];
+ assertFalse(dos.isInMemory());
+ assertNotNull(dos.getPath());
+ assertTrue(Files.isRegularFile(dos.getPath()));
+ final byte[] actual;
+ try (InputStream is = dos.getInputStream()) {
+ actual = read(is);
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ final byte[] expect = "".getBytes(StandardCharsets.UTF_8);
+ assertArrayEquals(expect, actual);
+ };
+ tester.run();
+ }
- /**
- * 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));
- }
+ /**
+ * Tests using the {@link DeferrableOutputStream} with threshold 0.
+ */
+ @Test
+ void testThresholdZero() {
+ DeferrableOutputStream[] streams = new DeferrableOutputStream[1];
+ final Consumer<Consumer<OutputStream>> tester = (consumer) -> {
+ try (final DeferrableOutputStream dos = new
DeferrableOutputStream(0, testFileSupplier, null)) {
+ streams[0] = dos;
+ assertTrue(dos.isInMemory());
+ assertNull(dos.getPath());
+ assertNull(dos.getBytes());
+ assertSame(State.initialized, dos.getState());
+ consumer.accept(dos);
+ assertFalse(dos.isInMemory());
+ assertNotNull(dos.getPath());
+ assertNull(dos.getBytes());
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ final DeferrableOutputStream dos = streams[0];
+ assertFalse(dos.isInMemory());
+ assertNotNull(dos.getPath());
+ assertTrue(Files.isRegularFile(dos.getPath()));
+ final byte[] actual;
+ try (InputStream is = dos.getInputStream()) {
+ actual = read(is);
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ final byte[] expect = ",".getBytes(StandardCharsets.UTF_8);
+ assertArrayEquals(expect, actual);
+ };
+ // Break the threshold using OutputStream.write(int);
+ tester.accept((os) -> {
+ try {
+ os.write(',');
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ });
+ // Break the threshold using OutputStream.write(byte[]);
+ tester.accept((os) -> {
+ final byte[] buffer = new byte[] { ',' };
+ try {
+ os.write(buffer);
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ });
+ // Break the threshold using OutputStream.write(byte[], int, int);
+ tester.accept((os) -> {
+ final byte[] buffer = new byte[] { ',', '-' };
+ try {
+ os.write(buffer, 0, 1);
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ });
+ }
}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 71634323..a080f290 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -46,7 +46,8 @@ The <action> type attribute can be add,update,fix,remove.
<action type="fix" dev="ggregory" due-to="Henri
Biestro, Gary Gregory">Fix migration documentation to mention Java 11.</action>
<action issue="FILEUPLOAD-362" type="fix" dev="ggregory" due-to="Kusal
Kithul-Godage, Gary Gregory">Unable to parse requests for file uploads with
special characters in filename on Windows.</action>
<action type="fix" dev="markt" due-to="Mark
Thomas">Implement stricter checks for fields using RFC 2231 / RFC 5987. Invalid
extended values will be ignored.</action>
- <action type="fix" dev="ggregory" due-to="Chenjp,
Gary Gregory">RFC2231Utils decodeText - Incorrect handling of percent encoded
text (#472).</action>
+ <action type="fix" dev="ggregory" due-to="Chenjp,
Gary Gregory">RFC2231Utils decodeText: Incorrect handling of percent encoded
text (#472).</action>
+ <action type="fix" dev="ggregory" due-to="alhuda,
Gary Gregory">Create temporary upload file with owner-only permissions
(#473).</action>
<!-- ADD -->
<!-- UPDATE -->
<action type="update" dev="ggregory" due-to="Gary Gregory">Bump
org.apache.commons:commons-parent from 96 to 102.</action>