Description

StreamUtils.copyRange copies the input stream with a buffer size of 4096 bytes. If the desired range is not divisible by 4096, copyRange will overread the input stream by reading whole buffers at a time. This will result in skipping the bytes in the input stream that were read into the buffer but not copied.

Consider the following kotlin code:

val a = ByteArrayInputStream(ByteArray(6000))
val b = ByteArrayOutputStream(6000)

val copied = StreamUtils.copyRange(a, b, 0, 5000) // copied == 5001
val rest = a.readAllBytes().size // rest == 0, should equal 999
val total = copied + rest // total == 5001, should equal 6000

Because the input stream spans over two buffers, copyRange will consume the whole stream even though the requested range was 1000 bytes smaller than the stream length.

Actual behavior

When the requested range is not divisible by 4096 and the input stream is longer than the range copyRange will consume extra bytes after the end of the range. This advances the stream unnecessarily and results in those bytes being skipped.

Expected behavior

StreamUtils.copyRange should only read the requested number of bytes from the input stream.

Suggested fix

The number of remaining bytes to be read should be taken into account instead of reading the whole buffer:

int bytesRead = in.read(buffer, 0, Math.min(buffer.size, numRemainingBytes));

Comment From: icguy

Wow, that was some response time! Thanks a lot for the fix!