From fb4e99b13d18bb8bcac2c8ab62ff39924ab0be6c Mon Sep 17 00:00:00 2001 From: Bruce Hamilton Date: Thu, 23 Jan 2025 18:52:50 +0100 Subject: [PATCH] KTOR-7934 Remove Content-Length for browser --- .../common/src/io/ktor/client/utils/HeadersUtils.kt | 12 +++++++++--- .../io/ktor/client/engine/js/WasmJsClientEngine.kt | 8 +++++++- .../src/io/ktor/client/test/base/ClientLoader.kt | 6 ++++++ .../client/tests/ContentEncodingIntegrationTest.kt | 3 ++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/ktor-client/ktor-client-core/common/src/io/ktor/client/utils/HeadersUtils.kt b/ktor-client/ktor-client-core/common/src/io/ktor/client/utils/HeadersUtils.kt index 1b78fb2e00..750669f3ad 100644 --- a/ktor-client/ktor-client-core/common/src/io/ktor/client/utils/HeadersUtils.kt +++ b/ktor-client/ktor-client-core/common/src/io/ktor/client/utils/HeadersUtils.kt @@ -15,10 +15,16 @@ private val DecompressionListAttribute: AttributeKey> = Attr * (like js and Curl) to make sure all the plugins and checks work with the correct content length and encoding. */ @InternalAPI -public fun HeadersBuilder.dropCompressionHeaders(method: HttpMethod, attributes: Attributes) { +public fun HeadersBuilder.dropCompressionHeaders( + method: HttpMethod, + attributes: Attributes, + alwaysRemove: Boolean = false, +) { if (method == HttpMethod.Head || method == HttpMethod.Options) return - val header = get(HttpHeaders.ContentEncoding) ?: return - attributes.computeIfAbsent(DecompressionListAttribute) { mutableListOf() }.add(header) + when(val header = get(HttpHeaders.ContentEncoding)) { + null -> if (!alwaysRemove) return + else -> attributes.computeIfAbsent(DecompressionListAttribute) { mutableListOf() }.add(header) + } remove(HttpHeaders.ContentEncoding) remove(HttpHeaders.ContentLength) } diff --git a/ktor-client/ktor-client-core/wasmJs/src/io/ktor/client/engine/js/WasmJsClientEngine.kt b/ktor-client/ktor-client-core/wasmJs/src/io/ktor/client/engine/js/WasmJsClientEngine.kt index 1a1e606777..c0c9cfdc91 100644 --- a/ktor-client/ktor-client-core/wasmJs/src/io/ktor/client/engine/js/WasmJsClientEngine.kt +++ b/ktor-client/ktor-client-core/wasmJs/src/io/ktor/client/engine/js/WasmJsClientEngine.kt @@ -173,7 +173,13 @@ internal fun org.w3c.fetch.Headers.mapToKtor(method: HttpMethod, attributes: Att append(key, value) } - dropCompressionHeaders(method, attributes) + // Content-Encoding is hidden for cross-origin calls, + // so browser requests should always ignore Content-Length + dropCompressionHeaders( + method, + attributes, + alwaysRemove = PlatformUtils.IS_BROWSER + ) } /** diff --git a/ktor-client/ktor-client-test-base/common/src/io/ktor/client/test/base/ClientLoader.kt b/ktor-client/ktor-client-test-base/common/src/io/ktor/client/test/base/ClientLoader.kt index 0cd15f4b92..b8925b6997 100644 --- a/ktor-client/ktor-client-test-base/common/src/io/ktor/client/test/base/ClientLoader.kt +++ b/ktor-client/ktor-client-test-base/common/src/io/ktor/client/test/base/ClientLoader.kt @@ -83,6 +83,12 @@ abstract class ClientLoader(private val timeout: Duration = 1.minutes) { return EngineSelectionRule { pattern.matches(it) } } + /** Includes the set of [engines] for the test */ + fun only(vararg engines: String): EngineSelectionRule { + val includePatterns = engines.map(EnginePattern::parse) + return EngineSelectionRule { engineName -> includePatterns.any { it.matches(engineName) } } + } + /** Excludes the specified [engines] from test execution. */ fun except(vararg engines: String): EngineSelectionRule = except(engines.asList()) diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ContentEncodingIntegrationTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ContentEncodingIntegrationTest.kt index 29e2f9e3c8..e67043a70a 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ContentEncodingIntegrationTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ContentEncodingIntegrationTest.kt @@ -20,7 +20,7 @@ class ContentEncodingIntegrationTest : ClientLoader() { // GZipEncoder is implemented only on JVM. @Test - fun testGzipWithContentLengthWithoutPlugin() = clientTests(only("jvm:*")) { + fun testGzipWithContentLengthWithoutPlugin() = clientTests(only("jvm:*", "web:Js")) { test { client -> val response = client.get("$TEST_URL/gzip-with-content-length") val content = if (response.headers[HttpHeaders.ContentEncoding] == "gzip") { @@ -43,4 +43,5 @@ class ContentEncodingIntegrationTest : ClientLoader() { assertEquals("32", response.headers[HttpHeaders.ContentLength]) } } + }