diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/MultipartBody.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/MultipartBody.kt index 6407ce29ea6b..abd8cc48f8ca 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/MultipartBody.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/MultipartBody.kt @@ -23,6 +23,9 @@ import okio.Buffer import okio.BufferedSink import okio.ByteString import okio.ByteString.Companion.encodeUtf8 +import okio.ForwardingSink +import okio.Sink +import okio.buffer /** * An [RFC 2387][rfc_2387]-compliant request body. @@ -160,7 +163,7 @@ class MultipartBody internal constructor( if (countBytes) { byteCount += contentLength } else { - body.writeTo(sink) + NoCloseSink(sink).buffer().use(body::writeTo) } sink.write(CRLF) @@ -377,4 +380,10 @@ class MultipartBody internal constructor( append('"') } } + + private class NoCloseSink( + sink: Sink, + ) : ForwardingSink(sink) { + override fun close() {} + } } diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/RequestBody.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/RequestBody.kt index be595678aa92..22d4995fbf3b 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/RequestBody.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/RequestBody.kt @@ -41,7 +41,7 @@ abstract class RequestBody { @Throws(IOException::class) open fun contentLength(): Long = -1L - /** Writes the content of this request to [sink]. */ + /** Writes the content of this request to [sink]. This should not close [sink]. */ @Throws(IOException::class) abstract fun writeTo(sink: BufferedSink) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/MultipartBodyTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/MultipartBodyTest.kt index a2b69d015ec0..2df3855b96bb 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/MultipartBodyTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/MultipartBodyTest.kt @@ -24,8 +24,11 @@ import okhttp3.Headers.Companion.headersOf import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.internal.http.GzipRequestBody import okio.Buffer import okio.BufferedSink +import okio.ForwardingSink +import okio.buffer import okio.utf8Size import org.junit.jupiter.api.Test @@ -254,6 +257,71 @@ class MultipartBodyTest { assertThat(buffer.readUtf8()).isEqualTo(expected) } + @Test + fun partThatClosesTheSink() { + class ClosingBody( + private val body: String, + ) : RequestBody() { + override fun contentType(): MediaType? = null + + @Throws(IOException::class) + override fun writeTo(sink: BufferedSink) { + sink.writeUtf8(body) + sink.close() + } + } + + val expected = + """ + |--123 + | + |hello + |--123 + | + |world + |--123-- + | + """.trimMargin().replace("\n", "\r\n") + val body = + MultipartBody + .Builder("123") + .addPart(ClosingBody("hello")) + .addPart("world".toRequestBody(null)) + .build() + val buffer = Buffer() + // Don't write to Buffer directly: Buffer.close() is a no-op. Wrap it so close() + // actually closes the sink, like a real HTTP write. + val sink = object : ForwardingSink(buffer) {}.buffer() + body.writeTo(sink) + sink.close() + assertThat(buffer.readUtf8()).isEqualTo(expected) + } + + @Test + fun gzippedParts() { + val body = + MultipartBody + .Builder("123") + .addPart(GzipRequestBody("part1".toRequestBody(null))) + .addPart(GzipRequestBody("part2".toRequestBody(null))) + .build() + + val expected = Buffer() + expected.writeUtf8("--123\r\n\r\n") + GzipRequestBody("part1".toRequestBody(null)).writeTo(expected) + expected.writeUtf8("\r\n--123\r\n\r\n") + GzipRequestBody("part2".toRequestBody(null)).writeTo(expected) + expected.writeUtf8("\r\n--123--\r\n") + + val buffer = Buffer() + // Don't write to Buffer directly: Buffer.close() is a no-op. Wrap it so close() + // actually closes the sink, like a real HTTP write. + val sink = object : ForwardingSink(buffer) {}.buffer() + body.writeTo(sink) + sink.close() + assertThat(buffer.readUtf8()).isEqualTo(expected.readUtf8()) + } + @Test fun contentTypeHeaderIsForbidden() { val multipart = MultipartBody.Builder()