diff --git a/ktor-client/ktor-client-core/api/ktor-client-core.api b/ktor-client/ktor-client-core/api/ktor-client-core.api index 002ef34f02..80eb3d759a 100644 --- a/ktor-client/ktor-client-core/api/ktor-client-core.api +++ b/ktor-client/ktor-client-core/api/ktor-client-core.api @@ -1557,7 +1557,8 @@ public final class io/ktor/client/utils/HeadersKt { } public final class io/ktor/client/utils/HeadersUtilsKt { - public static final fun dropCompressionHeaders (Lio/ktor/http/HeadersBuilder;Lio/ktor/http/HttpMethod;Lio/ktor/util/Attributes;)V + public static final fun dropCompressionHeaders (Lio/ktor/http/HeadersBuilder;Lio/ktor/http/HttpMethod;Lio/ktor/util/Attributes;Z)V + public static synthetic fun dropCompressionHeaders$default (Lio/ktor/http/HeadersBuilder;Lio/ktor/http/HttpMethod;Lio/ktor/util/Attributes;ZILjava/lang/Object;)V } public final class io/ktor/client/utils/HttpResponseReceiveFail { diff --git a/ktor-client/ktor-client-core/api/ktor-client-core.klib.api b/ktor-client/ktor-client-core/api/ktor-client-core.klib.api index f859311741..abb85dfa08 100644 --- a/ktor-client/ktor-client-core/api/ktor-client-core.klib.api +++ b/ktor-client/ktor-client-core/api/ktor-client-core.klib.api @@ -1394,7 +1394,7 @@ final fun (io.ktor.client/HttpClientConfig<*>).io.ktor.client.plugins/defaultReq final fun (io.ktor.http.content/OutgoingContent).io.ktor.client.utils/wrapHeaders(kotlin/Function1): io.ktor.http.content/OutgoingContent // io.ktor.client.utils/wrapHeaders|wrapHeaders@io.ktor.http.content.OutgoingContent(kotlin.Function1){}[0] final fun (io.ktor.http/Cookie).io.ktor.client.plugins.cookies/fillDefaults(io.ktor.http/Url): io.ktor.http/Cookie // io.ktor.client.plugins.cookies/fillDefaults|fillDefaults@io.ktor.http.Cookie(io.ktor.http.Url){}[0] final fun (io.ktor.http/Cookie).io.ktor.client.plugins.cookies/matches(io.ktor.http/Url): kotlin/Boolean // io.ktor.client.plugins.cookies/matches|matches@io.ktor.http.Cookie(io.ktor.http.Url){}[0] -final fun (io.ktor.http/HeadersBuilder).io.ktor.client.utils/dropCompressionHeaders(io.ktor.http/HttpMethod, io.ktor.util/Attributes) // io.ktor.client.utils/dropCompressionHeaders|dropCompressionHeaders@io.ktor.http.HeadersBuilder(io.ktor.http.HttpMethod;io.ktor.util.Attributes){}[0] +final fun (io.ktor.http/HeadersBuilder).io.ktor.client.utils/dropCompressionHeaders(io.ktor.http/HttpMethod, io.ktor.util/Attributes, kotlin/Boolean = ...) // io.ktor.client.utils/dropCompressionHeaders|dropCompressionHeaders@io.ktor.http.HeadersBuilder(io.ktor.http.HttpMethod;io.ktor.util.Attributes;kotlin.Boolean){}[0] final fun (io.ktor.http/HttpMessageBuilder).io.ktor.client.request/accept(io.ktor.http/ContentType) // io.ktor.client.request/accept|accept@io.ktor.http.HttpMessageBuilder(io.ktor.http.ContentType){}[0] final fun (io.ktor.http/HttpMessageBuilder).io.ktor.client.request/basicAuth(kotlin/String, kotlin/String) // io.ktor.client.request/basicAuth|basicAuth@io.ktor.http.HttpMessageBuilder(kotlin.String;kotlin.String){}[0] final fun (io.ktor.http/HttpMessageBuilder).io.ktor.client.request/bearerAuth(kotlin/String) // io.ktor.client.request/bearerAuth|bearerAuth@io.ktor.http.HttpMessageBuilder(kotlin.String){}[0] 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..b48e3817e6 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..550613bb02 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") { diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingTest.kt index d6e3e04b2e..8681e5a2bc 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingTest.kt @@ -73,7 +73,7 @@ class LoggingTest : ClientLoader() { } @Test - fun testLogLevelAll() = clientTests(except("native:CIO")) { + fun testLogLevelAll() = clientTests(except("native:CIO", "web:Js")) { val logger = TestLogger( "REQUEST: http://localhost:8080/logging", "METHOD: HttpMethod(value=GET)", @@ -103,7 +103,7 @@ class LoggingTest : ClientLoader() { } @Test - fun testLogLevelHeaders() = clientTests { + fun testLogLevelHeaders() = clientTests(except("web:Js")) { val logger = TestLogger { line("REQUEST: http://localhost:8080/logging") line("METHOD: HttpMethod(value=GET)") @@ -143,7 +143,7 @@ class LoggingTest : ClientLoader() { } @Test - fun testLogPostBody() = clientTests(except("native:CIO")) { + fun testLogPostBody() = clientTests(except("native:CIO", "web:Js")) { val testLogger = TestLogger( "REQUEST: http://localhost:8080/logging", "METHOD: HttpMethod(value=POST)", @@ -199,7 +199,7 @@ class LoggingTest : ClientLoader() { } @Test - fun testLogPostMalformedUtf8Body() = clientTests(except("native:CIO")) { + fun testLogPostMalformedUtf8Body() = clientTests(except("native:CIO", "web:Js")) { val testLogger = TestLogger( "REQUEST: http://localhost:8080/logging/non-utf", "METHOD: HttpMethod(value=POST)", @@ -255,7 +255,7 @@ class LoggingTest : ClientLoader() { } @Test - fun testRequestAndResponseBody() = clientTests(except("native:CIO")) { + fun testRequestAndResponseBody() = clientTests(except("native:CIO", "web:Js")) { val testLogger = TestLogger( "REQUEST: http://127.0.0.1:8080/content/echo", "METHOD: HttpMethod(value=POST)", @@ -305,7 +305,7 @@ class LoggingTest : ClientLoader() { } @Test - fun testRequestContentTypeInLog() = clientTests(except("Darwin", "native:CIO", "DarwinLegacy")) { + fun testRequestContentTypeInLog() = clientTests(except("Darwin", "native:CIO", "DarwinLegacy", "web:Js")) { val testLogger = TestLogger( "REQUEST: http://127.0.0.1:8080/content/echo", "METHOD: HttpMethod(value=POST)", @@ -357,7 +357,7 @@ class LoggingTest : ClientLoader() { } @Test - fun testLoggingWithCompression() = clientTests(except("Darwin", "DarwinLegacy", "native:CIO", "web:CIO")) { + fun testLoggingWithCompression() = clientTests(except("Darwin", "DarwinLegacy", "native:CIO", "web:*")) { val testLogger = TestLogger( "REQUEST: http://127.0.0.1:8080/compression/deflate", "METHOD: HttpMethod(value=GET)", @@ -504,7 +504,7 @@ class LoggingTest : ClientLoader() { data class User(val name: String) @Test - fun testLogPostBodyWithJson() = clientTests(retries = 5) { + fun testLogPostBodyWithJson() = clientTests(except("web:Js"), retries = 5) { val testLogger = TestLogger( "REQUEST: http://127.0.0.1:8080/content/echo", "METHOD: HttpMethod(value=POST)",