Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ internal class MultipartStreamReader(
val marker: ByteString = ByteString.encodeUtf8(CRLF + CRLF)
val indexOfMarker = content.indexOf(marker, 0)

if (indexOfMarker == -1L || indexOfMarker >= chunkLength) {
if (indexOfMarker == -1L || indexOfMarker > chunkLength - marker.size()) {
// No headers marker found inside the chunk. Treat the entire chunk as body.
val bodyLength = chunkLength
val body = Okio.buffer(FixedLengthSource(content, bodyLength))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ package com.facebook.react.devsupport
import okio.Buffer
import okio.BufferedSource
import okio.ByteString
import okio.ForwardingSource
import okio.Okio
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

Expand Down Expand Up @@ -212,6 +214,43 @@ class MultipartStreamReaderTest {
assertThat(callback.callCount).isEqualTo(1)
}

@Test
@Suppress("DEPRECATION_ERROR") // Match the reader's compatibility with legacy Okio.
fun testDelimitersAcrossEveryReadBoundary() {
// The trailing CRLF must not become a header separator spanning the next boundary.
val body = "binary\u0000\r\n--samplX\r\n--sample-\r\n"
val response = "preamble\r\n--sample\r\n${body}\r\n--sample\r\nsecond\r\n--sample--\r\nepilogue"
for (readSize in 1..response.length) {
val upstream = Buffer().writeUtf8(response)
val source =
Okio.buffer(
object : ForwardingSource(upstream) {
override fun read(sink: Buffer, byteCount: Long): Long =
super.read(sink, minOf(byteCount, readSize.toLong()))
}
)
val parts = mutableListOf<String>()
val last = mutableListOf<Boolean>()
val success =
MultipartStreamReader(source, "sample")
.readAllParts(
object : CallCountTrackingChunkCallback() {
override fun onChunkComplete(
headers: Map<String, String>,
body: BufferedSource,
isLastChunk: Boolean,
) {
parts.add(body.readUtf8())
last.add(isLastChunk)
}
}
)
assertThat(success).describedAs("read size %s", readSize).isTrue()
assertThat(parts).containsExactly(body, "second")
assertThat(last).containsExactly(false, true)
}
}

internal open class CallCountTrackingChunkCallback : MultipartStreamReader.ChunkListener {
var callCount = 0
private set
Expand Down