Skip to content

Commit 435c54c

Browse files
authored
Prevent extra data requests on zero-length reads (helidon-io#11434)
* Prevent extra data requests on zero-length reads * Validate zero-length read arguments
1 parent e7515a2 commit 435c54c

2 files changed

Lines changed: 35 additions & 0 deletions

File tree

http/media/media/src/main/java/io/helidon/http/media/ReadableEntityBase.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.IOException;
2222
import java.io.InputStream;
2323
import java.io.UncheckedIOException;
24+
import java.util.Objects;
2425
import java.util.Optional;
2526
import java.util.concurrent.atomic.AtomicBoolean;
2627
import java.util.function.Consumer;
@@ -301,6 +302,10 @@ public void close() throws IOException {
301302

302303
@Override
303304
public int read(byte[] b, int off, int len) {
305+
Objects.checkFromIndexSize(off, len, b.length);
306+
if (len == 0) {
307+
return 0;
308+
}
304309
if (finished) {
305310
return -1;
306311
}

http/media/media/src/test/java/io/helidon/http/media/ReadableEntityBaseTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import java.io.ByteArrayOutputStream;
1919
import java.io.IOException;
2020
import java.io.InputStream;
21+
import java.nio.charset.StandardCharsets;
2122
import java.util.Arrays;
23+
import java.util.concurrent.atomic.AtomicInteger;
2224
import java.util.function.Function;
2325

2426
import io.helidon.common.GenericType;
@@ -93,6 +95,34 @@ void testMaxBufferedEntityLength() {
9395
assertThat(e2.getMessage(), is("Entity has already been requested. Entity cannot be requested multiple times"));
9496
}
9597

98+
@Test
99+
void testZeroLengthReadDoesNotRequestMoreData() throws IOException {
100+
byte[] payload = "ping".getBytes(StandardCharsets.UTF_8);
101+
AtomicInteger reads = new AtomicInteger();
102+
ReadableEntityBase entityBase = new ReadableEntityImpl(estimate -> {
103+
if (reads.getAndIncrement() == 0) {
104+
return BufferData.create(payload);
105+
}
106+
return BufferData.empty();
107+
}, 1024);
108+
109+
InputStream inputStream = entityBase.inputStream();
110+
assertThat(Arrays.equals(inputStream.readNBytes(payload.length), payload), is(true));
111+
assertThat(inputStream.read(new byte[0]), is(0));
112+
assertThat(reads.get(), is(1));
113+
inputStream.close();
114+
}
115+
116+
@Test
117+
void testZeroLengthReadStillValidatesArguments() throws IOException {
118+
try (InputStream inputStream = new ReadableEntityImpl(new Readable(), 1024).inputStream()) {
119+
assertThrows(NullPointerException.class, () -> inputStream.read(null, 0, 0));
120+
}
121+
try (InputStream inputStream = new ReadableEntityImpl(new Readable(), 1024).inputStream()) {
122+
assertThrows(IndexOutOfBoundsException.class, () -> inputStream.read(new byte[1], 2, 0));
123+
}
124+
}
125+
96126
static class Readable implements Function<Integer, BufferData> {
97127
private boolean done;
98128

0 commit comments

Comments
 (0)