A handful of code paths in client4 call ByteBuffer.array() directly. Two problems:
array() throws UnsupportedOperationException for direct buffers and ReadOnlyBufferException for read-only buffers. So a ByteBufferBody backed by ByteBuffer.allocateDirect(...) crashes inside forceBodyAsString/forceBodyAsByteArray and friends.
- Even on a heap buffer,
array() returns the entire backing storage and ignores position/limit. A partially-consumed buffer comes back wrong.
There's already a correct helper in Compressor.byteBufferToArray — it just isn't used in these spots.
Reproducer (no sttp dep needed for the JDK-level demo):
//> using scala 3.8.3
import java.nio.ByteBuffer
@main def run(): Unit =
// direct buffer -> .array() throws
val direct = ByteBuffer.allocateDirect(10)
direct.put("hello".getBytes); direct.flip()
try direct.array()
catch case e: UnsupportedOperationException => println(s"direct: ${e.getClass.getSimpleName}")
// partial heap buffer -> .array() returns wrong slice
val partial = ByteBuffer.wrap("ABCDE".getBytes)
partial.position(2); partial.limit(4) // remaining = "CD"
println(s"partial.array() = '${new String(partial.array())}'") // "ABCDE", expected "CD"
Output:
direct: UnsupportedOperationException
partial.array() = 'ABCDE'
Source — call sites that need hasArray + slice handling:
|
): Queue[Array[Byte]] = queue.enqueue(bytes.array()) |
|
case ByteBufferBody(b, _) => new String(b.array()) |
|
case ByteBufferBody(b, _) => b.array() |
|
case ByteBufferBody(b, _) => b.array() |
The standard pattern (mirrors what Compressor.byteBufferToArray does):
def toArray(b: ByteBuffer): Array[Byte] =
if b.hasArray && b.arrayOffset == 0 && b.position == 0 && b.remaining == b.array.length then
b.array
else
val out = new Array[Byte](b.remaining)
b.duplicate.get(out)
out
The DigestAuthenticator hit is the practical one — auth-int digest of a direct-buffer body throws instead of hashing. Happy to PR consolidating the call sites onto a single helper.
A handful of code paths in
client4callByteBuffer.array()directly. Two problems:array()throwsUnsupportedOperationExceptionfor direct buffers andReadOnlyBufferExceptionfor read-only buffers. So aByteBufferBodybacked byByteBuffer.allocateDirect(...)crashes insideforceBodyAsString/forceBodyAsByteArrayand friends.array()returns the entire backing storage and ignoresposition/limit. A partially-consumed buffer comes back wrong.There's already a correct helper in
Compressor.byteBufferToArray— it just isn't used in these spots.Reproducer (no sttp dep needed for the JDK-level demo):
Output:
Source — call sites that need
hasArray+ slice handling:sttp/core/src/main/scala/sttp/client4/internal/package.scala
Line 46 in 6b817f7
sttp/core/src/main/scala/sttp/client4/testing/package.scala
Line 16 in 6b817f7
sttp/core/src/main/scala/sttp/client4/testing/package.scala
Line 33 in 6b817f7
sttp/core/src/main/scala/sttp/client4/internal/DigestAuthenticator.scala
Line 168 in 6b817f7
The standard pattern (mirrors what
Compressor.byteBufferToArraydoes):The DigestAuthenticator hit is the practical one —
auth-intdigest of a direct-buffer body throws instead of hashing. Happy to PR consolidating the call sites onto a single helper.