Skip to content

Commit fb917b5

Browse files
authored
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.
1 parent a96ae74 commit fb917b5

File tree

3 files changed

+28
-4
lines changed

3 files changed

+28
-4
lines changed

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ The <action> type attribute can be add,update,fix,remove.
5353
<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>
5454
<action type="fix" dev="pkarwasz" due-to="Piotr P. Karwasz">BoundedInputStream.available() correctly accounts for the maximum read limit.</action>
5555
<action type="fix" dev="ggregory" due-to="Gary Gregory, Piotr P. Karwasz">Deprecate IOUtils.readFully(InputStream, int) in favor of toByteArray(InputStream, int).</action>
56+
<action type="fix" dev="pkarwasz" due-to="Piotr P. Karwasz">IOUtils.toByteArray(InputStream) now throws IOException on byte array overflow.</action>
5657
<!-- ADD -->
5758
<action dev="ggregory" type="add" due-to="strangelookingnerd, Gary Gregory">FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte, Ronnabyte and Quettabyte #763.</action>
5859
<action dev="ggregory" type="add" due-to="strangelookingnerd, Gary Gregory">Add org.apache.commons.io.FileUtils.ONE_RB #763.</action>

src/main/java/org/apache/commons/io/IOUtils.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,7 +1645,7 @@ public static long copyLarge(final Reader reader, final Writer writer, final lon
16451645
* @param bufferSize The buffer size of the output stream; must be {@code > 0}.
16461646
* @return a ByteArrayOutputStream containing the read bytes.
16471647
*/
1648-
private static UnsynchronizedByteArrayOutputStream copyToOutputStream(
1648+
static UnsynchronizedByteArrayOutputStream copyToOutputStream(
16491649
final InputStream input, final long limit, final int bufferSize) throws IOException {
16501650
try (UnsynchronizedByteArrayOutputStream output = UnsynchronizedByteArrayOutputStream.builder()
16511651
.setBufferSize(bufferSize)
@@ -2680,15 +2680,14 @@ public static BufferedReader toBufferedReader(final Reader reader, final int siz
26802680
*
26812681
* @param inputStream The {@link InputStream} to read; must not be {@code null}.
26822682
* @return A new byte array.
2683-
* @throws IllegalArgumentException If the size of the stream is greater than the maximum array size.
2684-
* @throws IOException If an I/O error occurs while reading.
2683+
* @throws IOException If an I/O error occurs while reading or if the maximum array size is exceeded.
26852684
* @throws NullPointerException If {@code inputStream} is {@code null}.
26862685
*/
26872686
public static byte[] toByteArray(final InputStream inputStream) throws IOException {
26882687
// Using SOFT_MAX_ARRAY_LENGTH guarantees that size() will not overflow
26892688
final UnsynchronizedByteArrayOutputStream output = copyToOutputStream(inputStream, SOFT_MAX_ARRAY_LENGTH + 1, DEFAULT_BUFFER_SIZE);
26902689
if (output.size() > SOFT_MAX_ARRAY_LENGTH) {
2691-
throw new IllegalArgumentException(String.format("Cannot read more than %,d into a byte array", SOFT_MAX_ARRAY_LENGTH));
2690+
throw new IOException(String.format("Cannot read more than %,d into a byte array", SOFT_MAX_ARRAY_LENGTH));
26922691
}
26932692
return output.toByteArray();
26942693
}

src/test/java/org/apache/commons/io/IOUtilsTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import static org.junit.jupiter.api.Assertions.assertThrows;
2828
import static org.junit.jupiter.api.Assertions.assertTrue;
2929
import static org.junit.jupiter.api.Assertions.fail;
30+
import static org.mockito.Mockito.mock;
31+
import static org.mockito.Mockito.when;
3032

3133
import java.io.BufferedInputStream;
3234
import java.io.BufferedOutputStream;
@@ -80,6 +82,7 @@
8082
import org.apache.commons.io.output.NullOutputStream;
8183
import org.apache.commons.io.output.NullWriter;
8284
import org.apache.commons.io.output.StringBuilderWriter;
85+
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
8386
import org.apache.commons.io.test.TestUtils;
8487
import org.apache.commons.io.test.ThrowOnCloseReader;
8588
import org.apache.commons.lang3.StringUtils;
@@ -93,6 +96,8 @@
9396
import org.junit.jupiter.params.ParameterizedTest;
9497
import org.junit.jupiter.params.provider.Arguments;
9598
import org.junit.jupiter.params.provider.MethodSource;
99+
import org.mockito.MockedStatic;
100+
import org.mockito.Mockito;
96101

97102
/**
98103
* This is used to test {@link IOUtils} for correctness. The following checks are performed:
@@ -1965,4 +1970,23 @@ void testWriteLittleString() throws IOException {
19651970
}
19661971
}
19671972

1973+
@Test
1974+
void testToByteArray_ThrowsIOExceptionOnHugeStream() {
1975+
try (MockedStatic<IOUtils> utils = Mockito.mockStatic(IOUtils.class, Mockito.CALLS_REAL_METHODS)) {
1976+
// Prepare the mocks
1977+
final UnsynchronizedByteArrayOutputStream mockOutputStream =
1978+
mock(UnsynchronizedByteArrayOutputStream.class);
1979+
utils.when(() -> IOUtils.copyToOutputStream(
1980+
Mockito.any(InputStream.class), Mockito.anyLong(), Mockito.anyInt()))
1981+
.thenReturn(mockOutputStream);
1982+
when(mockOutputStream.size()).thenReturn(IOUtils.SOFT_MAX_ARRAY_LENGTH + 1);
1983+
1984+
// Test and check
1985+
final InputStream mockInputStream = mock(InputStream.class);
1986+
final IOException exception = assertThrows(IOException.class, () -> IOUtils.toByteArray(mockInputStream));
1987+
assertTrue(
1988+
exception.getMessage().contains(String.format("%,d", IOUtils.SOFT_MAX_ARRAY_LENGTH)),
1989+
"Exception message does not contain the maximum length");
1990+
}
1991+
}
19681992
}

0 commit comments

Comments
 (0)