From 5fd31eefc44267ffb201c017ff9fe1e6c02c2f5a Mon Sep 17 00:00:00 2001 From: Kunal Das Date: Fri, 11 Sep 2026 00:29:54 +0530 Subject: [PATCH 1/2] Fix multipart header separators crossing part boundaries --- .../react/devsupport/MultipartStreamReader.kt | 2 +- .../devsupport/MultipartStreamReaderTest.kt | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.kt index f7d4d12d4b93..a49f2c98339e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.kt @@ -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)) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt index 1ef4499184aa..205c47840911 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt @@ -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 @@ -212,6 +214,42 @@ class MultipartStreamReaderTest { assertThat(callback.callCount).isEqualTo(1) } + @Test + 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() + val last = mutableListOf() + val success = + MultipartStreamReader(source, "sample") + .readAllParts( + object : CallCountTrackingChunkCallback() { + override fun onChunkComplete( + headers: Map, + 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 From b150c6f7dd718c5b4a320fdecb06f2049b87b5c0 Mon Sep 17 00:00:00 2001 From: Kunal Das Date: Fri, 11 Sep 2026 00:48:48 +0530 Subject: [PATCH 2/2] Allow the regression to compile with ReactAndroid legacy Okio --- .../com/facebook/react/devsupport/MultipartStreamReaderTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt index 205c47840911..8ede630a48a9 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt @@ -215,6 +215,7 @@ class MultipartStreamReaderTest { } @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"