You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2025/09/22 13:21:01 UTC
(commons-io) branch master updated: feat: replace IAE with IOException on byte array overflow (#785)
This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git
The following commit(s) were added to refs/heads/master by this push:
new fb917b5e6 feat: replace IAE with IOException on byte array overflow (#785)
fb917b5e6 is described below
commit fb917b5e6f090e9a6326615d6d826b18f65a4d54
Author: Piotr P. Karwasz <pk...@apache.org>
AuthorDate: Mon Sep 22 15:20:55 2025 +0200
feat: replace IAE with IOException on byte array overflow (#785)
The method `IOUtils#toByteArray(InputStream)` previously threw an `IllegalArgumentException` when the number of bytes read exceeded what could be stored in a `byte[]`.
This change updates the method to throw an `IOException` instead, which is more semantically appropriate for stream-reading failures. A dedicated test has been added to verify this behavior.
---
src/changes/changes.xml | 1 +
src/main/java/org/apache/commons/io/IOUtils.java | 7 +++----
.../java/org/apache/commons/io/IOUtilsTest.java | 24 ++++++++++++++++++++++
3 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index daa2c4de6..3fa2e390d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -53,6 +53,7 @@ The <action> type attribute can be add,update,fix,remove.
<action type="fix" dev="pkarwasz" due-to="Piotr P. Karwasz">BoundedInputStream.getRemaining() now reports Long.MAX_VALUE instead of 0 when no limit is set.</action>
<action type="fix" dev="pkarwasz" due-to="Piotr P. Karwasz">BoundedInputStream.available() correctly accounts for the maximum read limit.</action>
<action type="fix" dev="ggregory" due-to="Gary Gregory, Piotr P. Karwasz">Deprecate IOUtils.readFully(InputStream, int) in favor of toByteArray(InputStream, int).</action>
+ <action type="fix" dev="pkarwasz" due-to="Piotr P. Karwasz">IOUtils.toByteArray(InputStream) now throws IOException on byte array overflow.</action>
<!-- ADD -->
<action dev="ggregory" type="add" due-to="strangelookingnerd, Gary Gregory">FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte, Ronnabyte and Quettabyte #763.</action>
<action dev="ggregory" type="add" due-to="strangelookingnerd, Gary Gregory">Add org.apache.commons.io.FileUtils.ONE_RB #763.</action>
diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java
index eae4cd78b..19d498cdc 100644
--- a/src/main/java/org/apache/commons/io/IOUtils.java
+++ b/src/main/java/org/apache/commons/io/IOUtils.java
@@ -1645,7 +1645,7 @@ public static long copyLarge(final Reader reader, final Writer writer, final lon
* @param bufferSize The buffer size of the output stream; must be {@code > 0}.
* @return a ByteArrayOutputStream containing the read bytes.
*/
- private static UnsynchronizedByteArrayOutputStream copyToOutputStream(
+ static UnsynchronizedByteArrayOutputStream copyToOutputStream(
final InputStream input, final long limit, final int bufferSize) throws IOException {
try (UnsynchronizedByteArrayOutputStream output = UnsynchronizedByteArrayOutputStream.builder()
.setBufferSize(bufferSize)
@@ -2680,15 +2680,14 @@ public static BufferedReader toBufferedReader(final Reader reader, final int siz
*
* @param inputStream The {@link InputStream} to read; must not be {@code null}.
* @return A new byte array.
- * @throws IllegalArgumentException If the size of the stream is greater than the maximum array size.
- * @throws IOException If an I/O error occurs while reading.
+ * @throws IOException If an I/O error occurs while reading or if the maximum array size is exceeded.
* @throws NullPointerException If {@code inputStream} is {@code null}.
*/
public static byte[] toByteArray(final InputStream inputStream) throws IOException {
// Using SOFT_MAX_ARRAY_LENGTH guarantees that size() will not overflow
final UnsynchronizedByteArrayOutputStream output = copyToOutputStream(inputStream, SOFT_MAX_ARRAY_LENGTH + 1, DEFAULT_BUFFER_SIZE);
if (output.size() > SOFT_MAX_ARRAY_LENGTH) {
- throw new IllegalArgumentException(String.format("Cannot read more than %,d into a byte array", SOFT_MAX_ARRAY_LENGTH));
+ throw new IOException(String.format("Cannot read more than %,d into a byte array", SOFT_MAX_ARRAY_LENGTH));
}
return output.toByteArray();
}
diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java
index 5f256d67e..ad397ed95 100644
--- a/src/test/java/org/apache/commons/io/IOUtilsTest.java
+++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java
@@ -27,6 +27,8 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -80,6 +82,7 @@
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.io.output.NullWriter;
import org.apache.commons.io.output.StringBuilderWriter;
+import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.apache.commons.io.test.TestUtils;
import org.apache.commons.io.test.ThrowOnCloseReader;
import org.apache.commons.lang3.StringUtils;
@@ -93,6 +96,8 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
/**
* This is used to test {@link IOUtils} for correctness. The following checks are performed:
@@ -1965,4 +1970,23 @@ void testWriteLittleString() throws IOException {
}
}
+ @Test
+ void testToByteArray_ThrowsIOExceptionOnHugeStream() {
+ try (MockedStatic<IOUtils> utils = Mockito.mockStatic(IOUtils.class, Mockito.CALLS_REAL_METHODS)) {
+ // Prepare the mocks
+ final UnsynchronizedByteArrayOutputStream mockOutputStream =
+ mock(UnsynchronizedByteArrayOutputStream.class);
+ utils.when(() -> IOUtils.copyToOutputStream(
+ Mockito.any(InputStream.class), Mockito.anyLong(), Mockito.anyInt()))
+ .thenReturn(mockOutputStream);
+ when(mockOutputStream.size()).thenReturn(IOUtils.SOFT_MAX_ARRAY_LENGTH + 1);
+
+ // Test and check
+ final InputStream mockInputStream = mock(InputStream.class);
+ final IOException exception = assertThrows(IOException.class, () -> IOUtils.toByteArray(mockInputStream));
+ assertTrue(
+ exception.getMessage().contains(String.format("%,d", IOUtils.SOFT_MAX_ARRAY_LENGTH)),
+ "Exception message does not contain the maximum length");
+ }
+ }
}