From 4431d95d6d3098562711f319e17f7651b018183c Mon Sep 17 00:00:00 2001
From: Rustam <rxsinukov@gmail.com>
Date: Mon, 13 Feb 2023 05:40:07 +0100
Subject: [PATCH] KTOR-3799 Add charset only for text/* content types (#3370)

---
 .../tests/JsonContentNegotiationTest.kt       | 22 +++++++++++++++++++
 .../common/src/io/ktor/http/ContentTypes.kt   |  2 +-
 .../io/ktor/tests/http/ContentTypeTest.kt     | 15 +++++++++++++
 3 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/ktor-client-content-negotiation-tests/jvm/src/io/ktor/client/plugins/contentnegotiation/tests/JsonContentNegotiationTest.kt b/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/ktor-client-content-negotiation-tests/jvm/src/io/ktor/client/plugins/contentnegotiation/tests/JsonContentNegotiationTest.kt
index a071673a4a7..771c3082dfc 100644
--- a/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/ktor-client-content-negotiation-tests/jvm/src/io/ktor/client/plugins/contentnegotiation/tests/JsonContentNegotiationTest.kt
+++ b/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/ktor-client-content-negotiation-tests/jvm/src/io/ktor/client/plugins/contentnegotiation/tests/JsonContentNegotiationTest.kt
@@ -209,4 +209,26 @@ abstract class JsonContentNegotiationTest(private val converter: ContentConverte
             assertEquals(null, response.body<Wrapper?>())
         }
     }
+
+    @Test
+    fun testNoCharsetIsAdded() = testApplication {
+        routing {
+            post("/") {
+                assertNull(call.request.contentType().charset())
+                call.respond("OK")
+            }
+        }
+
+        createClient {
+            install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) {
+                register(ContentType("application", "json-patch+json"), converter)
+            }
+        }.post("/") {
+            val data: Wrapper? = null
+            contentType(ContentType("application", "json-patch+json"))
+            setBody(data)
+        }.let {
+            assertEquals("OK", it.bodyAsText())
+        }
+    }
 }
diff --git a/ktor-http/common/src/io/ktor/http/ContentTypes.kt b/ktor-http/common/src/io/ktor/http/ContentTypes.kt
index dea67b7fb8d..64319b165b6 100644
--- a/ktor-http/common/src/io/ktor/http/ContentTypes.kt
+++ b/ktor-http/common/src/io/ktor/http/ContentTypes.kt
@@ -286,7 +286,7 @@ public fun ContentType.withCharset(charset: Charset): ContentType =
  * if [ContentType] is not ignored
  */
 public fun ContentType.withCharsetIfNeeded(charset: Charset): ContentType =
-    if (contentType.lowercase() == "application" && contentSubtype.lowercase() == "json") {
+    if (contentType.lowercase() != "text") {
         this
     } else {
         withParameter("charset", charset.name)
diff --git a/ktor-http/common/test/io/ktor/tests/http/ContentTypeTest.kt b/ktor-http/common/test/io/ktor/tests/http/ContentTypeTest.kt
index 5c04982e362..dc07c2082cc 100644
--- a/ktor-http/common/test/io/ktor/tests/http/ContentTypeTest.kt
+++ b/ktor-http/common/test/io/ktor/tests/http/ContentTypeTest.kt
@@ -5,6 +5,7 @@
 package io.ktor.tests.http
 
 import io.ktor.http.*
+import io.ktor.utils.io.charsets.Charsets
 import kotlin.test.*
 
 class ContentTypeTest {
@@ -140,4 +141,18 @@ class ContentTypeTest {
         val content = ContentType.parse(contentType)
         assertEquals("text/html; charset=UTF-8", content.toString())
     }
+
+    @Test
+    fun testNoCharsetForNonText() {
+        assertNull(ContentType.Audio.MP4.withCharsetIfNeeded(Charsets.UTF_8).charset())
+        assertNull(ContentType.Application.Json.withCharsetIfNeeded(Charsets.UTF_8).charset())
+        assertNull(ContentType("application", "json-patch+json").withCharsetIfNeeded(Charsets.UTF_8).charset())
+    }
+
+    @Test
+    fun testCharsetForText() {
+        assertEquals(Charsets.UTF_8, ContentType.Text.Any.withCharsetIfNeeded(Charsets.UTF_8).charset())
+        assertEquals(Charsets.UTF_8, ContentType.Text.Html.withCharsetIfNeeded(Charsets.UTF_8).charset())
+        assertEquals(Charsets.UTF_8, ContentType("Text", "custom").withCharsetIfNeeded(Charsets.UTF_8).charset())
+    }
 }