From c06ff312b29867e68bb12e829d5f015a29b92ff4 Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Mon, 6 Oct 2025 19:46:55 -0400 Subject: [PATCH] Get Content-Type from the request body (#9113) Co-authored-by: Jesse Wilson --- .../kotlin/okhttp3/Request.kt | 7 ++ .../src/jvmTest/kotlin/okhttp3/RequestTest.kt | 77 +++++++++++++------ 2 files changed, 62 insertions(+), 22 deletions(-) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Request.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Request.kt index cc5e073c3..6d4b5bdb4 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Request.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Request.kt @@ -484,6 +484,8 @@ class Request internal constructor( buildString { append("curl ${url.toString().shellEscape()}") + val contentType = body?.contentType()?.toString() + // Add method if not the default. val defaultMethod = when { @@ -496,9 +498,14 @@ class Request internal constructor( // Append headers. for ((name, value) in headers) { + if (contentType != null && name.equals("Content-Type", ignoreCase = true)) continue append(" \\\n -H ${"$name: $value".shellEscape()}") } + if (contentType != null) { + append(" \\\n -H ${"Content-Type: $contentType".shellEscape()}") + } + // Append body if present. if (includeBody && body != null) { val bodyBuffer = Buffer() diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/RequestTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/RequestTest.kt index 8564e1174..9dc98e5ed 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/RequestTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/RequestTest.kt @@ -34,6 +34,7 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody import okio.Buffer +import okio.ByteString.Companion.decodeHex import okio.ByteString.Companion.encodeUtf8 import okio.GzipSource import okio.buffer @@ -673,15 +674,13 @@ class RequestTest { @Test fun curlPostWithBody() { - val mediaType = "application/json".toMediaType() - val body = "{\"key\":\"value\"}".toRequestBody(mediaType) + val body = "{\"key\":\"value\"}".toRequestBody("application/json".toMediaType()) val request = Request .Builder() .url("https://api.example.com/data") .post(body) - .addHeader("Content-Type", "application/json") .addHeader("Authorization", "Bearer abc123") .build() @@ -689,8 +688,52 @@ class RequestTest { .isEqualTo( """ |curl 'https://api.example.com/data' \ - | -H 'Content-Type: application/json' \ | -H 'Authorization: Bearer abc123' \ + | -H 'Content-Type: application/json; charset=utf-8' \ + | --data '{"key":"value"}' + """.trimMargin(), + ) + } + + @Test + fun bodyContentTypeTakesPrecedence() { + val body = "{\"key\":\"value\"}".toRequestBody("application/json".toMediaType()) + + val request = + Request + .Builder() + .url("https://api.example.com/data") + .post(body) + .addHeader("Content-Type", "text/plain") + .build() + + assertThat(request.toCurl()) + .isEqualTo( + """ + |curl 'https://api.example.com/data' \ + | -H 'Content-Type: application/json; charset=utf-8' \ + | --data '{"key":"value"}' + """.trimMargin(), + ) + } + + @Test + fun requestContentTypeIsFallback() { + val body = "{\"key\":\"value\"}".toRequestBody(contentType = null) + + val request = + Request + .Builder() + .url("https://api.example.com/data") + .post(body) + .addHeader("Content-Type", "text/plain") + .build() + + assertThat(request.toCurl()) + .isEqualTo( + """ + |curl 'https://api.example.com/data' \ + | -H 'Content-Type: text/plain' \ | --data '{"key":"value"}' """.trimMargin(), ) @@ -699,15 +742,13 @@ class RequestTest { /** Put is not the default method so `-X 'PUT'` is included. */ @Test fun curlPutWithBody() { - val mediaType = "application/json".toMediaType() - val body = "{\"key\":\"value\"}".toRequestBody(mediaType) + val body = "{\"key\":\"value\"}".toRequestBody("application/json".toMediaType()) val request = Request .Builder() .url("https://api.example.com/data") .put(body) - .addHeader("Content-Type", "application/json") .addHeader("Authorization", "Bearer abc123") .build() @@ -716,8 +757,8 @@ class RequestTest { """ |curl 'https://api.example.com/data' \ | -X 'PUT' \ - | -H 'Content-Type: application/json' \ | -H 'Authorization: Bearer abc123' \ + | -H 'Content-Type: application/json; charset=utf-8' \ | --data '{"key":"value"}' """.trimMargin(), ) @@ -725,7 +766,6 @@ class RequestTest { @Test fun curlPostWithComplexBody() { - val mediaType = "application/json".toMediaType() val jsonBody = """ |{ @@ -739,14 +779,13 @@ class RequestTest { | """.trimMargin() - val body = jsonBody.toRequestBody(mediaType) + val body = jsonBody.toRequestBody("application/json".toMediaType()) val request = Request .Builder() .url("https://api.example.com/users") .post(body) - .addHeader("Content-Type", "application/json") .addHeader("Authorization", "Bearer xyz789") .build() @@ -754,8 +793,8 @@ class RequestTest { .isEqualTo( """ |curl 'https://api.example.com/users' \ - | -H 'Content-Type: application/json' \ | -H 'Authorization: Bearer xyz789' \ + | -H 'Content-Type: application/json; charset=utf-8' \ | --data '{ | "user": { | "id": 123, @@ -771,17 +810,14 @@ class RequestTest { @Test fun curlPostWithBinaryBody() { - val mediaType = "application/octet-stream".toMediaType() - val binaryData = byteArrayOf(0x00, 0x01, 0x02, 0x03) - - val body = binaryData.toRequestBody(mediaType) + val binaryData = "00010203".decodeHex() + val body = binaryData.toRequestBody("application/octet-stream".toMediaType()) val request = Request .Builder() .url("https://api.example.com/upload") .post(body) - .addHeader("Content-Type", "application/octet-stream") .build() val curl = request.toCurl() @@ -797,17 +833,14 @@ class RequestTest { @Test fun curlPostWithBinaryBodyOmitted() { - val mediaType = "application/octet-stream".toMediaType() - val binaryData = byteArrayOf(0x10, 0x20) - - val body = binaryData.toRequestBody(mediaType) + val binaryData = "1020".decodeHex() + val body = binaryData.toRequestBody("application/octet-stream".toMediaType()) val request = Request .Builder() .url("https://api.example.com/upload") .post(body) - .addHeader("Content-Type", "application/octet-stream") .build() val curl = request.toCurl(includeBody = false)