Summary
NetworkToolsInterceptor.captureResponseBody() reads the entire response body into memory by calling source.request(Long.MAX_VALUE). For large responses (images, file downloads, big JSON payloads) this duplicates the entire response in RAM, risking OutOfMemoryError or significant GC pressure.
Affected code
android/src/main/java/com/networktools/interceptor/NetworkToolsInterceptor.kt:
private fun captureResponseBody(response: Response): String? {
return try {
val source = response.body?.source()
source?.request(Long.MAX_VALUE) // reads entire body into buffer
val buffer = source?.buffer
buffer?.clone()?.readUtf8() // clones the full buffer again
} catch (e: Exception) {
null
}
}
For a 50 MB image download this allocates ~100 MB of heap (once in the source buffer, once in the clone). The same issue exists in captureRequestBody() for large upload payloads.
Fix — enforce a maxBodySize cap
private val MAX_CAPTURE_BYTES = 256 * 1024L // 256 KB default, configurable
private fun captureResponseBody(response: Response): String? {
return try {
val contentType = response.body?.contentType()?.toString() ?: ""
if (isBinaryContentType(contentType)) return "[binary content — not captured]"
val source = response.body?.source()
source?.request(MAX_CAPTURE_BYTES)
val snapshot = source?.buffer?.snapshot() ?: return null
val capturedBytes = minOf(snapshot.size, MAX_CAPTURE_BYTES)
val body = snapshot.substring(0, capturedBytes).utf8()
if (snapshot.size > MAX_CAPTURE_BYTES) "$body\n[... truncated — ${snapshot.size} bytes total]"
else body
} catch (e: Exception) {
null
}
}
private fun isBinaryContentType(contentType: String): Boolean {
val binaryPrefixes = listOf("image/", "video/", "audio/", "application/octet-stream", "application/zip")
return binaryPrefixes.any { contentType.startsWith(it) }
}
Expose the limit as a configurable property on NetworkToolsManager:
object NetworkToolsManager {
var maxBodyCaptureBytes: Long = 256 * 1024L
}
Acceptance criteria
Summary
NetworkToolsInterceptor.captureResponseBody()reads the entire response body into memory by callingsource.request(Long.MAX_VALUE). For large responses (images, file downloads, big JSON payloads) this duplicates the entire response in RAM, riskingOutOfMemoryErroror significant GC pressure.Affected code
android/src/main/java/com/networktools/interceptor/NetworkToolsInterceptor.kt:For a 50 MB image download this allocates ~100 MB of heap (once in the source buffer, once in the clone). The same issue exists in
captureRequestBody()for large upload payloads.Fix — enforce a
maxBodySizecapExpose the limit as a configurable property on
NetworkToolsManager:Acceptance criteria
captureResponseBodyreads at mostmaxBodyCaptureBytes(default 256 KB)[... truncated — N bytes total]marker[binary content — not captured]placeholdercaptureRequestBodyapplies the same capNetworkToolsManager.maxBodyCaptureBytesis publicly settableNetworkMonitorProvideraccepts amaxBodyCaptureBytesprop and passes it through to nativeimage/pngresponse → returns binary placeholder, no body bytes stored