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 @@ -260,15 +260,16 @@ internal object NetworkEventUtil {
val body = (requestBody as? ProgressRequestBody)?.innerBody() ?: requestBody

if (body.isOneShot()) {
// Fallback - body cannot be read twice
return "[Preview unavailable]"
// Reading would drain the underlying stream and break the real upload,
// so fall back to a placeholder that includes the byte count when known
return binaryPartLabel(body)
}

// MultipartBody does not propagate isOneShot() from its parts, so check each
// part explicitly. Reading a one-shot part here would drain the underlying
// stream and cause the real request to fail.
if (body is MultipartBody && body.parts().any { it.body().isOneShot() }) {
return "[Preview unavailable]"
return previewMultipartWithBinaryParts(body)
}

return try {
Expand All @@ -285,4 +286,53 @@ internal object NetworkEventUtil {
"[Preview unavailable]"
}
}

private fun previewMultipartWithBinaryParts(body: MultipartBody): String {
val boundary = body.boundary()
val out = StringBuilder()

for (part in body.parts()) {
out.append("--").append(boundary).append("\r\n")

part.headers()?.let { headers ->
for (i in 0 until headers.size()) {
out.append(headers.name(i)).append(": ").append(headers.value(i)).append("\r\n")
}
}
val partBody = part.body()
partBody.contentType()?.let { out.append("Content-Type: ").append(it).append("\r\n") }
out.append("\r\n")

if (partBody.isOneShot()) {
out.append(binaryPartLabel(partBody))
} else {
try {
val partBuffer = Buffer()
partBody.writeTo(partBuffer)
out.append(partBuffer.readUtf8())
} catch (e: IOException) {
out.append("[Preview unavailable]")
}
}
out.append("\r\n")
}
out.append("--").append(boundary).append("--\r\n")

return if (out.length <= MAX_BODY_PREVIEW_SIZE) {
out.toString()
} else {
out.substring(0, MAX_BODY_PREVIEW_SIZE) + "... (truncated, ${out.length} bytes total)"
}
}

/** Placeholder for a one-shot body, including the byte count when known. */
private fun binaryPartLabel(body: RequestBody): String {
val length =
try {
body.contentLength()
} catch (e: IOException) {
-1L
}
return if (length >= 0) "[Binary data, $length bytes]" else "[Binary data]"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/

@file:Suppress("DEPRECATION_ERROR") // Conflicting okhttp versions

package com.facebook.react.modules.network

import com.facebook.react.bridge.Arguments
Expand All @@ -15,7 +17,13 @@ import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
import com.facebook.testutils.shadows.ShadowArguments
import java.io.ByteArrayInputStream
import java.net.SocketTimeoutException
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
Expand Down Expand Up @@ -292,6 +300,84 @@ class NetworkEventUtilTest {
assertThat(args.getString(3)).isEqualTo(url)
}

@Test
fun testGetRequestBodyPreviewReturnsNullForNullBody() {
assertThat(NetworkEventUtil.getRequestBodyPreview(null)).isNull()
}

@Test
fun testGetRequestBodyPreviewReturnsBodyForStringRequest() {
val payload = """{"key":"value"}"""
val body = payload.toRequestBody("application/json".toMediaTypeOrNull())

assertThat(NetworkEventUtil.getRequestBodyPreview(body)).isEqualTo(payload)
}

@Test
fun testGetRequestBodyPreviewUnwrapsProgressRequestBody() {
val payload = "hello world"
val inner = payload.toRequestBody("text/plain".toMediaTypeOrNull())
val wrapped = ProgressRequestBody(inner) { _, _, _ -> }

assertThat(NetworkEventUtil.getRequestBodyPreview(wrapped)).isEqualTo(payload)
}

@Test
fun testGetRequestBodyPreviewMultipartWithTextParts() {
val body =
MultipartBody.Builder("test-boundary")
.setType(MultipartBody.FORM)
.addFormDataPart("field1", "value1")
.addFormDataPart("field2", "value2")
.build()

val preview = NetworkEventUtil.getRequestBodyPreview(body)

assertThat(preview).isNotNull()
assertThat(preview).contains("--test-boundary")
assertThat(preview).contains("--test-boundary--")
assertThat(preview).contains("name=\"field1\"")
assertThat(preview).contains("value1")
assertThat(preview).contains("name=\"field2\"")
assertThat(preview).contains("value2")
assertThat(preview).doesNotContain("[Preview unavailable]")
}

@Test
fun testGetRequestBodyPreviewMultipartWithFilePartReplacesBinaryContent() {
val fileBytes = ByteArray(2048) { it.toByte() }
val streamingPart =
RequestBodyUtil.create(MediaType.parse("application/octet-stream"), ByteArrayInputStream(fileBytes))
val body =
MultipartBody.Builder("test-boundary")
.setType(MultipartBody.FORM)
.addFormDataPart("description", "an image")
.addFormDataPart("file", "photo.jpg", streamingPart)
.build()

val preview = NetworkEventUtil.getRequestBodyPreview(body)

assertThat(preview).isNotNull()
assertThat(preview).contains("--test-boundary")
assertThat(preview).contains("name=\"description\"")
assertThat(preview).contains("an image")
assertThat(preview).contains("name=\"file\"")
assertThat(preview).contains("filename=\"photo.jpg\"")
assertThat(preview).contains("[Binary data, 2048 bytes]")
assertThat(preview).doesNotContain("[Preview unavailable]")
}

@Test
fun testGetRequestBodyPreviewSingleOneShotBodyShowsPlaceholder() {
val fileBytes = ByteArray(512) { it.toByte() }
val body =
RequestBodyUtil.create(MediaType.parse("application/octet-stream"), ByteArrayInputStream(fileBytes))

val preview = NetworkEventUtil.getRequestBodyPreview(body)

assertThat(preview).isEqualTo("[Binary data, 512 bytes]")
}

@Test
fun testNullReactContext() {
val url = "http://example.com"
Expand Down