Skip to content

Commit 45578a7

Browse files
authored
Add missing read argument validation in IOUtils (#795)
* Add missing `read` argument validation in `IOUtils` In #790 I introduced `IOUtils#checkIndexFromLength` calls to validate arguments across the codebase. Ironically, the `IOUtils` class itself was left out. This PR addresses that omission by adding argument validation to `IOUtils#read` and `IOUtils#readFully`. Key points: * Ensures consistency with the rest of Commons IO by validating `offset` and `length`. * Fixes inconsistent exception behavior: * Previously, `length < 0` resulted in an `IllegalArgumentException`. * `offset < 0` did not trigger validation and failed later with an `IndexOutOfBoundsException`. * With this change, both invalid cases are handled consistently and upfront. * fix: failing tests
1 parent 07d2cd9 commit 45578a7

File tree

2 files changed

+55
-40
lines changed

2 files changed

+55
-40
lines changed

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

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2054,6 +2054,7 @@ public static LineIterator lineIterator(final Reader reader) {
20542054
* @param input where to read input from.
20552055
* @param buffer destination.
20562056
* @return actual length read; may be less than requested if EOF was reached.
2057+
* @throws NullPointerException if {@code input} or {@code buffer} is null.
20572058
* @throws IOException if a read error occurs.
20582059
* @since 2.2
20592060
*/
@@ -2074,40 +2075,19 @@ public static int read(final InputStream input, final byte[] buffer) throws IOEx
20742075
* @param offset initial offset into buffer.
20752076
* @param length length to read, must be &gt;= 0.
20762077
* @return actual length read; may be less than requested if EOF was reached.
2077-
* @throws IllegalArgumentException if length is negative.
2078+
* @throws NullPointerException if {@code input} or {@code buffer} is null.
2079+
* @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if
2080+
* {@code offset + length} is greater than {@code buffer.length}.
20782081
* @throws IOException if a read error occurs.
20792082
* @since 2.2
20802083
*/
20812084
public static int read(final InputStream input, final byte[] buffer, final int offset, final int length)
20822085
throws IOException {
2083-
if (length == 0) {
2084-
return 0;
2085-
}
2086-
return read(input::read, buffer, offset, length);
2087-
}
2088-
2089-
/**
2090-
* Reads bytes from an input. This implementation guarantees that it will read as many bytes as possible before giving up; this may not always be the case
2091-
* for subclasses of {@link InputStream}.
2092-
*
2093-
* @param input How to read input.
2094-
* @param buffer destination.
2095-
* @param offset initial offset into buffer.
2096-
* @param length length to read, must be &gt;= 0.
2097-
* @return actual length read; may be less than requested if EOF was reached.
2098-
* @throws IllegalArgumentException if length is negative.
2099-
* @throws IOException if a read error occurs.
2100-
* @since 2.2
2101-
*/
2102-
static int read(final IOTriFunction<byte[], Integer, Integer, Integer> input, final byte[] buffer, final int offset, final int length)
2103-
throws IOException {
2104-
if (length < 0) {
2105-
throw new IllegalArgumentException("Length must not be negative: " + length);
2106-
}
2086+
checkFromIndexSize(buffer, offset, length);
21072087
int remaining = length;
21082088
while (remaining > 0) {
21092089
final int location = length - remaining;
2110-
final int count = input.apply(buffer, offset + location, remaining);
2090+
final int count = input.read(buffer, offset + location, remaining);
21112091
if (EOF == count) {
21122092
break;
21132093
}
@@ -2172,15 +2152,15 @@ public static int read(final Reader reader, final char[] buffer) throws IOExcept
21722152
* @param offset initial offset into buffer.
21732153
* @param length length to read, must be &gt;= 0.
21742154
* @return actual length read; may be less than requested if EOF was reached.
2175-
* @throws IllegalArgumentException if length is negative.
2155+
* @throws NullPointerException if {@code reader} or {@code buffer} is null.
2156+
* @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if
2157+
* {@code offset + length} is greater than {@code buffer.length}.
21762158
* @throws IOException if a read error occurs.
21772159
* @since 2.2
21782160
*/
21792161
public static int read(final Reader reader, final char[] buffer, final int offset, final int length)
21802162
throws IOException {
2181-
if (length < 0) {
2182-
throw new IllegalArgumentException("Length must not be negative: " + length);
2183-
}
2163+
checkFromIndexSize(buffer, offset, length);
21842164
int remaining = length;
21852165
while (remaining > 0) {
21862166
final int location = length - remaining;
@@ -2202,9 +2182,9 @@ public static int read(final Reader reader, final char[] buffer, final int offse
22022182
*
22032183
* @param input where to read input from.
22042184
* @param buffer destination.
2205-
* @throws IOException if there is a problem reading the file.
2206-
* @throws IllegalArgumentException if length is negative.
2185+
* @throws NullPointerException if {@code input} or {@code buffer} is null.
22072186
* @throws EOFException if the number of bytes read was incorrect.
2187+
* @throws IOException if there is a problem reading the file.
22082188
* @since 2.2
22092189
*/
22102190
public static void readFully(final InputStream input, final byte[] buffer) throws IOException {
@@ -2222,9 +2202,11 @@ public static void readFully(final InputStream input, final byte[] buffer) throw
22222202
* @param buffer destination.
22232203
* @param offset initial offset into buffer.
22242204
* @param length length to read, must be &gt;= 0.
2225-
* @throws IOException if there is a problem reading the file.
2226-
* @throws IllegalArgumentException if length is negative.
2205+
* @throws NullPointerException if {@code input} or {@code buffer} is null.
2206+
* @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if
2207+
* {@code offset + length} is greater than {@code buffer.length}.
22272208
* @throws EOFException if the number of bytes read was incorrect.
2209+
* @throws IOException if there is a problem reading the file.
22282210
* @since 2.2
22292211
*/
22302212
public static void readFully(final InputStream input, final byte[] buffer, final int offset, final int length)
@@ -2286,9 +2268,9 @@ public static void readFully(final ReadableByteChannel input, final ByteBuffer b
22862268
*
22872269
* @param reader where to read input from.
22882270
* @param buffer destination.
2289-
* @throws IOException if there is a problem reading the file.
2290-
* @throws IllegalArgumentException if length is negative.
2271+
* @throws NullPointerException if {@code reader} or {@code buffer} is null.
22912272
* @throws EOFException if the number of characters read was incorrect.
2273+
* @throws IOException if there is a problem reading the file.
22922274
* @since 2.2
22932275
*/
22942276
public static void readFully(final Reader reader, final char[] buffer) throws IOException {
@@ -2306,9 +2288,11 @@ public static void readFully(final Reader reader, final char[] buffer) throws IO
23062288
* @param buffer destination.
23072289
* @param offset initial offset into buffer.
23082290
* @param length length to read, must be &gt;= 0.
2309-
* @throws IOException if there is a problem reading the file.
2310-
* @throws IllegalArgumentException if length is negative.
2291+
* @throws NullPointerException if {@code reader} or {@code buffer} is null.
2292+
* @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if
2293+
* {@code offset + length} is greater than {@code buffer.length}.
23112294
* @throws EOFException if the number of characters read was incorrect.
2295+
* @throws IOException if there is a problem reading the file.
23122296
* @since 2.2
23132297
*/
23142298
public static void readFully(final Reader reader, final char[] buffer, final int offset, final int length)

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,31 @@ void testRead_ReadableByteChannel() throws Exception {
11951195
}
11961196
}
11971197

1198+
static Stream<Arguments> invalidRead_InputStream_Offset_ArgumentsProvider() {
1199+
final InputStream input = new ByteArrayInputStream(new byte[10]);
1200+
final byte[] b = new byte[10];
1201+
return Stream.of(
1202+
// input is null
1203+
Arguments.of(null, b, 0, 1, NullPointerException.class),
1204+
// b is null
1205+
Arguments.of(input, null, 0, 1, NullPointerException.class),
1206+
// off is negative
1207+
Arguments.of(input, b, -1, 1, IndexOutOfBoundsException.class),
1208+
// len is negative
1209+
Arguments.of(input, b, 0, -1, IndexOutOfBoundsException.class),
1210+
// off + len is too big
1211+
Arguments.of(input, b, 1, 10, IndexOutOfBoundsException.class),
1212+
// off + len is too big
1213+
Arguments.of(input, b, 10, 1, IndexOutOfBoundsException.class)
1214+
);
1215+
}
1216+
1217+
@ParameterizedTest
1218+
@MethodSource("invalidRead_InputStream_Offset_ArgumentsProvider")
1219+
void testRead_InputStream_Offset_ArgumentsValidation(InputStream input, byte[] b, int off, int len, Class<? extends Throwable> expected) {
1220+
assertThrows(expected, () -> IOUtils.read(input, b, off, len));
1221+
}
1222+
11981223
@Test
11991224
void testReadFully_InputStream__ReturnByteArray() throws Exception {
12001225
final byte[] bytes = "abcd1234".getBytes(StandardCharsets.UTF_8);
@@ -1213,7 +1238,7 @@ void testReadFully_InputStream_ByteArray() throws Exception {
12131238
final byte[] buffer = new byte[size];
12141239
final InputStream input = new ByteArrayInputStream(new byte[size]);
12151240

1216-
assertThrows(IllegalArgumentException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IllegalArgumentException");
1241+
assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IndexOutOfBoundsException");
12171242

12181243
IOUtils.readFully(input, buffer, 0, 0);
12191244
IOUtils.readFully(input, buffer, 0, size - 1);
@@ -1260,7 +1285,7 @@ void testReadFully_Reader() throws Exception {
12601285

12611286
IOUtils.readFully(input, buffer, 0, 0);
12621287
IOUtils.readFully(input, buffer, 0, size - 3);
1263-
assertThrows(IllegalArgumentException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IllegalArgumentException");
1288+
assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IndexOutOfBoundsException");
12641289
assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer, 0, 5), "Should have failed with EOFException");
12651290
IOUtils.closeQuietly(input);
12661291
}
@@ -1274,6 +1299,12 @@ void testReadFully_Reader_Offset() throws Exception {
12741299
IOUtils.closeQuietly(reader);
12751300
}
12761301

1302+
@ParameterizedTest
1303+
@MethodSource("invalidRead_InputStream_Offset_ArgumentsProvider")
1304+
void testReadFully_InputStream_Offset_ArgumentsValidation(InputStream input, byte[] b, int off, int len, Class<? extends Throwable> expected) {
1305+
assertThrows(expected, () -> IOUtils.read(input, b, off, len));
1306+
}
1307+
12771308
@Test
12781309
void testReadLines_CharSequence() throws IOException {
12791310
final File file = TestUtils.newFile(temporaryFolder, "lines.txt");

0 commit comments

Comments
 (0)