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