diff --git a/buildSrc/src/main/kotlin/TargetsConfig.kt b/buildSrc/src/main/kotlin/TargetsConfig.kt index d82cb47dfe3..d8f652d5b04 100644 --- a/buildSrc/src/main/kotlin/TargetsConfig.kt +++ b/buildSrc/src/main/kotlin/TargetsConfig.kt @@ -110,16 +110,16 @@ private val hierarchyTemplate = KotlinHierarchyTemplate { group("posix") } - group("jvmAndNix") { - withJvm() - group("nix") - } - group("desktop") { group("linux") group("windows") group("macos") } + + group("nonJvm") { + group("posix") + group("jsAndWasmShared") + } } } diff --git a/buildSrc/src/main/kotlin/test/server/tests/MultiPartFormData.kt b/buildSrc/src/main/kotlin/test/server/tests/MultiPartFormData.kt index 9b53033cf8c..8cd297df907 100644 --- a/buildSrc/src/main/kotlin/test/server/tests/MultiPartFormData.kt +++ b/buildSrc/src/main/kotlin/test/server/tests/MultiPartFormData.kt @@ -4,6 +4,7 @@ package test.server.tests +import io.ktor.client.request.forms.* import io.ktor.http.* import io.ktor.http.content.* import io.ktor.server.application.* @@ -34,6 +35,18 @@ internal fun Application.multiPartFormDataTest() { call.receiveMultipart().readPart() call.respond(HttpStatusCode.OK) } + post("receive") { + val multipart = MultiPartFormDataContent( + formData { + append("text", "Hello, World!") + append("file", ByteArray(1024) { it.toByte() }, Headers.build { + append(HttpHeaders.ContentDisposition, """form-data; name="file"; filename="test.bin"""") + append(HttpHeaders.ContentType, ContentType.Application.OctetStream.toString()) + }) + } + ) + call.respond(multipart) + } } } } diff --git a/gradle.properties b/gradle.properties index a96cc1ccc37..8be4c4ed07f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,12 @@ # -# Copyright 2014-2020 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. +# Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. # # sytleguide kotlin.code.style=official # config -version=3.0.2-SNAPSHOT +version=3.1.0-SNAPSHOT # gradle org.gradle.daemon=true diff --git a/ktor-client/ktor-client-android/api/ktor-client-android.api b/ktor-client/ktor-client-android/api/ktor-client-android.api index f8152b9a21f..c9626c1ad7c 100644 --- a/ktor-client/ktor-client-android/api/ktor-client-android.api +++ b/ktor-client/ktor-client-android/api/ktor-client-android.api @@ -1,6 +1,9 @@ public final class io/ktor/client/engine/android/Android : io/ktor/client/engine/HttpClientEngineFactory { public static final field INSTANCE Lio/ktor/client/engine/android/Android; public fun create (Lkotlin/jvm/functions/Function1;)Lio/ktor/client/engine/HttpClientEngine; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class io/ktor/client/engine/android/AndroidClientEngine : io/ktor/client/engine/HttpClientEngineBase { diff --git a/ktor-client/ktor-client-android/jvm/src/io/ktor/client/engine/android/Android.kt b/ktor-client/ktor-client-android/jvm/src/io/ktor/client/engine/android/Android.kt index 8138688db08..5efb76b6651 100644 --- a/ktor-client/ktor-client-android/jvm/src/io/ktor/client/engine/android/Android.kt +++ b/ktor-client/ktor-client-android/jvm/src/io/ktor/client/engine/android/Android.kt @@ -26,7 +26,7 @@ import io.ktor.client.engine.* * * You can learn more about client engines from [Engines](https://ktor.io/docs/http-client-engines.html). */ -public object Android : HttpClientEngineFactory { +public data object Android : HttpClientEngineFactory { override fun create(block: AndroidEngineConfig.() -> Unit): HttpClientEngine = AndroidClientEngine(AndroidEngineConfig().apply(block)) } diff --git a/ktor-client/ktor-client-apache/api/ktor-client-apache.api b/ktor-client/ktor-client-apache/api/ktor-client-apache.api index c9622b69391..7b79b512cd3 100644 --- a/ktor-client/ktor-client-apache/api/ktor-client-apache.api +++ b/ktor-client/ktor-client-apache/api/ktor-client-apache.api @@ -1,6 +1,9 @@ public final class io/ktor/client/engine/apache/Apache : io/ktor/client/engine/HttpClientEngineFactory { public static final field INSTANCE Lio/ktor/client/engine/apache/Apache; public fun create (Lkotlin/jvm/functions/Function1;)Lio/ktor/client/engine/HttpClientEngine; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class io/ktor/client/engine/apache/ApacheEngineConfig : io/ktor/client/engine/HttpClientEngineConfig { diff --git a/ktor-client/ktor-client-apache/jvm/src/io/ktor/client/engine/apache/Apache.kt b/ktor-client/ktor-client-apache/jvm/src/io/ktor/client/engine/apache/Apache.kt index ee3bd2489fc..2df9167d90d 100644 --- a/ktor-client/ktor-client-apache/jvm/src/io/ktor/client/engine/apache/Apache.kt +++ b/ktor-client/ktor-client-apache/jvm/src/io/ktor/client/engine/apache/Apache.kt @@ -25,7 +25,7 @@ import io.ktor.client.engine.* * * You can learn more about client engines from [Engines](https://ktor.io/docs/http-client-engines.html). */ -public object Apache : HttpClientEngineFactory { +public data object Apache : HttpClientEngineFactory { override fun create(block: ApacheEngineConfig.() -> Unit): HttpClientEngine { val config = ApacheEngineConfig().apply(block) return ApacheEngine(config) diff --git a/ktor-client/ktor-client-apache5/api/ktor-client-apache5.api b/ktor-client/ktor-client-apache5/api/ktor-client-apache5.api index 5f665603e19..9f7dd8a9e97 100644 --- a/ktor-client/ktor-client-apache5/api/ktor-client-apache5.api +++ b/ktor-client/ktor-client-apache5/api/ktor-client-apache5.api @@ -1,6 +1,9 @@ public final class io/ktor/client/engine/apache5/Apache5 : io/ktor/client/engine/HttpClientEngineFactory { public static final field INSTANCE Lio/ktor/client/engine/apache5/Apache5; public fun create (Lkotlin/jvm/functions/Function1;)Lio/ktor/client/engine/HttpClientEngine; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class io/ktor/client/engine/apache5/Apache5EngineConfig : io/ktor/client/engine/HttpClientEngineConfig { diff --git a/ktor-client/ktor-client-apache5/jvm/src/io/ktor/client/engine/apache5/Apache5.kt b/ktor-client/ktor-client-apache5/jvm/src/io/ktor/client/engine/apache5/Apache5.kt index dff6e8230b5..4d527129470 100644 --- a/ktor-client/ktor-client-apache5/jvm/src/io/ktor/client/engine/apache5/Apache5.kt +++ b/ktor-client/ktor-client-apache5/jvm/src/io/ktor/client/engine/apache5/Apache5.kt @@ -25,7 +25,7 @@ import io.ktor.client.engine.* * * You can learn more about client engines from [Engines](https://ktor.io/docs/http-client-engines.html). */ -public object Apache5 : HttpClientEngineFactory { +public data object Apache5 : HttpClientEngineFactory { override fun create(block: Apache5EngineConfig.() -> Unit): HttpClientEngine { val config = Apache5EngineConfig().apply(block) return Apache5Engine(config) diff --git a/ktor-client/ktor-client-cio/api/ktor-client-cio.klib.api b/ktor-client/ktor-client-cio/api/ktor-client-cio.klib.api index 6204ea570a1..4794aa06ca0 100644 --- a/ktor-client/ktor-client-cio/api/ktor-client-cio.klib.api +++ b/ktor-client/ktor-client-cio/api/ktor-client-cio.klib.api @@ -1,5 +1,5 @@ // Klib ABI Dump -// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] // Rendering settings: // - Signature version: 2 // - Show manifest properties: true @@ -62,3 +62,7 @@ final object io.ktor.client.engine.cio/CIO : io.ktor.client.engine/HttpClientEng } final fun (io.ktor.client.engine.cio/CIOEngineConfig).io.ktor.client.engine.cio/endpoint(kotlin/Function1): io.ktor.client.engine.cio/EndpointConfig // io.ktor.client.engine.cio/endpoint|endpoint@io.ktor.client.engine.cio.CIOEngineConfig(kotlin.Function1){}[0] + +// Targets: [js] +final val io.ktor.client.engine.cio/initHook // io.ktor.client.engine.cio/initHook|{}initHook[0] + final fun (): dynamic // io.ktor.client.engine.cio/initHook.|(){}[0] diff --git a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/CIOCommon.kt b/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/CIOCommon.kt similarity index 96% rename from ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/CIOCommon.kt rename to ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/CIOCommon.kt index ee1023e3bd6..28b85a40111 100644 --- a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/CIOCommon.kt +++ b/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/CIOCommon.kt @@ -26,10 +26,6 @@ import io.ktor.client.engine.* * You can learn more about client engines from [Engines](https://ktor.io/docs/http-client-engines.html). */ public data object CIO : HttpClientEngineFactory { - init { - addToLoader() - } - override fun create(block: CIOEngineConfig.() -> Unit): HttpClientEngine = CIOEngine(CIOEngineConfig().apply(block)) } diff --git a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/CIOEngine.kt b/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/CIOEngine.kt similarity index 98% rename from ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/CIOEngine.kt rename to ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/CIOEngine.kt index 59fac79bfd4..8103bf9fb4b 100644 --- a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/CIOEngine.kt +++ b/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/CIOEngine.kt @@ -61,7 +61,6 @@ internal class CIOEngine( val requestJob = requestField[Job]!! val selector = selectorManager - @OptIn(ExperimentalCoroutinesApi::class) GlobalScope.launch(parentContext, start = CoroutineStart.ATOMIC) { try { requestJob.join() diff --git a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/CIOEngineConfig.kt b/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/CIOEngineConfig.kt similarity index 100% rename from ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/CIOEngineConfig.kt rename to ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/CIOEngineConfig.kt diff --git a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/ConnectionFactory.kt b/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/ConnectionFactory.kt similarity index 100% rename from ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/ConnectionFactory.kt rename to ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/ConnectionFactory.kt diff --git a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/ConnectionPipelineCommon.kt b/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/ConnectionPipelineCommon.kt similarity index 100% rename from ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/ConnectionPipelineCommon.kt rename to ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/ConnectionPipelineCommon.kt diff --git a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/Endpoint.kt b/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/Endpoint.kt similarity index 100% rename from ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/Endpoint.kt rename to ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/Endpoint.kt diff --git a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/EngineTasks.kt b/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/EngineTasks.kt similarity index 100% rename from ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/EngineTasks.kt rename to ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/EngineTasks.kt diff --git a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/utils.kt b/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/utils.kt similarity index 98% rename from ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/utils.kt rename to ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/utils.kt index 8ec2b097ed1..72a12c9bfea 100644 --- a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/utils.kt +++ b/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/utils.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.client.engine.cio @@ -14,8 +14,6 @@ import io.ktor.http.content.* import io.ktor.util.date.* import io.ktor.utils.io.* import io.ktor.utils.io.CancellationException -import io.ktor.utils.io.core.* -import io.ktor.utils.io.errors.* import io.ktor.websocket.* import kotlinx.coroutines.* import kotlinx.io.IOException diff --git a/ktor-client/ktor-client-cio/jvmAndPosix/test/CIOEngineTest.kt b/ktor-client/ktor-client-cio/common/test/CIOEngineTest.kt similarity index 93% rename from ktor-client/ktor-client-cio/jvmAndPosix/test/CIOEngineTest.kt rename to ktor-client/ktor-client-cio/common/test/CIOEngineTest.kt index f442d2f88df..ccd0fd877eb 100644 --- a/ktor-client/ktor-client-cio/jvmAndPosix/test/CIOEngineTest.kt +++ b/ktor-client/ktor-client-cio/common/test/CIOEngineTest.kt @@ -1,8 +1,10 @@ /* - * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ + +package io.ktor.client.engine.cio + import io.ktor.client.* -import io.ktor.client.engine.cio.* import io.ktor.client.plugins.sse.* import io.ktor.client.plugins.websocket.* import io.ktor.client.request.* @@ -10,8 +12,8 @@ import io.ktor.client.tests.utils.* import io.ktor.http.* import io.ktor.network.selector.* import io.ktor.network.sockets.* +import io.ktor.test.dispatcher.* import io.ktor.utils.io.* -import io.ktor.utils.io.core.* import io.ktor.websocket.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -23,7 +25,7 @@ class CIOEngineTest { private val selectorManager = SelectorManager() @Test - fun testRequestTimeoutIgnoredWithWebSocket(): Unit = runBlocking { + fun testRequestTimeoutIgnoredWithWebSocket() = runTestWithRealTime { val client = HttpClient(CIO) { engine { requestTimeout = 10 @@ -47,7 +49,7 @@ class CIOEngineTest { } @Test - fun testRequestTimeoutIgnoredWithSSE(): Unit = runBlocking { + fun testRequestTimeoutIgnoredWithSSE() = runTestWithRealTime { val client = HttpClient(CIO) { engine { requestTimeout = 10 @@ -66,7 +68,7 @@ class CIOEngineTest { } @Test - fun testExpectHeader(): Unit = runBlocking { + fun testExpectHeader() = runTestWithRealTime { val body = "Hello World" withServerSocket { socket -> @@ -94,7 +96,7 @@ class CIOEngineTest { } @Test - fun testNoExpectHeaderIfNoBody(): Unit = runBlocking { + fun testNoExpectHeaderIfNoBody() = runTestWithRealTime { withServerSocket { socket -> val client = HttpClient(CIO) launch { @@ -115,7 +117,7 @@ class CIOEngineTest { } @Test - fun testDontWaitForContinueResponse(): Unit = runBlocking { + fun testDontWaitForContinueResponse() = runTestWithRealTime { withTimeout(30.seconds) { val body = "Hello World\n" @@ -147,7 +149,7 @@ class CIOEngineTest { } @Test - fun testRepeatRequestAfterExpectationFailed(): Unit = runBlocking { + fun testRepeatRequestAfterExpectationFailed() = runTestWithRealTime { val body = "Hello World" withServerSocket { socket -> diff --git a/ktor-client/ktor-client-cio/jvmAndPosix/test/ConnectionFactoryTest.kt b/ktor-client/ktor-client-cio/common/test/ConnectionFactoryTest.kt similarity index 90% rename from ktor-client/ktor-client-cio/jvmAndPosix/test/ConnectionFactoryTest.kt rename to ktor-client/ktor-client-cio/common/test/ConnectionFactoryTest.kt index a4354f66eda..cc77fc7fe4e 100644 --- a/ktor-client/ktor-client-cio/jvmAndPosix/test/ConnectionFactoryTest.kt +++ b/ktor-client/ktor-client-cio/common/test/ConnectionFactoryTest.kt @@ -1,9 +1,12 @@ /* - * Copyright 2014-2023 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -import io.ktor.client.engine.cio.* + +package io.ktor.client.engine.cio + import io.ktor.network.selector.* import io.ktor.network.sockets.* +import io.ktor.test.dispatcher.* import io.ktor.utils.io.core.* import kotlinx.coroutines.* import kotlin.test.* @@ -23,7 +26,7 @@ class ConnectionFactoryTest { } @Test - fun testLimitSemaphore() = runBlocking { + fun testLimitSemaphore() = runTestWithRealTime { val connectionFactory = ConnectionFactory( selectorManager, connectionsLimit = 2, @@ -44,7 +47,7 @@ class ConnectionFactoryTest { } @Test - fun testAddressSemaphore() = runBlocking { + fun testAddressSemaphore() = runTestWithRealTime { val connectionFactory = ConnectionFactory( selectorManager, connectionsLimit = 2, @@ -67,7 +70,7 @@ class ConnectionFactoryTest { } @Test - fun testReleaseLimitSemaphoreWhenFailed() = runBlocking { + fun testReleaseLimitSemaphoreWhenFailed() = runTestWithRealTime { val connectionFactory = ConnectionFactory( selectorManager, connectionsLimit = 2, diff --git a/ktor-client/ktor-client-cio/gradle.properties b/ktor-client/ktor-client-cio/gradle.properties new file mode 100644 index 00000000000..d12c10bfad8 --- /dev/null +++ b/ktor-client/ktor-client-cio/gradle.properties @@ -0,0 +1,5 @@ +# +# Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. +# +target.js.browser=false +target.wasmJs.browser=false diff --git a/ktor-client/ktor-client-cio/js/src/io/ktor/client/engine/cio/Loader.js.kt b/ktor-client/ktor-client-cio/js/src/io/ktor/client/engine/cio/Loader.js.kt new file mode 100644 index 00000000000..95af101c028 --- /dev/null +++ b/ktor-client/ktor-client-cio/js/src/io/ktor/client/engine/cio/Loader.js.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.client.engine.cio + +import io.ktor.client.engine.* +import io.ktor.util.* +import io.ktor.utils.io.* + +@Suppress("DEPRECATION") +@OptIn(ExperimentalStdlibApi::class, ExperimentalJsExport::class, InternalAPI::class) +@Deprecated("", level = DeprecationLevel.HIDDEN) +@JsExport +@EagerInitialization +public val initHook: dynamic = run { + if (PlatformUtils.IS_NODE) engines.append(CIO) +} diff --git a/ktor-client/ktor-client-cio/jvm/src/io/ktor/client/engine/cio/LoaderJvm.kt b/ktor-client/ktor-client-cio/jvm/src/io/ktor/client/engine/cio/LoaderJvm.kt deleted file mode 100644 index 2f48c8228ca..00000000000 --- a/ktor-client/ktor-client-cio/jvm/src/io/ktor/client/engine/cio/LoaderJvm.kt +++ /dev/null @@ -1,8 +0,0 @@ -/* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ - -package io.ktor.client.engine.cio - -internal actual fun addToLoader() { -} diff --git a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/Loader.kt b/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/Loader.kt deleted file mode 100644 index 727472064ec..00000000000 --- a/ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/Loader.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ - -package io.ktor.client.engine.cio - -internal expect fun addToLoader() diff --git a/ktor-client/ktor-client-cio/posix/src/io/ktor/client/engine/cio/ConnectionPipelineNative.kt b/ktor-client/ktor-client-cio/nonJvm/src/io/ktor/client/engine/cio/ConnectionPipeline.nonJvm.kt similarity index 83% rename from ktor-client/ktor-client-cio/posix/src/io/ktor/client/engine/cio/ConnectionPipelineNative.kt rename to ktor-client/ktor-client-cio/nonJvm/src/io/ktor/client/engine/cio/ConnectionPipeline.nonJvm.kt index 1c9c9baf521..a2c8a5d4381 100644 --- a/ktor-client/ktor-client-cio/posix/src/io/ktor/client/engine/cio/ConnectionPipelineNative.kt +++ b/ktor-client/ktor-client-cio/nonJvm/src/io/ktor/client/engine/cio/ConnectionPipeline.nonJvm.kt @@ -21,9 +21,9 @@ internal actual class ConnectionPipeline actual constructor( actual override val coroutineContext: CoroutineContext = parentContext init { - error("Pipelining is not supported in native CIO") + error("Pipelining is not supported in native/js/wasm CIO") } actual val pipelineContext: Job - get() = error("Pipelining is not supported in native CIO") + get() = error("Pipelining is not supported in native/js/wasm CIO") } diff --git a/ktor-client/ktor-client-cio/posix/src/io/ktor/client/engine/cio/ExceptionUtilsNative.kt b/ktor-client/ktor-client-cio/nonJvm/src/io/ktor/client/engine/cio/ExceptionUtils.nonJvm.kt similarity index 100% rename from ktor-client/ktor-client-cio/posix/src/io/ktor/client/engine/cio/ExceptionUtilsNative.kt rename to ktor-client/ktor-client-cio/nonJvm/src/io/ktor/client/engine/cio/ExceptionUtils.nonJvm.kt diff --git a/ktor-client/ktor-client-cio/posix/src/io/ktor/client/engine/cio/LoaderNative.kt b/ktor-client/ktor-client-cio/posix/src/io/ktor/client/engine/cio/Loader.posix.kt similarity index 60% rename from ktor-client/ktor-client-cio/posix/src/io/ktor/client/engine/cio/LoaderNative.kt rename to ktor-client/ktor-client-cio/posix/src/io/ktor/client/engine/cio/Loader.posix.kt index 48523ab257f..693972ec912 100644 --- a/ktor-client/ktor-client-cio/posix/src/io/ktor/client/engine/cio/LoaderNative.kt +++ b/ktor-client/ktor-client-cio/posix/src/io/ktor/client/engine/cio/Loader.posix.kt @@ -7,11 +7,7 @@ package io.ktor.client.engine.cio import io.ktor.client.engine.* import io.ktor.utils.io.* -@OptIn(ExperimentalStdlibApi::class) +@Suppress("DEPRECATION") +@OptIn(ExperimentalStdlibApi::class, InternalAPI::class) @EagerInitialization -private val initHook = CIO - -@OptIn(InternalAPI::class) -internal actual fun addToLoader() { - engines.append(CIO) -} +private val initHook = engines.append(CIO) diff --git a/ktor-client/ktor-client-cio/wasmJs/src/io/ktor/client/engine/cio/Loader.wasmJs.kt b/ktor-client/ktor-client-cio/wasmJs/src/io/ktor/client/engine/cio/Loader.wasmJs.kt new file mode 100644 index 00000000000..e4aae973489 --- /dev/null +++ b/ktor-client/ktor-client-cio/wasmJs/src/io/ktor/client/engine/cio/Loader.wasmJs.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.client.engine.cio + +import io.ktor.client.engine.* +import io.ktor.util.* +import io.ktor.utils.io.* + +@Suppress("DEPRECATION") +@OptIn(InternalAPI::class, ExperimentalStdlibApi::class) +@EagerInitialization +private val initHook: Unit = run { + if (PlatformUtils.IS_NODE) engines.append(CIO) +} 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 854e339bc84..0ea6d0e80bc 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 @@ -1161,6 +1161,11 @@ final object io.ktor.client.engine/ProxyBuilder { // io.ktor.client.engine/Proxy final fun socks(kotlin/String, kotlin/Int): io.ktor.client.engine/ProxyConfig // io.ktor.client.engine/ProxyBuilder.socks|socks(kotlin.String;kotlin.Int){}[0] } +final object io.ktor.client.engine/engines : kotlin.collections/Iterable> { // io.ktor.client.engine/engines|null[0] + final fun append(io.ktor.client.engine/HttpClientEngineFactory) // io.ktor.client.engine/engines.append|append(io.ktor.client.engine.HttpClientEngineFactory){}[0] + final fun iterator(): kotlin.collections/Iterator> // io.ktor.client.engine/engines.iterator|iterator(){}[0] +} + final object io.ktor.client.plugins.api/Send : io.ktor.client.plugins.api/ClientHook> { // io.ktor.client.plugins.api/Send|null[0] final fun install(io.ktor.client/HttpClient, kotlin.coroutines/SuspendFunction2) // io.ktor.client.plugins.api/Send.install|install(io.ktor.client.HttpClient;kotlin.coroutines.SuspendFunction2){}[0] @@ -1512,12 +1517,6 @@ final suspend inline fun <#A: reified kotlin/Any?> (io.ktor.client.plugins.webso final suspend inline fun <#A: reified kotlin/Any?> (io.ktor.client.plugins.websocket/DefaultClientWebSocketSession).io.ktor.client.plugins.websocket/sendSerialized(#A) // io.ktor.client.plugins.websocket/sendSerialized|sendSerialized@io.ktor.client.plugins.websocket.DefaultClientWebSocketSession(0:0){0§}[0] final suspend inline fun <#A: reified kotlin/Any?> (io.ktor.client.statement/HttpResponse).io.ktor.client.call/body(): #A // io.ktor.client.call/body|body@io.ktor.client.statement.HttpResponse(){0§}[0] -// Targets: [native] -final object io.ktor.client.engine/engines : kotlin.collections/Iterable> { // io.ktor.client.engine/engines|null[0] - final fun append(io.ktor.client.engine/HttpClientEngineFactory) // io.ktor.client.engine/engines.append|append(io.ktor.client.engine.HttpClientEngineFactory){}[0] - final fun iterator(): kotlin.collections/Iterator> // io.ktor.client.engine/engines.iterator|iterator(){}[0] -} - // Targets: [js, wasmJs] abstract interface io.ktor.client.fetch/AbortSignal : io.ktor.client.fetch/EventTarget { // io.ktor.client.fetch/AbortSignal|null[0] abstract var aborted // io.ktor.client.fetch/AbortSignal.aborted|{}aborted[0] @@ -1786,6 +1785,9 @@ open class io.ktor.client.engine.js/JsClientEngineConfig : io.ktor.client.engine // Targets: [js, wasmJs] final object io.ktor.client.engine.js/Js : io.ktor.client.engine/HttpClientEngineFactory { // io.ktor.client.engine.js/Js|null[0] final fun create(kotlin/Function1): io.ktor.client.engine/HttpClientEngine // io.ktor.client.engine.js/Js.create|create(kotlin.Function1){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // io.ktor.client.engine.js/Js.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // io.ktor.client.engine.js/Js.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // io.ktor.client.engine.js/Js.toString|toString(){}[0] } // Targets: [js, wasmJs] @@ -2179,6 +2181,10 @@ abstract interface io.ktor.client.fetch/Uint8ArrayConstructor { // io.ktor.clien abstract fun of(kotlin/Array...): io.ktor.client.fetch/Uint8Array // io.ktor.client.fetch/Uint8ArrayConstructor.of|of(kotlin.Array...){}[0] } +// Targets: [js] +final val io.ktor.client.engine.js/initHook // io.ktor.client.engine.js/initHook|{}initHook[0] + final fun (): dynamic // io.ktor.client.engine.js/initHook.|(){}[0] + // Targets: [js] final inline fun (io.ktor.client.fetch/Uint8Array).io.ktor.client.fetch/get(kotlin/Number): kotlin/Number? // io.ktor.client.fetch/get|get@io.ktor.client.fetch.Uint8Array(kotlin.Number){}[0] diff --git a/ktor-client/ktor-client-core/build.gradle.kts b/ktor-client/ktor-client-core/build.gradle.kts index 19fdad7c0a8..f11621825cf 100644 --- a/ktor-client/ktor-client-core/build.gradle.kts +++ b/ktor-client/ktor-client-core/build.gradle.kts @@ -8,6 +8,7 @@ kotlin.sourceSets { commonMain { dependencies { api(project(":ktor-http")) + api(project(":ktor-http:ktor-http-cio")) api(project(":ktor-shared:ktor-events")) api(project(":ktor-shared:ktor-websocket-serialization")) api(project(":ktor-shared:ktor-sse")) diff --git a/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt b/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt index 874ff130392..75aabe09998 100644 --- a/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt +++ b/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.client @@ -1449,7 +1449,7 @@ public class HttpClient( @Suppress("UNCHECKED_CAST") val plugin = installedFeatures[key as AttributeKey] - if (plugin is Closeable) { + if (plugin is AutoCloseable) { plugin.close() } } diff --git a/ktor-client/ktor-client-core/common/src/io/ktor/client/engine/HttpClientEngineBase.kt b/ktor-client/ktor-client-core/common/src/io/ktor/client/engine/HttpClientEngineBase.kt index 0ac6ebc76fb..8c7ec428928 100644 --- a/ktor-client/ktor-client-core/common/src/io/ktor/client/engine/HttpClientEngineBase.kt +++ b/ktor-client/ktor-client-core/common/src/io/ktor/client/engine/HttpClientEngineBase.kt @@ -1,11 +1,10 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.client.engine import io.ktor.util.* -import io.ktor.utils.io.core.* import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlin.coroutines.* @@ -39,19 +38,4 @@ public abstract class HttpClientEngineBase(private val engineName: String) : Htt public class ClientEngineClosedException(override val cause: Throwable? = null) : IllegalStateException("Client already closed") -/** - * Closes [CoroutineDispatcher] if it's [CloseableCoroutineDispatcher] or [Closeable]. - */ -@OptIn(ExperimentalCoroutinesApi::class) -private fun CoroutineDispatcher.close() { - try { - when (this) { - is CloseableCoroutineDispatcher -> close() - is Closeable -> close() - } - } catch (ignore: Throwable) { - // Some closeable dispatchers like Dispatchers.IO can't be closed. - } -} - internal expect fun ioDispatcher(): CoroutineDispatcher diff --git a/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/DefaultTransform.kt b/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/DefaultTransform.kt index 6d35d2a0f6e..3273ab2860b 100644 --- a/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/DefaultTransform.kt +++ b/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/DefaultTransform.kt @@ -8,6 +8,7 @@ import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* +import io.ktor.http.cio.* import io.ktor.http.content.* import io.ktor.util.logging.* import io.ktor.utils.io.* @@ -111,6 +112,22 @@ public fun HttpClient.defaultTransformers() { proceedWith(HttpResponseContainer(info, response.status)) } + MultiPartData::class -> { + val rawContentType = checkNotNull(context.response.headers[HttpHeaders.ContentType]) { + "No content type provided for multipart" + } + val contentType = ContentType.parse(rawContentType) + check(contentType.match(ContentType.MultiPart.FormData)) { + "Expected multipart/form-data, got $contentType" + } + + val contentLength = context.response.headers[HttpHeaders.ContentLength]?.toLong() + val body = CIOMultipartDataBase(coroutineContext, body, rawContentType, contentLength) + val parsedResponse = HttpResponseContainer(info, body) + + proceedWith(parsedResponse) + } + else -> null } if (result != null) { diff --git a/ktor-client/ktor-client-core/js/src/io/ktor/client/engine/js/Js.kt b/ktor-client/ktor-client-core/js/src/io/ktor/client/engine/js/Js.kt index e94bd74ef32..3b12c807001 100644 --- a/ktor-client/ktor-client-core/js/src/io/ktor/client/engine/js/Js.kt +++ b/ktor-client/ktor-client-core/js/src/io/ktor/client/engine/js/Js.kt @@ -1,10 +1,11 @@ /* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.client.engine.js import io.ktor.client.engine.* +import io.ktor.utils.io.* /** * A JavaScript client engine that uses the fetch API to execute requests. @@ -20,7 +21,7 @@ import io.ktor.client.engine.* * * You can learn more about client engines from [Engines](https://ktor.io/docs/http-client-engines.html). */ -public actual object Js : HttpClientEngineFactory { +public actual data object Js : HttpClientEngineFactory { override fun create(block: JsClientEngineConfig.() -> Unit): HttpClientEngine = JsClientEngine(JsClientEngineConfig().apply(block)) } @@ -45,3 +46,10 @@ public actual open class JsClientEngineConfig : HttpClientEngineConfig() { */ public var nodeOptions: dynamic = js("Object").create(null) } + +@Suppress("DEPRECATION") +@OptIn(ExperimentalStdlibApi::class, ExperimentalJsExport::class, InternalAPI::class) +@Deprecated("", level = DeprecationLevel.HIDDEN) +@JsExport +@EagerInitialization +public val initHook: dynamic = engines.append(Js) diff --git a/ktor-client/ktor-client-core/jsAndWasmShared/src/io/ktor/client/HttpClientJs.kt b/ktor-client/ktor-client-core/jsAndWasmShared/src/io/ktor/client/HttpClientJs.kt index 95c8f4c333d..db69d907cdc 100644 --- a/ktor-client/ktor-client-core/jsAndWasmShared/src/io/ktor/client/HttpClientJs.kt +++ b/ktor-client/ktor-client-core/jsAndWasmShared/src/io/ktor/client/HttpClientJs.kt @@ -1,9 +1,10 @@ /* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.client +import io.ktor.client.engine.* import io.ktor.client.engine.js.* import io.ktor.utils.io.* @@ -16,4 +17,9 @@ import io.ktor.utils.io.* @KtorDsl public actual fun HttpClient( block: HttpClientConfig<*>.() -> Unit -): HttpClient = HttpClient(JsClient(), block) +): HttpClient = HttpClient(FACTORY, block) + +// we need to fall back to the default (Js) engine if there are no other engines, +// but in the presence of other engines, they're preferred. +@OptIn(InternalAPI::class) +private val FACTORY = engines.firstOrNull { it != Js } ?: Js diff --git a/ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt b/ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt index 40887edfb05..12fef1b75e6 100644 --- a/ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt +++ b/ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.client.plugins.cache.storage @@ -10,7 +10,6 @@ import io.ktor.util.* import io.ktor.util.collections.* import io.ktor.util.date.* import io.ktor.utils.io.* -import io.ktor.utils.io.core.* import io.ktor.utils.io.jvm.javaio.* import kotlinx.coroutines.* import kotlinx.coroutines.sync.* diff --git a/ktor-client/ktor-client-core/posix/src/io/ktor/client/engine/Loader.kt b/ktor-client/ktor-client-core/nonJvm/src/io/ktor/client/engine/Loader.kt similarity index 100% rename from ktor-client/ktor-client-core/posix/src/io/ktor/client/engine/Loader.kt rename to ktor-client/ktor-client-core/nonJvm/src/io/ktor/client/engine/Loader.kt diff --git a/ktor-client/ktor-client-core/posix/src/io/ktor/client/HttpClient.kt b/ktor-client/ktor-client-core/posix/src/io/ktor/client/HttpClient.kt index 58c90f0c010..0695c91c5ce 100644 --- a/ktor-client/ktor-client-core/posix/src/io/ktor/client/HttpClient.kt +++ b/ktor-client/ktor-client-core/posix/src/io/ktor/client/HttpClient.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.client @@ -13,10 +13,13 @@ import io.ktor.utils.io.* * The [HttpClientEngine] is selected from the dependencies. * https://ktor.io/docs/http-client-engines.html */ -@OptIn(InternalAPI::class) @KtorDsl public actual fun HttpClient( block: HttpClientConfig<*>.() -> Unit -): HttpClient = engines.firstOrNull()?.let { HttpClient(it, block) } ?: error( - "Failed to find HttpClientEngineContainer. Consider adding [HttpClientEngine] implementation in dependencies." +): HttpClient = HttpClient(FACTORY, block) + +@OptIn(InternalAPI::class) +private val FACTORY = engines.firstOrNull() ?: error( + "Failed to find HTTP client engine implementation: consider adding client engine dependency. " + + "See https://ktor.io/docs/http-client-engines.html" ) diff --git a/ktor-client/ktor-client-core/wasmJs/src/io/ktor/client/engine/js/Js.kt b/ktor-client/ktor-client-core/wasmJs/src/io/ktor/client/engine/js/Js.kt index 82ff4ed746a..c134605f869 100644 --- a/ktor-client/ktor-client-core/wasmJs/src/io/ktor/client/engine/js/Js.kt +++ b/ktor-client/ktor-client-core/wasmJs/src/io/ktor/client/engine/js/Js.kt @@ -1,11 +1,12 @@ /* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.client.engine.js import io.ktor.client.engine.* -import io.ktor.client.utils.makeJsObject +import io.ktor.client.utils.* +import io.ktor.utils.io.* /** * A JavaScript client engine that uses the fetch API to execute requests. @@ -21,7 +22,7 @@ import io.ktor.client.utils.makeJsObject * * You can learn more about client engines from [Engines](https://ktor.io/docs/http-client-engines.html). */ -public actual object Js : HttpClientEngineFactory { +public actual data object Js : HttpClientEngineFactory { override fun create(block: JsClientEngineConfig.() -> Unit): HttpClientEngine = JsClientEngine(JsClientEngineConfig().apply(block)) } @@ -46,3 +47,8 @@ public actual open class JsClientEngineConfig : HttpClientEngineConfig() { */ public var nodeOptions: JsAny = makeJsObject() } + +@OptIn(InternalAPI::class, ExperimentalStdlibApi::class) +@Suppress("DEPRECATION") +@EagerInitialization +private val initHook: Unit = engines.append(Js) diff --git a/ktor-client/ktor-client-curl/desktop/test/io/ktor/client/engine/curl/test/CurlNativeTests.kt b/ktor-client/ktor-client-curl/desktop/test/io/ktor/client/engine/curl/test/CurlNativeTests.kt index 6204db99a37..366ca4da5a7 100644 --- a/ktor-client/ktor-client-curl/desktop/test/io/ktor/client/engine/curl/test/CurlNativeTests.kt +++ b/ktor-client/ktor-client-curl/desktop/test/io/ktor/client/engine/curl/test/CurlNativeTests.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.client.engine.curl.test @@ -9,9 +9,7 @@ import io.ktor.client.call.* import io.ktor.client.engine.curl.* import io.ktor.client.request.* import io.ktor.client.statement.* -import io.ktor.utils.io.core.* import kotlinx.coroutines.* -import kotlin.native.concurrent.* import kotlin.test.* class CurlNativeTests { diff --git a/ktor-client/ktor-client-curl/desktop/test/io/ktor/client/engine/curl/test/CurlProxyTest.kt b/ktor-client/ktor-client-curl/desktop/test/io/ktor/client/engine/curl/test/CurlProxyTest.kt index 73b64693d69..e0d8e2b350e 100644 --- a/ktor-client/ktor-client-curl/desktop/test/io/ktor/client/engine/curl/test/CurlProxyTest.kt +++ b/ktor-client/ktor-client-curl/desktop/test/io/ktor/client/engine/curl/test/CurlProxyTest.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.client.engine.curl.test @@ -11,7 +11,6 @@ import io.ktor.client.engine.curl.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* -import io.ktor.utils.io.core.* import kotlinx.coroutines.* import kotlin.test.* diff --git a/ktor-client/ktor-client-java/api/ktor-client-java.api b/ktor-client/ktor-client-java/api/ktor-client-java.api index 609867b39c0..e8ee2f28578 100644 --- a/ktor-client/ktor-client-java/api/ktor-client-java.api +++ b/ktor-client/ktor-client-java/api/ktor-client-java.api @@ -1,6 +1,9 @@ public final class io/ktor/client/engine/java/Java : io/ktor/client/engine/HttpClientEngineFactory { public static final field INSTANCE Lio/ktor/client/engine/java/Java; public fun create (Lkotlin/jvm/functions/Function1;)Lio/ktor/client/engine/HttpClientEngine; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class io/ktor/client/engine/java/JavaHttpConfig : io/ktor/client/engine/HttpClientEngineConfig { diff --git a/ktor-client/ktor-client-java/jvm/src/io/ktor/client/engine/java/Java.kt b/ktor-client/ktor-client-java/jvm/src/io/ktor/client/engine/java/Java.kt index 1d9fc377611..753a6362e54 100644 --- a/ktor-client/ktor-client-java/jvm/src/io/ktor/client/engine/java/Java.kt +++ b/ktor-client/ktor-client-java/jvm/src/io/ktor/client/engine/java/Java.kt @@ -25,7 +25,7 @@ import io.ktor.client.engine.* * * You can learn more about client engines from [Engines](https://ktor.io/docs/http-client-engines.html). */ -public object Java : HttpClientEngineFactory { +public data object Java : HttpClientEngineFactory { override fun create(block: JavaHttpConfig.() -> Unit): HttpClientEngine = JavaHttpEngine(JavaHttpConfig().apply(block)) } diff --git a/ktor-client/ktor-client-jetty-jakarta/api/ktor-client-jetty-jakarta.api b/ktor-client/ktor-client-jetty-jakarta/api/ktor-client-jetty-jakarta.api index faef63dd755..d7c00794a7c 100644 --- a/ktor-client/ktor-client-jetty-jakarta/api/ktor-client-jetty-jakarta.api +++ b/ktor-client/ktor-client-jetty-jakarta/api/ktor-client-jetty-jakarta.api @@ -1,6 +1,9 @@ public final class io/ktor/client/engine/jetty/jakarta/Jetty : io/ktor/client/engine/HttpClientEngineFactory { public static final field INSTANCE Lio/ktor/client/engine/jetty/jakarta/Jetty; public fun create (Lkotlin/jvm/functions/Function1;)Lio/ktor/client/engine/HttpClientEngine; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class io/ktor/client/engine/jetty/jakarta/JettyEngineConfig : io/ktor/client/engine/HttpClientEngineConfig { diff --git a/ktor-client/ktor-client-jetty-jakarta/jvm/src/io/ktor/client/engine/jetty/jakarta/Jetty.kt b/ktor-client/ktor-client-jetty-jakarta/jvm/src/io/ktor/client/engine/jetty/jakarta/Jetty.kt index 07df70ca0f2..2b5d62be189 100644 --- a/ktor-client/ktor-client-jetty-jakarta/jvm/src/io/ktor/client/engine/jetty/jakarta/Jetty.kt +++ b/ktor-client/ktor-client-jetty-jakarta/jvm/src/io/ktor/client/engine/jetty/jakarta/Jetty.kt @@ -26,7 +26,7 @@ import io.ktor.utils.io.* * * You can learn more about client engines from [Engines](https://ktor.io/docs/http-client-engines.html). */ -public object Jetty : HttpClientEngineFactory { +public data object Jetty : HttpClientEngineFactory { override fun create(block: JettyEngineConfig.() -> Unit): HttpClientEngine = JettyHttp2Engine(JettyEngineConfig().apply(block)) } diff --git a/ktor-client/ktor-client-jetty/api/ktor-client-jetty.api b/ktor-client/ktor-client-jetty/api/ktor-client-jetty.api index 3e86808ca47..9cdc9d6be2f 100644 --- a/ktor-client/ktor-client-jetty/api/ktor-client-jetty.api +++ b/ktor-client/ktor-client-jetty/api/ktor-client-jetty.api @@ -1,6 +1,9 @@ public final class io/ktor/client/engine/jetty/Jetty : io/ktor/client/engine/HttpClientEngineFactory { public static final field INSTANCE Lio/ktor/client/engine/jetty/Jetty; public fun create (Lkotlin/jvm/functions/Function1;)Lio/ktor/client/engine/HttpClientEngine; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class io/ktor/client/engine/jetty/JettyEngineConfig : io/ktor/client/engine/HttpClientEngineConfig { diff --git a/ktor-client/ktor-client-jetty/jvm/src/io/ktor/client/engine/jetty/Jetty.kt b/ktor-client/ktor-client-jetty/jvm/src/io/ktor/client/engine/jetty/Jetty.kt index 133d96eab51..d61b64a6576 100644 --- a/ktor-client/ktor-client-jetty/jvm/src/io/ktor/client/engine/jetty/Jetty.kt +++ b/ktor-client/ktor-client-jetty/jvm/src/io/ktor/client/engine/jetty/Jetty.kt @@ -26,7 +26,7 @@ import io.ktor.utils.io.* * * You can learn more about client engines from [Engines](https://ktor.io/docs/http-client-engines.html). */ -public object Jetty : HttpClientEngineFactory { +public data object Jetty : HttpClientEngineFactory { override fun create(block: JettyEngineConfig.() -> Unit): HttpClientEngine = JettyHttp2Engine(JettyEngineConfig().apply(block)) } diff --git a/ktor-client/ktor-client-okhttp/api/ktor-client-okhttp.api b/ktor-client/ktor-client-okhttp/api/ktor-client-okhttp.api index 931abbd1131..454b838c8e8 100644 --- a/ktor-client/ktor-client-okhttp/api/ktor-client-okhttp.api +++ b/ktor-client/ktor-client-okhttp/api/ktor-client-okhttp.api @@ -1,6 +1,9 @@ public final class io/ktor/client/engine/okhttp/OkHttp : io/ktor/client/engine/HttpClientEngineFactory { public static final field INSTANCE Lio/ktor/client/engine/okhttp/OkHttp; public fun create (Lkotlin/jvm/functions/Function1;)Lio/ktor/client/engine/HttpClientEngine; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class io/ktor/client/engine/okhttp/OkHttpConfig : io/ktor/client/engine/HttpClientEngineConfig { diff --git a/ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttp.kt b/ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttp.kt index b8e462f3703..d36221c4020 100644 --- a/ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttp.kt +++ b/ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttp.kt @@ -26,7 +26,7 @@ import io.ktor.client.engine.* * * You can learn more about client engines from [Engines](https://ktor.io/docs/http-client-engines.html). */ -public object OkHttp : HttpClientEngineFactory { +public data object OkHttp : HttpClientEngineFactory { override fun create(block: OkHttpConfig.() -> Unit): HttpClientEngine = OkHttpEngine(OkHttpConfig().apply(block)) } diff --git a/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/AuthTest.kt b/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/AuthTest.kt index 8809eb5f08a..828573ec60f 100644 --- a/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/AuthTest.kt +++ b/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/AuthTest.kt @@ -23,7 +23,7 @@ import kotlin.test.assertFailsWith class AuthTest : ClientLoader() { @Test - fun testDigestAuthLegacy() = clientTests(listOf("Js", "native")) { + fun testDigestAuthLegacy() = clientTests(listOf("Js", "native:*")) { config { install(Auth) { digest { @@ -43,7 +43,7 @@ class AuthTest : ClientLoader() { } @Test - fun testDigestAuth() = clientTests(listOf("Js", "native")) { + fun testDigestAuth() = clientTests(listOf("Js", "native:*")) { config { install(Auth) { digest { @@ -60,7 +60,7 @@ class AuthTest : ClientLoader() { } @Test - fun testDigestAuthPerRealm() = clientTests(listOf("Js", "native")) { + fun testDigestAuthPerRealm() = clientTests(listOf("Js", "native:*")) { config { install(Auth) { digest { @@ -84,7 +84,7 @@ class AuthTest : ClientLoader() { } @Test - fun testDigestAuthSHA256() = clientTests(listOf("Js", "native")) { + fun testDigestAuthSHA256() = clientTests(listOf("Js", "native:*")) { config { install(Auth) { digest { diff --git a/ktor-client/ktor-client-tests/build.gradle.kts b/ktor-client/ktor-client-tests/build.gradle.kts index bb37cd747f9..31519f0bd5a 100644 --- a/ktor-client/ktor-client-tests/build.gradle.kts +++ b/ktor-client/ktor-client-tests/build.gradle.kts @@ -74,9 +74,9 @@ kotlin.sourceSets { } } - jvmAndPosixTest { + commonTest { dependencies { - runtimeOnly(project(":ktor-client:ktor-client-cio")) + api(project(":ktor-client:ktor-client-cio")) } } diff --git a/ktor-client/ktor-client-tests/common/src/io/ktor/client/tests/utils/ClientLoader.kt b/ktor-client/ktor-client-tests/common/src/io/ktor/client/tests/utils/ClientLoader.kt index 47fb84bf809..9ed401b63a3 100644 --- a/ktor-client/ktor-client-tests/common/src/io/ktor/client/tests/utils/ClientLoader.kt +++ b/ktor-client/ktor-client-tests/common/src/io/ktor/client/tests/utils/ClientLoader.kt @@ -6,11 +6,18 @@ package io.ktor.client.tests.utils import io.ktor.client.engine.* import kotlinx.coroutines.test.* +import kotlin.time.* +import kotlin.time.Duration.Companion.minutes + +internal expect val enginesToTest: Iterable> +internal expect val platformName: String +internal expect fun platformDumpCoroutines() +internal expect fun platformWaitForAllCoroutines() /** * Helper interface to test client. */ -expect abstract class ClientLoader(timeoutSeconds: Int = 60) { +abstract class ClientLoader(private val timeout: Duration = 1.minutes) { /** * Perform test against all clients from dependencies. */ @@ -18,10 +25,108 @@ expect abstract class ClientLoader(timeoutSeconds: Int = 60) { skipEngines: List = emptyList(), onlyWithEngine: String? = null, block: suspend TestClientBuilder.() -> Unit - ): TestResult + ): TestResult = runTest(timeout = timeout) { + val skipPatterns = skipEngines.map { SkipEnginePattern.parse(it.lowercase()) } + + val failures: List = enginesToTest.mapNotNull { engineFactory -> + val engineName = engineFactory.toString().lowercase() + + if (shouldRun(engineName, skipPatterns, onlyWithEngine?.lowercase())) { + try { + println("Run test with engine $engineName") + // run test here + performTestWithEngine(engineFactory, this@ClientLoader, block) + null // engine test passed + } catch (cause: Throwable) { + // engine test failed, save failure to report after run for every engine. + TestFailure(engineName, cause) + } + } else { + println("Skipping test with engine $engineName") + null // engine skipped + } + } + + if (failures.isNotEmpty()) { + val message = buildString { + appendLine("Test failed for engines: ${failures.map { it.engineName }}") + failures.forEach { + appendLine("Test failed for engine '$platformName:${it.engineName}' with:") + appendLine(it.cause.stackTraceToString().prependIndent(" ")) + } + } + throw AssertionError(message) + } + } + + private fun shouldRun( + engineName: String, + skipEnginePatterns: List, + onlyWithEngine: String? + ): Boolean { + if (onlyWithEngine != null && onlyWithEngine != engineName) return false + + skipEnginePatterns.forEach { + if (it.matches(engineName)) return false + } + + return true + } /** * Print coroutines in debug mode. */ - fun dumpCoroutines() + fun dumpCoroutines(): Unit = platformDumpCoroutines() + + /** + * Issues to fix before unlock: + * 1. Pinger & Ponger in ws + * 2. Nonce generator + */ + // @After + fun waitForAllCoroutines(): Unit = platformWaitForAllCoroutines() +} + +private data class SkipEnginePattern( + val skippedPlatform: String?, // null means * or empty + val skippedEngine: String?, // null means * or empty +) { + fun matches(engineName: String): Boolean { + var result = true + if (skippedEngine != null) { + result = result && engineName == skippedEngine + } + if (result && skippedPlatform != null) { + result = result && platformName.startsWith(skippedPlatform) + } + return result + } + + companion object { + fun parse(pattern: String): SkipEnginePattern { + val parts = pattern.split(":").map { it.takeIf { it != "*" } } + val platform: String? + val engine: String? + when (parts.size) { + 1 -> { + platform = null + engine = parts[0] + } + + 2 -> { + platform = parts[0] + engine = parts[1] + } + + else -> error("Skip engine pattern should consist of two parts: PLATFORM:ENGINE or ENGINE") + } + + if (platform == null && engine == null) { + error("Skip engine pattern should consist of two parts: PLATFORM:ENGINE or ENGINE") + } + return SkipEnginePattern(platform, engine) + } + } } + +private class TestFailure(val engineName: String, val cause: Throwable) diff --git a/ktor-client/ktor-client-tests/common/src/io/ktor/client/tests/utils/CommonClientTestUtils.kt b/ktor-client/ktor-client-tests/common/src/io/ktor/client/tests/utils/CommonClientTestUtils.kt index 3efe5ea95c8..e1a039d9147 100644 --- a/ktor-client/ktor-client-tests/common/src/io/ktor/client/tests/utils/CommonClientTestUtils.kt +++ b/ktor-client/ktor-client-tests/common/src/io/ktor/client/tests/utils/CommonClientTestUtils.kt @@ -1,14 +1,11 @@ /* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_WARNING", "KDocMissingDocumentation") - package io.ktor.client.tests.utils import io.ktor.client.* import io.ktor.client.engine.* -import io.ktor.utils.io.core.* import kotlinx.coroutines.* import kotlinx.coroutines.test.* import kotlin.time.Duration.Companion.milliseconds @@ -28,44 +25,24 @@ const val TEST_WEBSOCKET_SERVER: String = "ws://127.0.0.1:8080" */ const val TCP_SERVER: String = "http://127.0.0.1:8082" -/** - * Perform test with selected client [engine]. - */ -fun testWithEngine( - engine: HttpClientEngine, - timeoutMillis: Long = 60 * 1000L, - block: suspend TestClientBuilder<*>.() -> Unit -) = testWithClient(HttpClient(engine), timeoutMillis, block) - -/** - * Perform test with selected [client]. - */ -private fun testWithClient( - client: HttpClient, - timeout: Long, - block: suspend TestClientBuilder.() -> Unit -) = runTest(timeout = timeout.milliseconds) { - val builder1 = TestClientBuilder().also { it.block() } - concurrency(builder1.concurrency) { threadId -> - repeat(builder1.repeatCount) { attempt -> - @Suppress("UNCHECKED_CAST") - client.config { builder1.config(this as HttpClientConfig) } - .use { client -> builder1.test(TestInfo(threadId, attempt), client) } - } - } - client.engine.close() -} - /** * Perform test with selected client engine [factory]. */ -@OptIn(DelicateCoroutinesApi::class) fun testWithEngine( factory: HttpClientEngineFactory, loader: ClientLoader? = null, timeoutMillis: Long = 60L * 1000L, block: suspend TestClientBuilder.() -> Unit ) = runTest(timeout = timeoutMillis.milliseconds) { + performTestWithEngine(factory, loader, block) +} + +@OptIn(DelicateCoroutinesApi::class) +suspend fun performTestWithEngine( + factory: HttpClientEngineFactory, + loader: ClientLoader? = null, + block: suspend TestClientBuilder.() -> Unit +) { val builder = TestClientBuilder().apply { block() } if (builder.dumpAfterDelay > 0 && loader != null) { diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/BodyProgressTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/BodyProgressTest.kt index 41c47af0e37..34d286b0b88 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/BodyProgressTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/BodyProgressTest.kt @@ -28,7 +28,7 @@ private val TEST_ARRAY = ByteArray(8 * 1025) { 1 } private val TEST_NAME = "123".repeat(5000) @OptIn(DelicateCoroutinesApi::class) -class BodyProgressTest : ClientLoader(timeoutSeconds = 60) { +class BodyProgressTest : ClientLoader() { @Serializable data class User(val login: String, val id: Long) diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ContentTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ContentTest.kt index 5269fb0f89a..feba14fad05 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ContentTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ContentTest.kt @@ -42,7 +42,7 @@ val testArrays = testSize.map { makeArray(it) } -class ContentTest : ClientLoader(5 * 60) { +class ContentTest : ClientLoader(5.minutes) { @Test fun testGetFormData() = clientTests { diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/EventsTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/EventsTest.kt index 55195d6d085..fbf9b12ff05 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/EventsTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/EventsTest.kt @@ -74,7 +74,7 @@ class EventsTest : ClientLoader() { } @Test - fun testRedirectEvent() = clientTests(listOf("js")) { + fun testRedirectEvent() = clientTests(listOf("Js")) { test { client -> counter.value = 0 client.monitor.subscribe(HttpResponseRedirectEvent) { diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpStatementTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpStatementTest.kt index 0371b934c74..00b8f69fd35 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpStatementTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpStatementTest.kt @@ -38,7 +38,7 @@ class HttpStatementTest : ClientLoader() { } @Test - fun testGZipFromSavedResponse() = clientTests(listOf("native:CIO")) { + fun testGZipFromSavedResponse() = clientTests(listOf("native:CIO", "web:CIO")) { config { ContentEncoding { gzip() diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpTimeoutTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpTimeoutTest.kt index fd4efed4e8f..5d6c0083e70 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpTimeoutTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpTimeoutTest.kt @@ -328,7 +328,7 @@ class HttpTimeoutTest : ClientLoader() { // Js can't configure test timeout in browser @Test - fun testRedirect() = clientTests(listOf("js")) { + fun testRedirect() = clientTests(listOf("Js")) { config { install(HttpTimeout) { requestTimeoutMillis = 10000 } } @@ -345,7 +345,7 @@ class HttpTimeoutTest : ClientLoader() { // Js can't configure test timeout in browser @Test - fun testRedirectPerRequestAttributes() = clientTests(listOf("js")) { + fun testRedirectPerRequestAttributes() = clientTests(listOf("Js")) { config { install(HttpTimeout) } @@ -430,7 +430,7 @@ class HttpTimeoutTest : ClientLoader() { } @Test - fun testConnectionRefusedException() = clientTests(listOf("Js", "native:*", "win:*")) { + fun testConnectionRefusedException() = clientTests(listOf("Js", "native:*", "jvm/win:*")) { config { install(HttpTimeout) { connectTimeoutMillis = 1000 } } @@ -447,7 +447,7 @@ class HttpTimeoutTest : ClientLoader() { } @Test - fun testSocketTimeoutRead() = clientTests(listOf("Js", "native:CIO", "Java")) { + fun testSocketTimeoutRead() = clientTests(listOf("Js", "native:CIO", "Curl", "Java")) { config { install(HttpTimeout) { socketTimeoutMillis = 1000 } } @@ -462,7 +462,9 @@ class HttpTimeoutTest : ClientLoader() { } @Test - fun testSocketTimeoutReadPerRequestAttributes() = clientTests(listOf("Js", "native:CIO", "Java", "Apache5")) { + fun testSocketTimeoutReadPerRequestAttributes() = clientTests( + listOf("Js", "native:CIO", "Curl", "Java", "Apache5") + ) { config { install(HttpTimeout) } @@ -479,7 +481,9 @@ class HttpTimeoutTest : ClientLoader() { } @Test - fun testSocketTimeoutWriteFailOnWrite() = clientTests(listOf("Js", "Android", "native:CIO", "Java")) { + fun testSocketTimeoutWriteFailOnWrite() = clientTests( + listOf("Js", "Android", "Curl", "native:CIO", "web:CIO", "Java") + ) { config { install(HttpTimeout) { socketTimeoutMillis = 500 } } @@ -493,7 +497,7 @@ class HttpTimeoutTest : ClientLoader() { @Test fun testSocketTimeoutWriteFailOnWritePerRequestAttributes() = clientTests( - listOf("Js", "Android", "Apache5", "native:CIO", "Java") + listOf("Js", "Android", "Apache5", "Curl", "native:CIO", "web:CIO", "Java") ) { config { install(HttpTimeout) diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/JsonTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/JsonTest.kt index 58b59a3ed90..1cd73017f76 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/JsonTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/JsonTest.kt @@ -22,7 +22,7 @@ class JsonTest : ClientLoader() { @OptIn(ExperimentalStdlibApi::class) @Test - fun testUserGenerics() = clientTests(listOf("js")) { + fun testUserGenerics() = clientTests(listOf("Js")) { config { install(ContentNegotiation) { json() } } 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 d9a319b375e..d9a09503802 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 @@ -351,7 +351,7 @@ class LoggingTest : ClientLoader() { } @Test - fun testLoggingWithCompression() = clientTests(listOf("native:CIO")) { + fun testLoggingWithCompression() = clientTests(listOf("Darwin", "native:CIO", "DarwinLegacy", "web:CIO")) { val testLogger = TestLogger( "REQUEST: http://127.0.0.1:8080/compression/deflate", "METHOD: HttpMethod(value=GET)", diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/MultiPartFormDataTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/MultiPartFormDataTest.kt index 74cc70ebca7..7d947df4211 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/MultiPartFormDataTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/MultiPartFormDataTest.kt @@ -4,13 +4,15 @@ package io.ktor.client.tests +import io.ktor.client.call.* import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.client.tests.utils.* import io.ktor.http.* +import io.ktor.http.content.* +import io.ktor.utils.io.* import kotlinx.io.* import kotlin.test.* -import kotlin.time.* /** * Tests client request with multi-part form data. @@ -25,7 +27,7 @@ class MultiPartFormDataTest : ClientLoader() { } @Test - fun testMultiPartFormData() = clientTests(listOf("native")) { + fun testMultiPartFormData() = clientTests(listOf("native:*")) { test { client -> val result = client.preparePost("$TEST_SERVER/multipart") { setBody( @@ -48,4 +50,41 @@ class MultiPartFormDataTest : ClientLoader() { assertTrue(response.status.isSuccess()) } } + + @Test + fun testReceiveMultiPartFormData() = clientTests { + test { client -> + val response = client.post("$TEST_SERVER/multipart/receive") + + val multipart = response.body() + var textFound = false + var fileFound = false + + multipart.forEachPart { part -> + when (part) { + is PartData.FormItem -> { + assertEquals("text", part.name) + assertEquals("Hello, World!", part.value) + textFound = true + } + is PartData.FileItem -> { + assertEquals("file", part.name) + assertEquals("test.bin", part.originalFileName) + + val bytes = part.provider().readRemaining().readByteArray() + assertEquals(1024, bytes.size) + for (i in bytes.indices) { + assertEquals(i.toByte(), bytes[i]) + } + fileFound = true + } + else -> fail("Unexpected part type: ${part::class.simpleName}") + } + part.dispose() + } + + assertTrue(textFound, "Text part not found") + assertTrue(fileFound, "File part not found") + } + } } diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ProxyTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ProxyTest.kt index 970099950b8..32f28ce918d 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ProxyTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ProxyTest.kt @@ -19,7 +19,7 @@ data class ProxyResponse(val status: String) class ProxyTest : ClientLoader() { @Test - fun testHttpProxy() = clientTests(listOf("Js")) { + fun testHttpProxy() = clientTests(listOf("Js", "web:CIO")) { config { engine { proxy = ProxyBuilder.http(TCP_SERVER) @@ -33,7 +33,7 @@ class ProxyTest : ClientLoader() { } @Test - fun testProxyWithSerialization() = clientTests(listOf("Js")) { + fun testProxyWithSerialization() = clientTests(listOf("Js", "web:CIO")) { config { engine { proxy = ProxyBuilder.http(TCP_SERVER) diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/WebSocketTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/WebSocketTest.kt index be90945596f..a087b838d39 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/WebSocketTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/WebSocketTest.kt @@ -329,7 +329,7 @@ class WebSocketTest : ClientLoader() { @Ignore // TODO KTOR-7088 @Test fun testImmediateReceiveAfterConnect() = clientTests( - ENGINES_WITHOUT_WS + "Darwin" + "js" // TODO KTOR-7088 + ENGINES_WITHOUT_WS + "Darwin" + "Js" // TODO KTOR-7088 ) { config { install(WebSockets) diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/CacheLegacyStorageTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/CacheLegacyStorageTest.kt index 536c3b38e2b..5e3f7f26c45 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/CacheLegacyStorageTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/CacheLegacyStorageTest.kt @@ -569,7 +569,7 @@ class CacheLegacyStorageTest : ClientLoader() { } @Test - fun testPublicAndPrivateCache() = clientTests(listOf("native")) { + fun testPublicAndPrivateCache() = clientTests(listOf("native:*")) { val publicStorage = HttpCacheStorage.Unlimited() val privateStorage = HttpCacheStorage.Unlimited() config { diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/CookiesIntegrationTests.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/CookiesIntegrationTests.kt index 6d32ae4c905..1a4ec38a9d9 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/CookiesIntegrationTests.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/CookiesIntegrationTests.kt @@ -106,7 +106,7 @@ class CookiesIntegrationTests : ClientLoader() { } @Test - fun testPath() = clientTests(listOf("js")) { + fun testPath() = clientTests(listOf("Js")) { config { install(HttpCookies) } @@ -186,7 +186,7 @@ class CookiesIntegrationTests : ClientLoader() { } @Test - fun testCookiesWithWrongValue() = clientTests(listOf("js", "Darwin", "DarwinLegacy", "WinHttp")) { + fun testCookiesWithWrongValue() = clientTests(listOf("Js", "Darwin", "DarwinLegacy", "WinHttp")) { config { install(HttpCookies) } diff --git a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/ServerSentEventsTest.kt b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/ServerSentEventsTest.kt index 5d3fcfc5229..409cfa62a12 100644 --- a/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/ServerSentEventsTest.kt +++ b/ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/plugins/ServerSentEventsTest.kt @@ -23,8 +23,9 @@ import kotlinx.coroutines.flow.* import kotlin.coroutines.* import kotlin.test.* import kotlin.test.assertFailsWith +import kotlin.time.Duration.Companion.minutes -class ServerSentEventsTest : ClientLoader(timeoutSeconds = 120) { +class ServerSentEventsTest : ClientLoader(2.minutes) { @Test fun testExceptionIfSseIsNotInstalled() = testSuspend { diff --git a/ktor-client/ktor-client-tests/js/src/io/ktor/client/tests/utils/ClientLoaderJs.kt b/ktor-client/ktor-client-tests/js/src/io/ktor/client/tests/utils/ClientLoaderJs.kt deleted file mode 100644 index 16497edf244..00000000000 --- a/ktor-client/ktor-client-tests/js/src/io/ktor/client/tests/utils/ClientLoaderJs.kt +++ /dev/null @@ -1,37 +0,0 @@ -// ktlint-disable filename -/* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.client.tests.utils - -import io.ktor.client.engine.* -import io.ktor.client.engine.js.* -import kotlinx.coroutines.* -import kotlinx.coroutines.test.* - -/** - * Helper interface to test client. - */ -actual abstract class ClientLoader actual constructor(private val timeoutSeconds: Int) { - /** - * Perform test against all clients from dependencies. - */ - @OptIn(DelicateCoroutinesApi::class) - actual fun clientTests( - skipEngines: List, - onlyWithEngine: String?, - block: suspend TestClientBuilder.() -> Unit - ): TestResult { - val skipEnginesLowerCase = skipEngines.map { it.lowercase() } - if ((onlyWithEngine != null && onlyWithEngine != "js") || skipEnginesLowerCase.contains("js")) { - return runTest { } - } - - return testWithEngine(Js, timeoutMillis = timeoutSeconds * 1000L, block = block) - } - - actual fun dumpCoroutines() { - error("Debug probes unsupported[js]") - } -} diff --git a/ktor-client/ktor-client-tests/jsAndWasmShared/src/io/ktor/client/tests/utils/ClientLoader.jsAndWasmShared.kt b/ktor-client/ktor-client-tests/jsAndWasmShared/src/io/ktor/client/tests/utils/ClientLoader.jsAndWasmShared.kt new file mode 100644 index 00000000000..81f38bb16f9 --- /dev/null +++ b/ktor-client/ktor-client-tests/jsAndWasmShared/src/io/ktor/client/tests/utils/ClientLoader.jsAndWasmShared.kt @@ -0,0 +1,13 @@ +// ktlint-disable filename +/* + * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.client.tests.utils + +import io.ktor.client.engine.* +import io.ktor.utils.io.* + +@OptIn(InternalAPI::class) +internal actual val enginesToTest: Iterable> get() = engines +internal actual val platformName: String get() = "web" diff --git a/ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/utils/ClientLoader.jvm.kt b/ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/utils/ClientLoader.jvm.kt new file mode 100644 index 00000000000..2ad36d0b601 --- /dev/null +++ b/ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/utils/ClientLoader.jvm.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.client.tests.utils + +import io.ktor.client.* +import io.ktor.client.engine.* +import kotlinx.coroutines.* +import kotlinx.coroutines.debug.* +import java.util.* + +internal actual val enginesToTest: Iterable> by lazy { + ServiceLoader.load( + HttpClientEngineContainer::class.java, + HttpClientEngineContainer::class.java.classLoader + ).map { it.factory } +} +internal actual val platformName: String by lazy { + val os = System.getProperty("os.name", "unknown").lowercase(Locale.getDefault()) + "jvm/" + when { + os.contains("win") -> "win" + os.contains("mac") -> "mac" + os.contains("nux") -> "unix" + else -> "unknown" + } +} + +@OptIn(ExperimentalCoroutinesApi::class) +internal actual fun platformDumpCoroutines() { + DebugProbes.dumpCoroutines() + + println("Thread Dump") + Thread.getAllStackTraces().forEach { (thread, stackTrace) -> + println("Thread: $thread") + stackTrace.forEach { + println("\t$it") + } + } +} + +@OptIn(ExperimentalCoroutinesApi::class) +internal actual fun platformWaitForAllCoroutines() { + check(DebugProbes.isInstalled) { + "Debug probes isn't installed." + } + + val info = DebugProbes.dumpCoroutinesInfo() + + if (info.isEmpty()) { + return + } + + val message = buildString { + appendLine("Test failed. There are running coroutines") + appendLine(info.dump()) + } + + error(message) +} + +@OptIn(ExperimentalCoroutinesApi::class) +private fun List.dump(): String = buildString { + this@dump.forEach { info -> + appendLine("Coroutine: $info") + info.lastObservedStackTrace().forEach { + appendLine("\t$it") + } + } +} diff --git a/ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/utils/ClientLoaderJvm.kt b/ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/utils/ClientLoaderJvm.kt deleted file mode 100644 index 7aaea789cb7..00000000000 --- a/ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/utils/ClientLoaderJvm.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.client.tests.utils - -import io.ktor.client.* -import io.ktor.client.engine.* -import io.ktor.util.reflect.* -import io.ktor.utils.io.* -import kotlinx.coroutines.* -import kotlinx.coroutines.debug.* -import java.util.* -import kotlin.time.Duration.Companion.seconds - -/** - * Helper interface to test client. - */ -actual abstract class ClientLoader actual constructor(val timeoutSeconds: Int) { - - @OptIn(InternalAPI::class) - private val engines: List by lazy { loadServices() } - - /** - * Perform test against all clients from dependencies. - */ - @OptIn(ExperimentalCoroutinesApi::class) - actual fun clientTests( - skipEngines: List, - onlyWithEngine: String?, - block: suspend TestClientBuilder.() -> Unit - ) { - DebugProbes.install() - for (engine in engines) { - if (shouldSkip(engine, skipEngines, onlyWithEngine)) { - continue - } - runBlocking { - withTimeout(timeoutSeconds.seconds.inWholeMilliseconds) { - testWithEngine(engine.factory, this@ClientLoader, timeoutSeconds * 1000L, block) - } - } - } - } - - fun shouldSkip(engine: HttpClientEngineContainer, skipEngines: List, onlyWithEngine: String?): Boolean = - skipEngines.any { shouldSkip(engine.toString(), it, onlyWithEngine) } - - fun shouldSkip(engineName: String, skipEngine: String, onlyWithEngine: String?): Boolean { - val locale = Locale.getDefault() - val skipEngineArray = skipEngine.lowercase(locale).split(":") - - val (platform, skipEngineName) = when (skipEngineArray.size) { - 2 -> skipEngineArray[0] to skipEngineArray[1] - 1 -> "*" to skipEngineArray[0] - else -> throw IllegalStateException("Wrong skip engine format, expected 'engine' or 'platform:engine'") - } - - val platformShouldBeSkipped = "*" == platform || OS_NAME == platform - val engineShouldBeSkipped = "*" == skipEngineName || engineName.lowercase(locale) == skipEngineName.lowercase( - locale - ) - val notOnlyEngine = onlyWithEngine != null && engineName.lowercase(locale) != onlyWithEngine.lowercase(locale) - - return (engineShouldBeSkipped && platformShouldBeSkipped) || notOnlyEngine - } - - @OptIn(ExperimentalCoroutinesApi::class) - actual fun dumpCoroutines() { - DebugProbes.dumpCoroutines() - - println("Thread Dump") - Thread.getAllStackTraces().forEach { (thread, stackTrace) -> - println("Thread: $thread") - stackTrace.forEach { - println("\t$it") - } - } - } - - /** - * Issues to fix before unlock: - * 1. Pinger & Ponger in ws - * 2. Nonce generator - */ - // @After - @OptIn(ExperimentalCoroutinesApi::class) - fun waitForAllCoroutines() { - check(DebugProbes.isInstalled) { - "Debug probes isn't installed." - } - - val info = DebugProbes.dumpCoroutinesInfo() - - if (info.isEmpty()) { - return - } - - val message = buildString { - appendLine("Test failed. There are running coroutines") - appendLine(info.dump()) - } - - error(message) - } -} - -private val OS_NAME: String - get() { - val os = System.getProperty("os.name", "unknown").lowercase(Locale.getDefault()) - return when { - os.contains("win") -> "win" - os.contains("mac") -> "mac" - os.contains("nux") -> "unix" - else -> "unknown" - } - } - -@OptIn(ExperimentalCoroutinesApi::class) -private fun List.dump(): String = buildString { - this@dump.forEach { info -> - appendLine("Coroutine: $info") - info.lastObservedStackTrace().forEach { - appendLine("\t$it") - } - } -} diff --git a/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/LoggingTestJvm.kt b/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/LoggingTestJvm.kt index 8862a94c1b9..6845097a066 100644 --- a/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/LoggingTestJvm.kt +++ b/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/LoggingTestJvm.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.slf4j.* import org.slf4j.* import kotlin.test.* +import kotlin.time.Duration.Companion.seconds private class LoggerWithMdc : Logger { val logs = mutableListOf>() @@ -27,7 +28,7 @@ private class LoggerWithMdc : Logger { } } -class LoggingTestJvm : ClientLoader(timeoutSeconds = 1000000) { +class LoggingTestJvm : ClientLoader(1000000.seconds) { @OptIn(InternalAPI::class) @Test diff --git a/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/MultithreadedTest.kt b/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/MultithreadedTest.kt index e6caa8229a0..9f6f199676f 100644 --- a/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/MultithreadedTest.kt +++ b/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/MultithreadedTest.kt @@ -10,11 +10,12 @@ import io.ktor.client.tests.utils.* import kotlinx.coroutines.* import java.util.concurrent.* import kotlin.test.* +import kotlin.time.Duration.Companion.minutes private const val TEST_SIZE = 100_000 private const val DEFAULT_THREADS_COUNT = 32 -class MultithreadedTest : ClientLoader(timeoutSeconds = 10 * 60) { +class MultithreadedTest : ClientLoader(10.minutes) { @Test @Ignore fun numberTest() = clientTests { diff --git a/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/WebSocketJvmTest.kt b/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/WebSocketJvmTest.kt index af98b6a2aff..bb7fa124340 100644 --- a/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/WebSocketJvmTest.kt +++ b/ktor-client/ktor-client-tests/jvm/test/io/ktor/client/tests/WebSocketJvmTest.kt @@ -8,10 +8,11 @@ import io.ktor.client.plugins.websocket.* import io.ktor.client.tests.utils.* import io.ktor.websocket.* import kotlin.test.* +import kotlin.time.Duration.Companion.seconds private const val TEST_SIZE: Int = 100 -class WebSocketJvmTest : ClientLoader(100000) { +class WebSocketJvmTest : ClientLoader(100000.seconds) { @Test fun testWebSocketDeflateBinary() = clientTests(listOf("Android", "Apache", "Apache5")) { diff --git a/ktor-client/ktor-client-tests/nonJvm/src/io/ktor/client/tests/utils/ClientLoader.nonJvm.kt b/ktor-client/ktor-client-tests/nonJvm/src/io/ktor/client/tests/utils/ClientLoader.nonJvm.kt new file mode 100644 index 00000000000..4ea4cbf9b6b --- /dev/null +++ b/ktor-client/ktor-client-tests/nonJvm/src/io/ktor/client/tests/utils/ClientLoader.nonJvm.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.client.tests.utils + +// supported only on JVM +internal actual fun platformDumpCoroutines() {} +internal actual fun platformWaitForAllCoroutines() {} diff --git a/ktor-client/ktor-client-tests/posix/src/io/ktor/client/tests/utils/ClientLoader.posix.kt b/ktor-client/ktor-client-tests/posix/src/io/ktor/client/tests/utils/ClientLoader.posix.kt new file mode 100644 index 00000000000..ff21e2b486f --- /dev/null +++ b/ktor-client/ktor-client-tests/posix/src/io/ktor/client/tests/utils/ClientLoader.posix.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.client.tests.utils + +import io.ktor.client.engine.* +import io.ktor.util.* +import io.ktor.utils.io.* +import kotlin.experimental.* +import kotlin.native.runtime.* + +@OptIn(InternalAPI::class) +internal actual val enginesToTest: Iterable> get() = engines +internal actual val platformName: String get() = "native" diff --git a/ktor-client/ktor-client-tests/posix/src/io/ktor/client/tests/utils/ClientLoaderNative.kt b/ktor-client/ktor-client-tests/posix/src/io/ktor/client/tests/utils/ClientLoaderNative.kt deleted file mode 100644 index 0d15345a810..00000000000 --- a/ktor-client/ktor-client-tests/posix/src/io/ktor/client/tests/utils/ClientLoaderNative.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.client.tests.utils - -import io.ktor.client.engine.* -import io.ktor.util.* -import io.ktor.utils.io.* -import kotlin.experimental.* -import kotlin.native.runtime.* - -private class TestFailure(val name: String, val cause: Throwable) { - @OptIn(ExperimentalNativeApi::class) - override fun toString(): String = buildString { - appendLine("Test failed with engine: $name") - appendLine(cause) - for (stackline in cause.getStackTrace()) { - appendLine("\t$stackline") - } - } -} - -/** - * Helper interface to test client. - */ -actual abstract class ClientLoader actual constructor(private val timeoutSeconds: Int) { - /** - * Perform test against all clients from dependencies. - */ - @OptIn(InternalAPI::class) - actual fun clientTests( - skipEngines: List, - onlyWithEngine: String?, - block: suspend TestClientBuilder.() -> Unit - ) { - if (skipEngines.any { it.startsWith("native") }) return - - val skipEnginesLowerCase = skipEngines.map { it.lowercase() }.toSet() - val filteredEngines: List> = engines.filter { - val name = it.toString().lowercase() - !skipEnginesLowerCase.contains(name) && !skipEnginesLowerCase.contains("native:$name") - } - - val failures = mutableListOf() - for (engine in filteredEngines) { - if (onlyWithEngine != null && onlyWithEngine != engine.toString()) continue - - val result = runCatching { - testWithEngine(engine, timeoutMillis = timeoutSeconds.toLong() * 1000L) { - block() - } - } - - if (result.isFailure) { - failures += TestFailure(engine.toString(), result.exceptionOrNull()!!) - } - } - - if (failures.isEmpty()) { - return - } - - error(failures.joinToString("\n")) - } - - actual fun dumpCoroutines() { - error("Debug probes unsupported native.") - } -} diff --git a/ktor-client/ktor-client-tests/wasmJs/src/io/ktor/client/tests/utils/ClientLoaderWasm.kt b/ktor-client/ktor-client-tests/wasmJs/src/io/ktor/client/tests/utils/ClientLoaderWasm.kt deleted file mode 100644 index ebcb2dd50c9..00000000000 --- a/ktor-client/ktor-client-tests/wasmJs/src/io/ktor/client/tests/utils/ClientLoaderWasm.kt +++ /dev/null @@ -1,41 +0,0 @@ -// ktlint-disable filename -/* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.client.tests.utils - -import io.ktor.client.engine.* -import io.ktor.client.engine.js.* -import kotlinx.coroutines.* -import kotlinx.coroutines.test.* - -/** - * Helper interface to test client. - */ -actual abstract class ClientLoader actual constructor(private val timeoutSeconds: Int) { - /** - * Perform test against all clients from dependencies. - */ - @OptIn(DelicateCoroutinesApi::class) - actual fun clientTests( - skipEngines: List, - onlyWithEngine: String?, - block: suspend TestClientBuilder.() -> Unit - ): TestResult { - val skipEnginesLowerCase = skipEngines.map { it.lowercase() } - return if ((onlyWithEngine != null && onlyWithEngine != "js") || skipEnginesLowerCase.contains("js")) { - runTest {} - } else { - testWithEngine(Js) { - withTimeout(timeoutSeconds.toLong() * 1000) { - block() - } - } - } - } - - actual fun dumpCoroutines() { - error("Debug probes unsupported[wasm]") - } -} diff --git a/ktor-client/ktor-client-winhttp/windows/src/io/ktor/client/engine/winhttp/internal/WinHttpResponseData.kt b/ktor-client/ktor-client-winhttp/windows/src/io/ktor/client/engine/winhttp/internal/WinHttpResponseData.kt index 0271f8fb464..93df7931549 100644 --- a/ktor-client/ktor-client-winhttp/windows/src/io/ktor/client/engine/winhttp/internal/WinHttpResponseData.kt +++ b/ktor-client/ktor-client-winhttp/windows/src/io/ktor/client/engine/winhttp/internal/WinHttpResponseData.kt @@ -1,16 +1,14 @@ /* - * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.client.engine.winhttp.internal -import io.ktor.client.plugins.sse.* import io.ktor.client.request.* import io.ktor.http.* import io.ktor.http.cio.* import io.ktor.util.date.* import io.ktor.utils.io.* -import io.ktor.utils.io.core.* import kotlin.coroutines.* internal class WinHttpResponseData( diff --git a/ktor-client/ktor-client-winhttp/windows/test/io/ktor/client/engine/winhttp/WinHttpProxyTest.kt b/ktor-client/ktor-client-winhttp/windows/test/io/ktor/client/engine/winhttp/WinHttpProxyTest.kt index bd977ceb70c..a14241ae849 100644 --- a/ktor-client/ktor-client-winhttp/windows/test/io/ktor/client/engine/winhttp/WinHttpProxyTest.kt +++ b/ktor-client/ktor-client-winhttp/windows/test/io/ktor/client/engine/winhttp/WinHttpProxyTest.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.client.engine.winhttp @@ -8,7 +8,6 @@ import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.engine.* import io.ktor.client.request.* -import io.ktor.utils.io.core.* import kotlinx.coroutines.* import kotlin.test.* diff --git a/ktor-http/api/ktor-http.api b/ktor-http/api/ktor-http.api index f369eca9d6a..3f490eb1bf2 100644 --- a/ktor-http/api/ktor-http.api +++ b/ktor-http/api/ktor-http.api @@ -262,7 +262,7 @@ public final class io/ktor/http/ContentTypesKt { public static final fun withCharsetIfNeeded (Lio/ktor/http/ContentType;Ljava/nio/charset/Charset;)Lio/ktor/http/ContentType; } -public final class io/ktor/http/Cookie { +public final class io/ktor/http/Cookie : java/io/Serializable { public static final field Companion Lio/ktor/http/Cookie$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Lio/ktor/http/CookieEncoding;Ljava/lang/Integer;Lio/ktor/util/date/GMTDate;Ljava/lang/String;Ljava/lang/String;ZZLjava/util/Map;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/ktor/http/CookieEncoding;Ljava/lang/Integer;Lio/ktor/util/date/GMTDate;Ljava/lang/String;Ljava/lang/String;ZZLjava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -966,7 +966,7 @@ public final class io/ktor/http/URLParserKt { public static final fun takeFrom (Lio/ktor/http/URLBuilder;Ljava/lang/String;)Lio/ktor/http/URLBuilder; } -public final class io/ktor/http/URLProtocol { +public final class io/ktor/http/URLProtocol : java/io/Serializable { public static final field Companion Lio/ktor/http/URLProtocol$Companion; public fun (Ljava/lang/String;I)V public final fun component1 ()Ljava/lang/String; @@ -1026,7 +1026,7 @@ public final class io/ktor/http/UnsafeHeaderException : java/lang/IllegalArgumen public fun (Ljava/lang/String;)V } -public final class io/ktor/http/Url { +public final class io/ktor/http/Url : java/io/Serializable { public static final field Companion Lio/ktor/http/Url$Companion; public fun equals (Ljava/lang/Object;)Z public final fun getEncodedFragment ()Ljava/lang/String; @@ -1053,6 +1053,7 @@ public final class io/ktor/http/Url { } public final class io/ktor/http/Url$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class io/ktor/http/UrlKt { @@ -1060,6 +1061,15 @@ public final class io/ktor/http/UrlKt { public static final fun getProtocolWithAuthority (Lio/ktor/http/Url;)Ljava/lang/String; } +public final class io/ktor/http/UrlSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Lio/ktor/http/UrlSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/ktor/http/Url; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/ktor/http/Url;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + public final class io/ktor/http/auth/AuthScheme { public static final field Basic Ljava/lang/String; public static final field Bearer Ljava/lang/String; diff --git a/ktor-http/api/ktor-http.klib.api b/ktor-http/api/ktor-http.klib.api index 7bd6b70711b..c4372c4f9a4 100644 --- a/ktor-http/api/ktor-http.klib.api +++ b/ktor-http/api/ktor-http.klib.api @@ -573,7 +573,7 @@ final class io.ktor.http/ContentType : io.ktor.http/HeaderValueWithParameters { } } -final class io.ktor.http/Cookie { // io.ktor.http/Cookie|null[0] +final class io.ktor.http/Cookie : io.ktor.utils.io/JvmSerializable { // io.ktor.http/Cookie|null[0] constructor (kotlin/String, kotlin/String, io.ktor.http/CookieEncoding = ..., kotlin/Int? = ..., io.ktor.util.date/GMTDate? = ..., kotlin/String? = ..., kotlin/String? = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin.collections/Map = ...) // io.ktor.http/Cookie.|(kotlin.String;kotlin.String;io.ktor.http.CookieEncoding;kotlin.Int?;io.ktor.util.date.GMTDate?;kotlin.String?;kotlin.String?;kotlin.Boolean;kotlin.Boolean;kotlin.collections.Map){}[0] final val domain // io.ktor.http/Cookie.domain|{}domain[0] @@ -1048,7 +1048,7 @@ final class io.ktor.http/URLParserException : kotlin/IllegalStateException { // constructor (kotlin/String, kotlin/Throwable) // io.ktor.http/URLParserException.|(kotlin.String;kotlin.Throwable){}[0] } -final class io.ktor.http/URLProtocol { // io.ktor.http/URLProtocol|null[0] +final class io.ktor.http/URLProtocol : io.ktor.utils.io/JvmSerializable { // io.ktor.http/URLProtocol|null[0] constructor (kotlin/String, kotlin/Int) // io.ktor.http/URLProtocol.|(kotlin.String;kotlin.Int){}[0] final val defaultPort // io.ktor.http/URLProtocol.defaultPort|{}defaultPort[0] @@ -1085,7 +1085,7 @@ final class io.ktor.http/UnsafeHeaderException : kotlin/IllegalArgumentException constructor (kotlin/String) // io.ktor.http/UnsafeHeaderException.|(kotlin.String){}[0] } -final class io.ktor.http/Url { // io.ktor.http/Url|null[0] +final class io.ktor.http/Url : io.ktor.utils.io/JvmSerializable { // io.ktor.http/Url|null[0] final val encodedFragment // io.ktor.http/Url.encodedFragment|{}encodedFragment[0] final fun (): kotlin/String // io.ktor.http/Url.encodedFragment.|(){}[0] final val encodedPassword // io.ktor.http/Url.encodedPassword|{}encodedPassword[0] @@ -1129,7 +1129,9 @@ final class io.ktor.http/Url { // io.ktor.http/Url|null[0] final fun hashCode(): kotlin/Int // io.ktor.http/Url.hashCode|hashCode(){}[0] final fun toString(): kotlin/String // io.ktor.http/Url.toString|toString(){}[0] - final object Companion // io.ktor.http/Url.Companion|null[0] + final object Companion { // io.ktor.http/Url.Companion|null[0] + final fun serializer(): kotlinx.serialization/KSerializer // io.ktor.http/Url.Companion.serializer|serializer(){}[0] + } } sealed class io.ktor.http.auth/HttpAuthHeader { // io.ktor.http.auth/HttpAuthHeader|null[0] @@ -1580,6 +1582,14 @@ final object io.ktor.http/HttpHeaders { // io.ktor.http/HttpHeaders|null[0] final fun isUnsafe(kotlin/String): kotlin/Boolean // io.ktor.http/HttpHeaders.isUnsafe|isUnsafe(kotlin.String){}[0] } +final object io.ktor.http/UrlSerializer : kotlinx.serialization/KSerializer { // io.ktor.http/UrlSerializer|null[0] + final val descriptor // io.ktor.http/UrlSerializer.descriptor|{}descriptor[0] + final fun (): kotlinx.serialization.descriptors/SerialDescriptor // io.ktor.http/UrlSerializer.descriptor.|(){}[0] + + final fun deserialize(kotlinx.serialization.encoding/Decoder): io.ktor.http/Url // io.ktor.http/UrlSerializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0] + final fun serialize(kotlinx.serialization.encoding/Encoder, io.ktor.http/Url) // io.ktor.http/UrlSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;io.ktor.http.Url){}[0] +} + final const val io.ktor.http/DEFAULT_PORT // io.ktor.http/DEFAULT_PORT|{}DEFAULT_PORT[0] final fun (): kotlin/Int // io.ktor.http/DEFAULT_PORT.|(){}[0] diff --git a/ktor-http/build.gradle.kts b/ktor-http/build.gradle.kts index 0ec58b576f7..bb98a0ec6c1 100644 --- a/ktor-http/build.gradle.kts +++ b/ktor-http/build.gradle.kts @@ -14,5 +14,12 @@ kotlin { api(libs.kotlinx.serialization.core) } } + jvmTest { + dependencies { + implementation(project(":ktor-shared:ktor-junit")) + implementation(project(":ktor-shared:ktor-serialization:ktor-serialization-kotlinx")) + implementation(project(":ktor-shared:ktor-serialization:ktor-serialization-kotlinx:ktor-serialization-kotlinx-json")) + } + } } } diff --git a/ktor-http/common/src/io/ktor/http/Cookie.kt b/ktor-http/common/src/io/ktor/http/Cookie.kt index 83b16e6d562..6872c1d466a 100644 --- a/ktor-http/common/src/io/ktor/http/Cookie.kt +++ b/ktor-http/common/src/io/ktor/http/Cookie.kt @@ -2,10 +2,13 @@ * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ +@file:OptIn(InternalAPI::class) + package io.ktor.http import io.ktor.util.* import io.ktor.util.date.* +import io.ktor.utils.io.* import kotlinx.serialization.* import kotlin.jvm.* @@ -37,7 +40,17 @@ public data class Cookie( val secure: Boolean = false, val httpOnly: Boolean = false, val extensions: Map = emptyMap() -) +) : JvmSerializable { + private fun writeReplace(): Any = JvmSerializerReplacement(CookieJvmSerializer, this) +} + +internal object CookieJvmSerializer : JvmSerializer { + override fun jvmSerialize(value: Cookie): ByteArray = + renderSetCookieHeader(value).encodeToByteArray() + + override fun jvmDeserialize(value: ByteArray): Cookie = + parseServerSetCookieHeader(value.decodeToString()) +} /** * Cooke encoding strategy diff --git a/ktor-http/common/src/io/ktor/http/Mimes.kt b/ktor-http/common/src/io/ktor/http/Mimes.kt index c1da7de818e..4e9d52f09b7 100644 --- a/ktor-http/common/src/io/ktor/http/Mimes.kt +++ b/ktor-http/common/src/io/ktor/http/Mimes.kt @@ -1,1238 +1,1014 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.http import io.ktor.util.* +internal const val INITIAL_MIMES_LIST_SIZE: Int = 1212 + private val rawMimes: String get() = """ -.123,application/vnd.lotus-1-2-3 -.3dmf,x-world/x-3dmf -.3dml,text/vnd.in3d.3dml -.3dm,x-world/x-3dmf -.3g2,video/3gpp2 -.3gp,video/3gpp -.7z,application/x-7z-compressed -.aab,application/x-authorware-bin -.aac,audio/aac -.aam,application/x-authorware-map -.a,application/octet-stream -.aas,application/x-authorware-seg -.abc,text/vnd.abc -.abw,application/x-abiword -.ac,application/pkix-attr-cert -.acc,application/vnd.americandynamics.acc -.ace,application/x-ace-compressed -.acgi,text/html -.acu,application/vnd.acucobol -.adp,audio/adpcm -.aep,application/vnd.audiograph -.afl,video/animaflex -.afp,application/vnd.ibm.modcap -.ahead,application/vnd.ahead.space -.ai,application/postscript -.aif,audio/aiff -.aifc,audio/aiff -.aiff,audio/aiff -.aim,application/x-aim -.aip,text/x-audiosoft-intra -.air,application/vnd.adobe.air-application-installer-package+zip -.ait,application/vnd.dvb.ait -.ami,application/vnd.amiga.ami -.ani,application/x-navi-animation -.aos,application/x-nokia-9000-communicator-add-on-software -.apk,application/vnd.android.package-archive -.application,application/x-ms-application -,application/pgp-encrypted -.apr,application/vnd.lotus-approach -.aps,application/mime -.arc,application/octet-stream -.arj,application/arj -.arj,application/octet-stream -.art,image/x-jg -.asf,video/x-ms-asf -.asm,text/x-asm -.aso,application/vnd.accpac.simply.aso -.asp,text/asp -.asx,application/x-mplayer2 -.asx,video/x-ms-asf -.asx,video/x-ms-asf-plugin -.atc,application/vnd.acucorp -.atomcat,application/atomcat+xml -.atomsvc,application/atomsvc+xml -.atom,application/atom+xml -.atx,application/vnd.antix.game-component -.au,audio/basic -.au,audio/x-au -.avi,video/avi -.avi,video/msvideo -.avi,video/x-msvideo -.avif,image/avif -.avifs,image/avif -.avs,video/avs-video -.aw,application/applixware -.azf,application/vnd.airzip.filesecure.azf -.azs,application/vnd.airzip.filesecure.azs -.azw,application/vnd.amazon.ebook -.bcpio,application/x-bcpio -.bdf,application/x-font-bdf -.bdm,application/vnd.syncml.dm+wbxml -.bed,application/vnd.realvnc.bed -.bh2,application/vnd.fujitsu.oasysprs -.bin,application/macbinary -.bin,application/mac-binary -.bin,application/octet-stream -.bin,application/x-binary -.bin,application/x-macbinary -.bmi,application/vnd.bmi -.bm,image/bmp -.bmp,image/bmp -.bmp,image/x-windows-bmp -.boo,application/book -.book,application/book -.box,application/vnd.previewsystems.box -.boz,application/x-bzip2 -.bsh,application/x-bsh -.btif,image/prs.btif -.bz2,application/x-bzip2 -.bz,application/x-bzip -.c11amc,application/vnd.cluetrust.cartomobile-config -.c11amz,application/vnd.cluetrust.cartomobile-config-pkg -.c4g,application/vnd.clonk.c4group -.cab,application/vnd.ms-cab-compressed -.car,application/vnd.curl.car -.cat,application/vnd.ms-pki.seccat -.ccad,application/clariscad -.cco,application/x-cocoa -.cc,text/plain -.cc,text/x-c -.ccxml,application/ccxml+xml, -.cdbcmsg,application/vnd.contact.cmsg -.cdf,application/cdf -.cdf,application/x-cdf -.cdf,application/x-netcdf -.cdkey,application/vnd.mediastation.cdkey -.cdmia,application/cdmi-capability -.cdmic,application/cdmi-container -.cdmid,application/cdmi-domain -.cdmio,application/cdmi-object -.cdmiq,application/cdmi-queue -.cdx,chemical/x-cdx -.cdxml,application/vnd.chemdraw+xml -.cdy,application/vnd.cinderella -.cer,application/pkix-cert -.cgm,image/cgm -.cha,application/x-chat -.chat,application/x-chat -.chm,application/vnd.ms-htmlhelp -.chrt,application/vnd.kde.kchart -.cif,chemical/x-cif -.cii,application/vnd.anser-web-certificate-issue-initiation -.cil,application/vnd.ms-artgalry -.cla,application/vnd.claymore -.class,application/java -.class,application/java-byte-code -.class,application/java-vm -.class,application/x-java-class -.clkk,application/vnd.crick.clicker.keyboard -.clkp,application/vnd.crick.clicker.palette -.clkt,application/vnd.crick.clicker.template -.clkw,application/vnd.crick.clicker.wordbank -.clkx,application/vnd.crick.clicker -.clp,application/x-msclip -.cmc,application/vnd.cosmocaller -.cmdf,chemical/x-cmdf -.cml,chemical/x-cml -.cmp,application/vnd.yellowriver-custom-menu -.cmx,image/x-cmx -.cod,application/vnd.rim.cod -.collection,font/collection -.com,application/octet-stream -.com,text/plain -.conf,text/plain -.cpio,application/x-cpio -.cpp,text/x-c -.cpt,application/mac-compactpro -.cpt,application/x-compactpro -.cpt,application/x-cpt -.crd,application/x-mscardfile -.crl,application/pkcs-crl -.crl,application/pkix-crl -.crt,application/pkix-cert -.crt,application/x-x509-ca-cert -.crt,application/x-x509-user-cert -.cryptonote,application/vnd.rig.cryptonote -.csh,application/x-csh -.csh,text/x-script.csh -.csml,chemical/x-csml -.csp,application/vnd.commonspace -.css,text/css -.csv,text/csv -.c,text/plain -.c++,text/plain -.c,text/x-c -.cu,application/cu-seeme -.curl,text/vnd.curl -.cww,application/prs.cww -.cxx,text/plain -.dat,binary/octet-stream -.dae,model/vnd.collada+xml -.daf,application/vnd.mobius.daf -.davmount,application/davmount+xml -.dcr,application/x-director -.dcurl,text/vnd.curl.dcurl -.dd2,application/vnd.oma.dd2+xml -.ddd,application/vnd.fujixerox.ddd -.deb,application/x-debian-package -.deepv,application/x-deepv -.def,text/plain -.der,application/x-x509-ca-cert -.dfac,application/vnd.dreamfactory -.dif,video/x-dv -.dir,application/x-director -.dis,application/vnd.mobius.dis -.djvu,image/vnd.djvu -.dl,video/dl -.dl,video/x-dl -.dna,application/vnd.dna -.doc,application/msword -.docm,application/vnd.ms-word.document.macroenabled.12 -.docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document -.dot,application/msword -.dotm,application/vnd.ms-word.template.macroenabled.12 -.dotx,application/vnd.openxmlformats-officedocument.wordprocessingml.template -.dp,application/commonground -.dp,application/vnd.osgi.dp -.dpg,application/vnd.dpgraph -.dra,audio/vnd.dra -.drw,application/drafting -.dsc,text/prs.lines.tag -.dssc,application/dssc+der -.dtb,application/x-dtbook+xml -.dtd,application/xml-dtd -.dts,audio/vnd.dts -.dtshd,audio/vnd.dts.hd -.dump,application/octet-stream -.dvi,application/x-dvi -.dv,video/x-dv -.dwf,model/vnd.dwf -.dwg,application/acad -.dwg,image/vnd.dwg -.dwg,image/x-dwg -.dxf,application/dxf -.dxf,image/vnd.dwg -.dxf,image/vnd.dxf -.dxf,image/x-dwg -.dxp,application/vnd.spotfire.dxp -.dxr,application/x-director -.ecelp4800,audio/vnd.nuera.ecelp4800 -.ecelp7470,audio/vnd.nuera.ecelp7470 -.ecelp9600,audio/vnd.nuera.ecelp9600 -.edm,application/vnd.novadigm.edm -.edx,application/vnd.novadigm.edx -.efif,application/vnd.picsel -.ei6,application/vnd.pg.osasli -.elc,application/x-elc -.el,text/x-script.elisp -.eml,message/rfc822 -.emma,application/emma+xml -.env,application/x-envoy -.eol,audio/vnd.digital-winds -.eot,application/vnd.ms-fontobject -.eps,application/postscript -.epub,application/epub+zip -.es3,application/vnd.eszigno3+xml -.es,application/ecmascript -.es,application/x-esrehber -.esf,application/vnd.epson.esf -.etx,text/x-setext -.evy,application/envoy -.evy,application/x-envoy -.exe,application/octet-stream -.exe,application/x-msdownload -.exi,application/exi -.ext,application/vnd.novadigm.ext -.ez2,application/vnd.ezpix-album -.ez3,application/vnd.ezpix-package -.f4v,video/x-f4v -.f77,text/x-fortran -.f90,text/plain -.f90,text/x-fortran -.fbs,image/vnd.fastbidsheet -.fcs,application/vnd.isac.fcs -.fdf,application/vnd.fdf -.fe_launch,application/vnd.denovo.fcselayout-link -.fg5,application/vnd.fujitsu.oasysgp -.fh,image/x-freehand -.fif,application/fractals -.fif,image/fif -.fig,application/x-xfig -.fli,video/fli -.fli,video/x-fli -.flo,application/vnd.micrografx.flo -.flo,image/florian -.flv,video/x-flv -.flw,application/vnd.kde.kivio -.flx,text/vnd.fmi.flexstor -.fly,text/vnd.fly -.fm,application/vnd.framemaker -.fmf,video/x-atomic3d-feature -.fnc,application/vnd.frogans.fnc -.for,text/plain -.for,text/x-fortran -.fpx,image/vnd.fpx -.fpx,image/vnd.net-fpx -.frl,application/freeloader -.fsc,application/vnd.fsc.weblaunch -.fst,image/vnd.fst -.ftc,application/vnd.fluxtime.clip -.f,text/plain -.f,text/x-fortran -.fti,application/vnd.anser-web-funds-transfer-initiation -.funk,audio/make -.fvt,video/vnd.fvt -.fxp,application/vnd.adobe.fxp -.fzs,application/vnd.fuzzysheet -.g2w,application/vnd.geoplan -.g3,image/g3fax -.g3w,application/vnd.geospace -.gac,application/vnd.groove-account -.gdl,model/vnd.gdl -.geo,application/vnd.dynageo -.gex,application/vnd.geometry-explorer -.ggb,application/vnd.geogebra.file -.ggt,application/vnd.geogebra.tool -.ghf,application/vnd.groove-help -.gif,image/gif -.gim,application/vnd.groove-identity-message -.gl,video/gl -.gl,video/x-gl -.gmx,application/vnd.gmx -.gnumeric,application/x-gnumeric -.gph,application/vnd.flographit -.gqf,application/vnd.grafeq -.gram,application/srgs -.grv,application/vnd.groove-injector -.grxml,application/srgs+xml -.gsd,audio/x-gsm -.gsf,application/x-font-ghostscript -.gsm,audio/x-gsm -.gsp,application/x-gsp -.gss,application/x-gss -.gtar,application/x-gtar -.g,text/plain -.gtm,application/vnd.groove-tool-message -.gtw,model/vnd.gtw -.gv,text/vnd.graphviz -.gxt,application/vnd.geonext -.gz,application/gzip -.gz,application/x-compressed -.gz,application/x-gzip -.gzip,application/gzip -.gzip,application/x-gzip -.gzip,multipart/x-gzip -.h261,video/h261 -.h263,video/h263 -.h264,video/h264 -.hal,application/vnd.hal+xml -.hbci,application/vnd.hbci -.hdf,application/x-hdf -.help,application/x-helpfile -.heic,image/heic -.heif,image/heif -.hgl,application/vnd.hp-hpgl -.hh,text/plain -.hh,text/x-h -.hlb,text/x-script -.hlp,application/hlp -.hlp,application/winhlp -.hlp,application/x-helpfile -.hlp,application/x-winhelp -.hpg,application/vnd.hp-hpgl -.hpgl,application/vnd.hp-hpgl -.hpid,application/vnd.hp-hpid -.hps,application/vnd.hp-hps -.hqx,application/binhex -.hqx,application/binhex4 -.hqx,application/mac-binhex -.hqx,application/mac-binhex40 -.hqx,application/x-binhex40 -.hqx,application/x-mac-binhex40 -.hta,application/hta -.htc,text/x-component -.h,text/plain -.h,text/x-h -.htke,application/vnd.kenameaapp -.htmls,text/html -.html,text/html -.htm,text/html -.htt,text/webviewhtml -.htx,text/html -.hvd,application/vnd.yamaha.hv-dic -.hvp,application/vnd.yamaha.hv-voice -.hvs,application/vnd.yamaha.hv-script -.i2g,application/vnd.intergeo -.icc,application/vnd.iccprofile -.ice,x-conference/x-cooltalk -.ico,image/x-icon -.ics,text/calendar -.idc,text/plain -.ief,image/ief -.iefs,image/ief -.iff,application/iff -.ifm,application/vnd.shana.informed.formdata -.iges,application/iges -.iges,model/iges -.igl,application/vnd.igloader -.igm,application/vnd.insors.igm -.igs,application/iges -.igs,model/iges -.igx,application/vnd.micrografx.igx -.iif,application/vnd.shana.informed.interchange -.ima,application/x-ima -.imap,application/x-httpd-imap -.imp,application/vnd.accpac.simply.imp -.ims,application/vnd.ms-ims -.inf,application/inf -.ins,application/x-internett-signup -.ip,application/x-ip2 -.ipfix,application/ipfix -.ipk,application/vnd.shana.informed.package -.irm,application/vnd.ibm.rights-management -.irp,application/vnd.irepository.package+xml -.isu,video/x-isvideo -.it,audio/it -.itp,application/vnd.shana.informed.formtemplate -.iv,application/x-inventor -.ivp,application/vnd.immervision-ivp -.ivr,i-world/i-vrml -.ivu,application/vnd.immervision-ivu -.ivy,application/x-livescreen -.jad,text/vnd.sun.j2me.app-descriptor -.jam,application/vnd.jam -.jam,audio/x-jam -.jar,application/java-archive -.java,text/plain -.java,text/x-java-source -.jav,text/plain -.jav,text/x-java-source -.jcm,application/x-java-commerce -.jfif,image/jpeg -.jfif,image/pjpeg -.jfif-tbnl,image/jpeg -.jisp,application/vnd.jisp -.jlt,application/vnd.hp-jlyt -.jnlp,application/x-java-jnlp-file -.joda,application/vnd.joost.joda-archive -.jpeg,image/jpeg -.jpe,image/jpeg -.jpg,image/jpeg -.jpgv,video/jpeg -.jpm,video/jpm -.jps,image/x-jps -.js,text/javascript -.js,application/javascript -.json,application/json -.jut,image/jutvision -.kar,audio/midi -.karbon,application/vnd.kde.karbon -.kar,music/x-karaoke -.key,application/pgp-keys -.keychain,application/octet-stream -.kfo,application/vnd.kde.kformula -.kia,application/vnd.kidspiration -.kml,application/vnd.google-earth.kml+xml -.kmz,application/vnd.google-earth.kmz -.kne,application/vnd.kinar -.kon,application/vnd.kde.kontour -.kpr,application/vnd.kde.kpresenter -.ksh,application/x-ksh -.ksh,text/x-script.ksh -.ksp,application/vnd.kde.kspread -.ktx,image/ktx -.ktz,application/vnd.kahootz -.kwd,application/vnd.kde.kword -.la,audio/nspaudio -.la,audio/x-nspaudio -.lam,audio/x-liveaudio -.lasxml,application/vnd.las.las+xml -.latex,application/x-latex -.lbd,application/vnd.llamagraphics.life-balance.desktop -.lbe,application/vnd.llamagraphics.life-balance.exchange+xml -.les,application/vnd.hhe.lesson-player -.lha,application/lha -.lha,application/x-lha -.link66,application/vnd.route66.link66+xml -.list,text/plain -.lma,audio/nspaudio -.lma,audio/x-nspaudio -.log,text/plain -.lrm,application/vnd.ms-lrm -.lsp,application/x-lisp -.lsp,text/x-script.lisp -.lst,text/plain -.lsx,text/x-la-asf -.ltf,application/vnd.frogans.ltf -.ltx,application/x-latex -.lvp,audio/vnd.lucent.voice -.lwp,application/vnd.lotus-wordpro -.lzh,application/octet-stream -.lzh,application/x-lzh -.lzx,application/lzx -.lzx,application/octet-stream -.lzx,application/x-lzx -.m1v,video/mpeg -.m21,application/mp21 -.m2a,audio/mpeg -.m2v,video/mpeg -.m3u8,application/vnd.apple.mpegurl -.m3u,audio/x-mpegurl -.m4a,audio/mp4 -.m4v,video/mp4 -.ma,application/mathematica -.mads,application/mads+xml -.mag,application/vnd.ecowin.chart -.man,application/x-troff-man -.map,application/x-navimap -.mar,text/plain -.mathml,application/mathml+xml -.mbd,application/mbedlet -.mbk,application/vnd.mobius.mbk -.mbox,application/mbox -.mc1,application/vnd.medcalcdata -.mc${'$'},application/x-magic-cap-package-1.0 -.mcd,application/mcad -.mcd,application/vnd.mcd -.mcd,application/x-mathcad -.mcf,image/vasa -.mcf,text/mcf -.mcp,application/netmc -.mcurl,text/vnd.curl.mcurl -.mdb,application/x-msaccess -.mdi,image/vnd.ms-modi -.me,application/x-troff-me -.meta4,application/metalink4+xml -.mets,application/mets+xml -.mfm,application/vnd.mfmp -.mgp,application/vnd.osgeo.mapguide.package -.mgz,application/vnd.proteus.magazine -.mht,message/rfc822 -.mhtml,message/rfc822 -.mid,application/x-midi -.mid,audio/midi -.mid,audio/x-mid -.midi,application/x-midi -.midi,audio/midi -.midi,audio/x-mid -.midi,audio/x-midi -.midi,music/crescendo -.midi,x-music/x-midi -.mid,music/crescendo -.mid,x-music/x-midi -.mif,application/vnd.mif -.mif,application/x-frame -.mif,application/x-mif -.mime,message/rfc822 -.mime,www/mime -.mj2,video/mj2 -.mjf,audio/x-vnd.audioexplosion.mjuicemediafile -.mjpg,video/x-motion-jpeg -.mjs,text/javascript -.mkv,video/x-matroska -.mkv,audio/x-matroska -.mlp,application/vnd.dolby.mlp -.mm,application/base64 -.mm,application/x-meme -.mmd,application/vnd.chipnuts.karaoke-mmd -.mme,application/base64 -.mmf,application/vnd.smaf -.mmr,image/vnd.fujixerox.edmics-mmr -.mny,application/x-msmoney -.mod,audio/mod -.mod,audio/x-mod -.mods,application/mods+xml -.moov,video/quicktime -.movie,video/x-sgi-movie -.mov,video/quicktime -.mp2,audio/mpeg -.mp2,audio/x-mpeg -.mp2,video/mpeg -.mp2,video/x-mpeg -.mp2,video/x-mpeq2a -.mp3,audio/mpeg -.mp3,audio/mpeg3 -.mp4a,audio/mp4 -.mp4,video/mp4 -.mp4,application/mp4 -.mpa,audio/mpeg -.mpc,application/vnd.mophun.certificate -.mpc,application/x-project -.mpeg,video/mpeg -.mpe,video/mpeg -.mpga,audio/mpeg -.mpg,video/mpeg -.mpg,audio/mpeg -.mpkg,application/vnd.apple.installer+xml -.mpm,application/vnd.blueice.multipass -.mpn,application/vnd.mophun.application -.mpp,application/vnd.ms-project -.mpt,application/x-project -.mpv,application/x-project -.mpx,application/x-project -.mpy,application/vnd.ibm.minipay -.mqy,application/vnd.mobius.mqy -.mrc,application/marc -.mrcx,application/marcxml+xml -.ms,application/x-troff-ms -.mscml,application/mediaservercontrol+xml -.mseq,application/vnd.mseq -.msf,application/vnd.epson.msf -.msg,application/vnd.ms-outlook -.msh,model/mesh -.msl,application/vnd.mobius.msl -.msty,application/vnd.muvee.style -.m,text/plain -.m,text/x-m -.mts,model/vnd.mts -.mus,application/vnd.musician -.musicxml,application/vnd.recordare.musicxml+xml -.mvb,application/x-msmediaview -.mv,video/x-sgi-movie -.mwf,application/vnd.mfer -.mxf,application/mxf -.mxl,application/vnd.recordare.musicxml -.mxml,application/xv+xml -.mxs,application/vnd.triscape.mxs -.mxu,video/vnd.mpegurl -.my,audio/make -.mzz,application/x-vnd.audioexplosion.mzz -.n3,text/n3 -N/A,application/andrew-inset -.nap,image/naplps -.naplps,image/naplps -.nbp,application/vnd.wolfram.player -.nc,application/x-netcdf -.ncm,application/vnd.nokia.configuration-message -.ncx,application/x-dtbncx+xml -.n-gage,application/vnd.nokia.n-gage.symbian.install -.ngdat,application/vnd.nokia.n-gage.data -.niff,image/x-niff -.nif,image/x-niff -.nix,application/x-mix-transfer -.nlu,application/vnd.neurolanguage.nlu -.nml,application/vnd.enliven -.nnd,application/vnd.noblenet-directory -.nns,application/vnd.noblenet-sealer -.nnw,application/vnd.noblenet-web -.npx,image/vnd.net-fpx -.nsc,application/x-conference -.nsf,application/vnd.lotus-notes -.nvd,application/x-navidoc -.oa2,application/vnd.fujitsu.oasys2 -.oa3,application/vnd.fujitsu.oasys3 -.o,application/octet-stream -.oas,application/vnd.fujitsu.oasys -.obd,application/x-msbinder -.oda,application/oda -.odb,application/vnd.oasis.opendocument.database -.odc,application/vnd.oasis.opendocument.chart -.odf,application/vnd.oasis.opendocument.formula -.odft,application/vnd.oasis.opendocument.formula-template -.odg,application/vnd.oasis.opendocument.graphics -.odi,application/vnd.oasis.opendocument.image -.odm,application/vnd.oasis.opendocument.text-master -.odp,application/vnd.oasis.opendocument.presentation -.ods,application/vnd.oasis.opendocument.spreadsheet -.odt,application/vnd.oasis.opendocument.text -.oga,audio/ogg -.ogg,audio/ogg -.ogv,video/ogg -.ogx,application/ogg -.omc,application/x-omc -.omcd,application/x-omcdatamaker -.omcr,application/x-omcregerator -.onetoc,application/onenote -.opf,application/oebps-package+xml -.org,application/vnd.lotus-organizer -.osf,application/vnd.yamaha.openscoreformat -.osfpvg,application/vnd.yamaha.openscoreformat.osfpvg+xml -.otc,application/vnd.oasis.opendocument.chart-template -.otf,font/otf -.otg,application/vnd.oasis.opendocument.graphics-template -.oth,application/vnd.oasis.opendocument.text-web -.oti,application/vnd.oasis.opendocument.image-template -.otp,application/vnd.oasis.opendocument.presentation-template -.ots,application/vnd.oasis.opendocument.spreadsheet-template -.ott,application/vnd.oasis.opendocument.text-template -.oxt,application/vnd.openofficeorg.extension -.p10,application/pkcs10 -.p12,application/pkcs-12 -.p7a,application/x-pkcs7-signature -.p7b,application/x-pkcs7-certificates -.p7c,application/pkcs7-mime -.p7m,application/pkcs7-mime -.p7r,application/x-pkcs7-certreqresp -.p7s,application/pkcs7-signature -.p8,application/pkcs8 -.pages,application/vnd.apple.pages -.part,application/pro_eng -.par,text/plain-bas -.pas,text/pascal -.paw,application/vnd.pawaafile -.pbd,application/vnd.powerbuilder6 -.pbm,image/x-portable-bitmap -.pcf,application/x-font-pcf -.pcl,application/vnd.hp-pcl -.pcl,application/x-pcl -.pclxl,application/vnd.hp-pclxl -.pct,image/x-pict -.pcurl,application/vnd.curl.pcurl -.pcx,image/x-pcx -.pdb,application/vnd.palm -.pdb,chemical/x-pdb -.pdf,application/pdf -.pem,application/x-pem-file -.pfa,application/x-font-type1 -.pfr,application/font-tdpfr -.pfunk,audio/make -.pfunk,audio/make.my.funk -.pfx,application/x-pkcs12 -.pgm,image/x-portable-graymap -.pgn,application/x-chess-pgn -.pgp,application/pgp-signature -.pic,image/pict -.pict,image/pict -.pkg,application/x-newton-compatible-pkg -.pki,application/pkixcmp -.pkipath,application/pkix-pkipath -.pko,application/vnd.ms-pki.pko -.plb,application/vnd.3gpp.pic-bw-large -.plc,application/vnd.mobius.plc -.plf,application/vnd.pocketlearn -.pls,application/pls+xml -.pl,text/plain -.pl,text/x-script.perl -.plx,application/x-pixclscript -.pm4,application/x-pagemaker -.pm5,application/x-pagemaker -.pm,image/x-xpixmap -.pml,application/vnd.ctc-posml -.pm,text/x-script.perl-module -.png,image/png -.pnm,application/x-portable-anymap -.pnm,image/x-portable-anymap -.portpkg,application/vnd.macports.portpkg -.pot,application/mspowerpoint -.pot,application/vnd.ms-powerpoint -.potm,application/vnd.ms-powerpoint.template.macroenabled.12 -.potx,application/vnd.openxmlformats-officedocument.presentationml.template -.pov,model/x-pov -.ppa,application/vnd.ms-powerpoint -.ppam,application/vnd.ms-powerpoint.addin.macroenabled.12 -.ppd,application/vnd.cups-ppd -.ppm,image/x-portable-pixmap -.pps,application/mspowerpoint -.pps,application/vnd.ms-powerpoint -.ppsm,application/vnd.ms-powerpoint.slideshow.macroenabled.12 -.ppsx,application/vnd.openxmlformats-officedocument.presentationml.slideshow -.ppt,application/mspowerpoint -.ppt,application/powerpoint -.ppt,application/vnd.ms-powerpoint -.ppt,application/x-mspowerpoint -.pptm,application/vnd.ms-powerpoint.presentation.macroenabled.12 -.pptx,application/vnd.openxmlformats-officedocument.presentationml.presentation -.ppz,application/mspowerpoint -.prc,application/x-mobipocket-ebook -.pre,application/vnd.lotus-freelance -.pre,application/x-freelance -.prf,application/pics-rules -.prt,application/pro_eng -.ps,application/postscript -.psb,application/vnd.3gpp.pic-bw-small -.psd,application/octet-stream -.psd,image/vnd.adobe.photoshop -.psf,application/x-font-linux-psf -.pskcxml,application/pskc+xml -.p,text/x-pascal -.ptid,application/vnd.pvi.ptid1 -.pub,application/x-mspublisher -.pvb,application/vnd.3gpp.pic-bw-var -.pvu,paleovu/x-pv -.pwn,application/vnd.3m.post-it-notes -.pwz,application/vnd.ms-powerpoint -.pya,audio/vnd.ms-playready.media.pya -.pyc,application/x-bytecode.python -.py,text/x-script.python -.pyv,video/vnd.ms-playready.media.pyv -.qam,application/vnd.epson.quickanime -.qbo,application/vnd.intu.qbo -.qcp,audio/vnd.qcelp -.qd3d,x-world/x-3dmf -.qd3,x-world/x-3dmf -.qfx,application/vnd.intu.qfx -.qif,image/x-quicktime -.qps,application/vnd.publishare-delta-tree -.qtc,video/x-qtc -.qtif,image/x-quicktime -.qti,image/x-quicktime -.qt,video/quicktime -.qxd,application/vnd.quark.quarkxpress -.ra,audio/x-pn-realaudio -.ra,audio/x-pn-realaudio-plugin -.ra,audio/x-realaudio -.ram,audio/x-pn-realaudio -.rar,application/x-rar-compressed -.ras,application/x-cmu-raster -.ras,image/cmu-raster -.ras,image/x-cmu-raster -.rast,image/cmu-raster -.rcprofile,application/vnd.ipunplugged.rcprofile -.rdf,application/rdf+xml -.rdz,application/vnd.data-vision.rdz -.rep,application/vnd.businessobjects -.res,application/x-dtbresource+xml -.rexx,text/x-script.rexx -.rf,image/vnd.rn-realflash -.rgb,image/x-rgb -.rif,application/reginfo+xml -.rip,audio/vnd.rip -.rl,application/resource-lists+xml -.rlc,image/vnd.fujixerox.edmics-rlc -.rld,application/resource-lists-diff+xml -.rm,application/vnd.rn-realmedia -.rm,audio/x-pn-realaudio -.rmi,audio/mid -.rmm,audio/x-pn-realaudio -.rmp,audio/x-pn-realaudio -.rmp,audio/x-pn-realaudio-plugin -.rms,application/vnd.jcp.javame.midlet-rms -.rnc,application/relax-ng-compact-syntax -.rng,application/ringing-tones -.rng,application/vnd.nokia.ringing-tone -.rnx,application/vnd.rn-realplayer -.roff,application/x-troff -.rp9,application/vnd.cloanto.rp9 -.rp,image/vnd.rn-realpix -.rpm,audio/x-pn-realaudio-plugin -.rpm,application/x-rpm -.rpss,application/vnd.nokia.radio-presets -.rpst,application/vnd.nokia.radio-preset -.rq,application/sparql-query -.rs,application/rls-services+xml -.rsd,application/rsd+xml -.rss,application/rss+xml -.rtf,application/rtf -.rtf,text/rtf -.rt,text/richtext -.rt,text/vnd.rn-realtext -.rtx,application/rtf -.rtx,text/richtext -.rv,video/vnd.rn-realvideo -.s3m,audio/s3m -.saf,application/vnd.yamaha.smaf-audio -.saveme,application/octet-stream -.sbk,application/x-tbook -.sbml,application/sbml+xml -.sc,application/vnd.ibm.secure-container -.scd,application/x-msschedule -.scm,application/vnd.lotus-screencam -.scm,application/x-lotusscreencam -.scm,text/x-script.guile -.scm,text/x-script.scheme -.scm,video/x-scm -.scq,application/scvp-cv-request -.scs,application/scvp-cv-response -.scurl,text/vnd.curl.scurl -.sda,application/vnd.stardivision.draw -.sdc,application/vnd.stardivision.calc -.sdd,application/vnd.stardivision.impress -.sdf,application/octet-stream -.sdkm,application/vnd.solent.sdkm+xml -.sdml,text/plain -.sdp,application/sdp -.sdp,application/x-sdp -.sdr,application/sounder -.sdw,application/vnd.stardivision.writer -.sea,application/sea -.sea,application/x-sea -.see,application/vnd.seemail -.seed,application/vnd.fdsn.seed -.sema,application/vnd.sema -.semd,application/vnd.semd -.semf,application/vnd.semf -.ser,application/java-serialized-object -.set,application/set -.setpay,application/set-payment-initiation -.setreg,application/set-registration-initiation -.sfd-hdstx,application/vnd.hydrostatix.sof-data -.sfnt,font/sfnt -.sfs,application/vnd.spotfire.sfs -.sgl,application/vnd.stardivision.writer-global -.sgml,text/sgml -.sgml,text/x-sgml -.sgm,text/sgml -.sgm,text/x-sgml -.sh,application/x-bsh -.sh,application/x-sh -.sh,application/x-shar -.shar,application/x-bsh -.shar,application/x-shar -.shf,application/shf+xml -.sh,text/x-script.sh -.shtml,text/html -.shtml,text/x-server-parsed-html -.sid,audio/x-psid -.sis,application/vnd.symbian.install -.sit,application/x-sit -.sit,application/x-stuffit -.sitx,application/x-stuffitx -.skd,application/x-koan -.skm,application/x-koan -.skp,application/vnd.koan -.skp,application/x-koan -.skt,application/x-koan -.sl,application/x-seelogo -.sldm,application/vnd.ms-powerpoint.slide.macroenabled.12 -.sldx,application/vnd.openxmlformats-officedocument.presentationml.slide -.slt,application/vnd.epson.salt -.sm,application/vnd.stepmania.stepchart -.smf,application/vnd.stardivision.math -.smi,application/smil -.smi,application/smil+xml -.smil,application/smil -.snd,audio/basic -.snd,audio/x-adpcm -.snf,application/x-font-snf -.sol,application/solids -.spc,application/x-pkcs7-certificates -.spc,text/x-speech -.spf,application/vnd.yamaha.smaf-phrase -.spl,application/futuresplash -.spl,application/x-futuresplash -.spot,text/vnd.in3d.spot -.spp,application/scvp-vp-response -.spq,application/scvp-vp-request -.spr,application/x-sprite -.sprite,application/x-sprite -.src,application/x-wais-source -.srt,text/srt -.sru,application/sru+xml -.srx,application/sparql-results+xml -.sse,application/vnd.kodak-descriptor -.ssf,application/vnd.epson.ssf -.ssi,text/x-server-parsed-html -.ssm,application/streamingmedia -.ssml,application/ssml+xml -.sst,application/vnd.ms-pki.certstore -.st,application/vnd.sailingtracker.track -.stc,application/vnd.sun.xml.calc.template -.std,application/vnd.sun.xml.draw.template -.step,application/step -.s,text/x-asm -.stf,application/vnd.wt.stf -.sti,application/vnd.sun.xml.impress.template -.stk,application/hyperstudio -.stl,application/sla -.stl,application/vnd.ms-pki.stl -.stl,application/x-navistyle -.stp,application/step -.str,application/vnd.pg.format -.stw,application/vnd.sun.xml.writer.template -.sub,image/vnd.dvb.subtitle -.sus,application/vnd.sus-calendar -.sv4cpio,application/x-sv4cpio -.sv4crc,application/x-sv4crc -.svc,application/vnd.dvb.service -.svd,application/vnd.svd -.svf,image/vnd.dwg -.svf,image/x-dwg -.svg,image/svg+xml -.svr,application/x-world -.svr,x-world/x-svr -.swf,application/x-shockwave-flash -.swi,application/vnd.aristanetworks.swi -.sxc,application/vnd.sun.xml.calc -.sxd,application/vnd.sun.xml.draw -.sxg,application/vnd.sun.xml.writer.global -.sxi,application/vnd.sun.xml.impress -.sxm,application/vnd.sun.xml.math -.sxw,application/vnd.sun.xml.writer -.talk,text/x-speech -.tao,application/vnd.tao.intent-module-archive -.t,application/x-troff -.tar,application/x-tar -.tbk,application/toolbook -.tbk,application/x-tbook -.tcap,application/vnd.3gpp2.tcap -.tcl,application/x-tcl -.tcl,text/x-script.tcl -.tcsh,text/x-script.tcsh -.teacher,application/vnd.smart.teacher -.tei,application/tei+xml -.tex,application/x-tex -.texi,application/x-texinfo -.texinfo,application/x-texinfo -.text,text/plain -.tfi,application/thraud+xml -.tfm,application/x-tex-tfm -.tgz,application/gnutar -.tgz,application/x-compressed -.thmx,application/vnd.ms-officetheme -.tiff,image/tiff -.tif,image/tiff -.tmo,application/vnd.tmobile-livetv -.torrent,application/x-bittorrent -.tpl,application/vnd.groove-tool-template -.tpt,application/vnd.trid.tpt -.tra,application/vnd.trueapp -.tr,application/x-troff -.trm,application/x-msterminal -.tsd,application/timestamped-data -.tsi,audio/tsp-audio -.tsp,application/dsptype -.tsp,audio/tsplayer -.tsv,text/tab-separated-values -.t,text/troff -.ttf,font/ttf -.ttl,text/turtle -.turbot,image/florian -.twd,application/vnd.simtech-mindmapper -.txd,application/vnd.genomatix.tuxedo -.txf,application/vnd.mobius.txf -.txt,text/plain -.ufd,application/vnd.ufdl -.uil,text/x-uil -.umj,application/vnd.umajin -.unis,text/uri-list -.uni,text/uri-list -.unityweb,application/vnd.unity -.unv,application/i-deas -.uoml,application/vnd.uoml+xml -.uris,text/uri-list -.uri,text/uri-list -.ustar,application/x-ustar -.ustar,multipart/x-ustar -.utz,application/vnd.uiq.theme -.uu,application/octet-stream -.uue,text/x-uuencode -.uu,text/x-uuencode -.uva,audio/vnd.dece.audio -.uvh,video/vnd.dece.hd -.uvi,image/vnd.dece.graphic -.uvm,video/vnd.dece.mobile -.uvp,video/vnd.dece.pd -.uvs,video/vnd.dece.sd -.uvu,video/vnd.uvvu.mp4 -.uvv,video/vnd.dece.video -.vcd,application/x-cdlink -.vcf,text/x-vcard -.vcg,application/vnd.groove-vcard -.vcs,text/x-vcalendar -.vcx,application/vnd.vcx -.vda,application/vda -.vdo,video/vdo -.vew,application/groupwise -.vis,application/vnd.visionary -.vivo,video/vivo -.vivo,video/vnd.vivo -.viv,video/vivo -.viv,video/vnd.vivo -.vmd,application/vocaltec-media-desc -.vmf,application/vocaltec-media-file -.vob,video/dvd -.voc,audio/voc -.voc,audio/x-voc -.vos,video/vosaic -.vox,audio/voxware -.vqe,audio/x-twinvq-plugin -.vqf,audio/x-twinvq -.vql,audio/x-twinvq-plugin -.vrml,application/x-vrml -.vrml,model/vrml -.vrml,x-world/x-vrml -.vrt,x-world/x-vrt -.vsd,application/vnd.visio -.vsd,application/x-visio -.vsf,application/vnd.vsf -.vst,application/x-visio -.vsw,application/x-visio -.vtt,text/vtt -.vtu,model/vnd.vtu -.vxml,application/voicexml+xml -.w60,application/wordperfect6.0 -.w61,application/wordperfect6.1 -.w6w,application/msword -.wad,application/x-doom -.war,application/zip -.wasm,application/wasm -.wav,audio/wav -.wax,audio/x-ms-wax -.wb1,application/x-qpro -.wbmp,image/vnd.wap.wbmp -.wbs,application/vnd.criticaltools.wbs+xml -.wbxml,application/vnd.wap.wbxml -.weba,audio/webm -.web,application/vnd.xara -.webm,video/webm -.webmanifest,application/manifest+json -.webp,image/webp -.wg,application/vnd.pmi.widget -.wgt,application/widget -.wiz,application/msword -.wk1,application/x-123 -.wma,audio/x-ms-wma -.wmd,application/x-ms-wmd -.wmf,application/x-msmetafile -.wmf,windows/metafile -.wmlc,application/vnd.wap.wmlc -.wmlsc,application/vnd.wap.wmlscriptc -.wmls,text/vnd.wap.wmlscript -.wml,text/vnd.wap.wml -.wm,video/x-ms-wm -.wmv,video/x-ms-wmv -.wmx,video/x-ms-wmx -.wmz,application/x-ms-wmz -.woff,font/woff -.woff2,font/woff2 -.word,application/msword -.wp5,application/wordperfect -.wp5,application/wordperfect6.0 -.wp6,application/wordperfect -.wp,application/wordperfect -.wpd,application/vnd.wordperfect -.wpd,application/wordperfect -.wpd,application/x-wpwin -.wpl,application/vnd.ms-wpl -.wps,application/vnd.ms-works -.wq1,application/x-lotus -.wqd,application/vnd.wqd -.wri,application/mswrite -.wri,application/x-mswrite -.wri,application/x-wri -.wrl,application/x-world -.wrl,model/vrml -.wrl,x-world/x-vrml -.wrz,model/vrml -.wrz,x-world/x-vrml -.wsc,text/scriplet -.wsdl,application/wsdl+xml -.wspolicy,application/wspolicy+xml -.wsrc,application/x-wais-source -.wtb,application/vnd.webturbo -.wtk,application/x-wintalk -.wvx,video/x-ms-wvx -.x3d,application/vnd.hzn-3d-crossword -.xap,application/x-silverlight-app -.xar,application/vnd.xara -.xbap,application/x-ms-xbap -.xbd,application/vnd.fujixerox.docuworks.binder -.xbm,image/xbm -.xbm,image/x-xbitmap -.xbm,image/x-xbm -.xdf,application/xcap-diff+xml -.xdm,application/vnd.syncml.dm+xml -.xdp,application/vnd.adobe.xdp+xml -.xdr,video/x-amt-demorun -.xdssc,application/dssc+xml -.xdw,application/vnd.fujixerox.docuworks -.xenc,application/xenc+xml -.xer,application/patch-ops-error+xml -.xfdf,application/vnd.adobe.xfdf -.xfdl,application/vnd.xfdl -.xgz,xgl/drawing -.xhtml,application/xhtml+xml -.xif,image/vnd.xiff -.xla,application/excel -.xla,application/x-excel -.xla,application/x-msexcel -.xlam,application/vnd.ms-excel.addin.macroenabled.12 -.xl,application/excel -.xlb,application/excel -.xlb,application/vnd.ms-excel -.xlb,application/x-excel -.xlc,application/excel -.xlc,application/vnd.ms-excel -.xlc,application/x-excel -.xld,application/excel -.xld,application/x-excel -.xlk,application/excel -.xlk,application/x-excel -.xll,application/excel -.xll,application/vnd.ms-excel -.xll,application/x-excel -.xlm,application/excel -.xlm,application/vnd.ms-excel -.xlm,application/x-excel -.xls,application/excel -.xls,application/vnd.ms-excel -.xls,application/x-excel -.xls,application/x-msexcel -.xlsb,application/vnd.ms-excel.sheet.binary.macroenabled.12 -.xlsm,application/vnd.ms-excel.sheet.macroenabled.12 -.xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet -.xlt,application/excel -.xlt,application/x-excel -.xltm,application/vnd.ms-excel.template.macroenabled.12 -.xltx,application/vnd.openxmlformats-officedocument.spreadsheetml.template -.xlv,application/excel -.xlv,application/x-excel -.xlw,application/excel -.xlw,application/vnd.ms-excel -.xlw,application/x-excel -.xlw,application/x-msexcel -.xm,audio/xm -.xml,application/xml -.xml,text/xml -.xmz,xgl/movie -.xo,application/vnd.olpc-sugar -.xop,application/xop+xml -.xpi,application/x-xpinstall -.xpix,application/x-vnd.ls-xpix -.xpm,image/xpm -.xpm,image/x-xpixmap -.x-png,image/png -.xpr,application/vnd.is-xpr -.xps,application/vnd.ms-xpsdocument -.xpw,application/vnd.intercon.formnet -.xslt,application/xslt+xml -.xsm,application/vnd.syncml+xml -.xspf,application/xspf+xml -.xsr,video/x-amt-showrun -.xul,application/vnd.mozilla.xul+xml -.xwd,image/x-xwd -.xwd,image/x-xwindowdump -.xyz,chemical/x-pdb -.xyz,chemical/x-xyz -.xz,application/x-xz -.yaml,text/yaml -.yang,application/yang -.yin,application/yin+xml -.z,application/x-compress -.z,application/x-compressed -.zaz,application/vnd.zzazz.deck+xml -.zip,application/zip -.zip,application/x-compressed -.zip,application/x-zip-compressed -.zip,multipart/x-zip -.zir,application/vnd.zul -.zmm,application/vnd.handheld-entertainment+xml -.zoo,application/octet-stream -.zsh,text/x-script.zsh +application/acad,dwg +application/andrew-inset,ez +application/applixware,aw +application/arj,arj +application/atom+xml,atom +application/atomcat+xml,atomcat +application/atomsvc+xml,atomsvc +application/base64,mm mme +application/binhex,hqx +application/binhex4,hqx +application/book,boo book +application/ccxml+xml,ccxml +application/cdf,cdf +application/cdmi-capability,cdmia +application/cdmi-container,cdmic +application/cdmi-domain,cdmid +application/cdmi-object,cdmio +application/cdmi-queue,cdmiq +application/clariscad,ccad +application/commonground,dp +application/cu-seeme,cu +application/davmount+xml,davmount +application/drafting,drw +application/dsptype,tsp +application/dssc+der,dssc +application/dssc+xml,xdssc +application/dxf,dxf +application/emma+xml,emma +application/envoy,evy +application/epub+zip,epub +application/excel,xl xla xlb xlc xld xlk xll xlm xls xlt xlv xlw +application/exi,exi +application/font-tdpfr,pfr +application/fractals,fif +application/freeloader,frl +application/futuresplash,spl +application/gnutar,tgz +application/groupwise,vew +application/gzip,gz gzip +application/hlp,hlp +application/hta,hta +application/hyperstudio,stk +application/i-deas,unv +application/iff,iff +application/iges,iges igs +application/inf,inf +application/ipfix,ipfix +application/java,class +application/java-archive,jar +application/java-byte-code,class +application/java-serialized-object,ser +application/java-vm,class +application/json,json +application/lha,lha +application/lzx,lzx +application/macbinary,bin +application/mac-binary,bin +application/mac-binhex,hqx +application/mac-binhex40,hqx +application/mac-compactpro,cpt +application/mads+xml,mads +application/manifest+json,webmanifest +application/marc,mrc +application/marcxml+xml,mrcx +application/mathematica,ma +application/mathml+xml,mathml +application/mbedlet,mbd +application/mbox,mbox +application/mcad,mcd +application/mediaservercontrol+xml,mscml +application/metalink4+xml,meta4 +application/mets+xml,mets +application/mime,aps +application/mods+xml,mods +application/mp21,m21 +application/mspowerpoint,pot pps ppt ppz +application/msword,doc dot w6w wiz word +application/mswrite,wri +application/mxf,mxf +application/netmc,mcp +application/octet-stream,a arc arj bin com dump exe keychain lzh lzx o psd saveme sdf uu zoo +application/oda,oda +application/oebps-package+xml,opf +application/ogg,ogx +application/onenote,onetoc +application/patch-ops-error+xml,xer +application/pdf,pdf +application/pgp-encrypted, +application/pgp-keys,key +application/pgp-signature,pgp +application/pics-rules,prf +application/pkcs-12,p12 +application/pkcs-crl,crl +application/pkcs10,p10 +application/pkcs7-mime,p7c p7m +application/pkcs7-signature,p7s +application/pkcs8,p8 +application/pkix-attr-cert,ac +application/pkix-cert,cer crt +application/pkix-crl,crl +application/pkix-pkipath,pkipath +application/pkixcmp,pki +application/pls+xml,pls +application/postscript,ai eps ps +application/powerpoint,ppt +application/pro_eng,part prt +application/prs.cww,cww +application/pskc+xml,pskcxml +application/rdf+xml,rdf +application/reginfo+xml,rif +application/relax-ng-compact-syntax,rnc +application/resource-lists+xml,rl +application/resource-lists-diff+xml,rld +application/ringing-tones,rng +application/rls-services+xml,rs +application/rsd+xml,rsd +application/rss+xml,rss +application/rtf,rtf rtx +application/sbml+xml,sbml +application/scvp-cv-request,scq +application/scvp-cv-response,scs +application/scvp-vp-request,spq +application/scvp-vp-response,spp +application/sdp,sdp +application/sea,sea +application/set,set +application/set-payment-initiation,setpay +application/set-registration-initiation,setreg +application/shf+xml,shf +application/sla,stl +application/smil+xml,smi +application/solids,sol +application/sounder,sdr +application/sparql-query,rq +application/sparql-results+xml,srx +application/srgs+xml,grxml +application/srgs,gram +application/sru+xml,sru +application/ssml+xml,ssml +application/step,step stp +application/streamingmedia,ssm +application/tei+xml,tei +application/thraud+xml,tfi +application/timestamped-data,tsd +application/toolbook,tbk +application/vda,vda +application/vnd.3gpp.pic-bw-large,plb +application/vnd.3gpp.pic-bw-small,psb +application/vnd.3gpp.pic-bw-var,pvb +application/vnd.3gpp2.tcap,tcap +application/vnd.3m.post-it-notes,pwn +application/vnd.accpac.simply.aso,aso +application/vnd.accpac.simply.imp,imp +application/vnd.acucobol,acu +application/vnd.acucorp,atc +application/vnd.adobe.air-application-installer-package+zip,air +application/vnd.adobe.fxp,fxp +application/vnd.adobe.xdp+xml,xdp +application/vnd.adobe.xfdf,xfdf +application/vnd.ahead.space,ahead +application/vnd.airzip.filesecure.azf,azf +application/vnd.airzip.filesecure.azs,azs +application/vnd.amazon.ebook,azw +application/vnd.americandynamics.acc,acc +application/vnd.amiga.ami,ami +application/vnd.android.package-archive,apk +application/vnd.anser-web-certificate-issue-initiation,cii +application/vnd.anser-web-funds-transfer-initiation,fti +application/vnd.antix.game-component,atx +application/vnd.apple.installer+xml,mpkg +application/vnd.apple.mpegurl,m3u8 +application/vnd.apple.pages,pages +application/vnd.aristanetworks.swi,swi +application/vnd.audiograph,aep +application/vnd.blueice.multipass,mpm +application/vnd.bmi,bmi +application/vnd.businessobjects,rep +application/vnd.chemdraw+xml,cdxml +application/vnd.chipnuts.karaoke-mmd,mmd +application/vnd.cinderella,cdy +application/vnd.claymore,cla +application/vnd.cloanto.rp9,rp9 +application/vnd.clonk.c4group,c4g +application/vnd.cluetrust.cartomobile-config,c11amc +application/vnd.cluetrust.cartomobile-config-pkg,c11amz +application/vnd.commonspace,csp +application/vnd.contact.cmsg,cdbcmsg +application/vnd.cosmocaller,cmc +application/vnd.crick.clicker,clkx +application/vnd.crick.clicker.keyboard,clkk +application/vnd.crick.clicker.palette,clkp +application/vnd.crick.clicker.template,clkt +application/vnd.crick.clicker.wordbank,clkw +application/vnd.criticaltools.wbs+xml,wbs +application/vnd.ctc-posml,pml +application/vnd.cups-ppd,ppd +application/vnd.curl.car,car +application/vnd.curl.pcurl,pcurl +application/vnd.data-vision.rdz,rdz +application/vnd.denovo.fcselayout-link,fe_launch +application/vnd.dna,dna +application/vnd.dolby.mlp,mlp +application/vnd.dpgraph,dpg +application/vnd.dreamfactory,dfac +application/vnd.dvb.ait,ait +application/vnd.dvb.service,svc +application/vnd.dynageo,geo +application/vnd.ecowin.chart,mag +application/vnd.enliven,nml +application/vnd.epson.esf,esf +application/vnd.epson.msf,msf +application/vnd.epson.quickanime,qam +application/vnd.epson.salt,slt +application/vnd.epson.ssf,ssf +application/vnd.eszigno3+xml,es3 +application/vnd.ezpix-album,ez2 +application/vnd.ezpix-package,ez3 +application/vnd.fdf,fdf +application/vnd.fdsn.seed,seed +application/vnd.flographit,gph +application/vnd.fluxtime.clip,ftc +application/vnd.framemaker,fm +application/vnd.fsc.weblaunch,fsc +application/vnd.fujitsu.oasys,oas +application/vnd.fujitsu.oasys2,oa2 +application/vnd.fujitsu.oasys3,oa3 +application/vnd.fujitsu.oasysgp,fg5 +application/vnd.fujitsu.oasysprs,bh2 +application/vnd.fujixerox.ddd,ddd +application/vnd.fujixerox.docuworks,xdw +application/vnd.fujixerox.docuworks.binder,xbd +application/vnd.fuzzysheet,fzs +application/vnd.genomatix.tuxedo,txd +application/vnd.geogebra.file,ggb +application/vnd.geogebra.tool,ggt +application/vnd.geometry-explorer,gex +application/vnd.geonext,gxt +application/vnd.geoplan,g2w +application/vnd.geospace,g3w +application/vnd.gmx,gmx +application/vnd.google-earth.kml+xml,kml +application/vnd.google-earth.kmz,kmz +application/vnd.grafeq,gqf +application/vnd.groove-account,gac +application/vnd.groove-help,ghf +application/vnd.groove-identity-message,gim +application/vnd.groove-injector,grv +application/vnd.groove-tool-message,gtm +application/vnd.groove-tool-template,tpl +application/vnd.groove-vcard,vcg +application/vnd.hal+xml,hal +application/vnd.handheld-entertainment+xml,zmm +application/vnd.hbci,hbci +application/vnd.hhe.lesson-player,les +application/vnd.hp-hpgl,hgl hpg hpgl +application/vnd.hp-hpid,hpid +application/vnd.hp-hps,hps +application/vnd.hp-jlyt,jlt +application/vnd.hp-pcl,pcl +application/vnd.hp-pclxl,pclxl +application/vnd.hydrostatix.sof-data,sfd-hdstx +application/vnd.hzn-3d-crossword,x3d +application/vnd.ibm.minipay,mpy +application/vnd.ibm.rights-management,irm +application/vnd.ibm.secure-container,sc +application/vnd.iccprofile,icc +application/vnd.igloader,igl +application/vnd.immervision-ivp,ivp +application/vnd.immervision-ivu,ivu +application/vnd.insors.igm,igm +application/vnd.intercon.formnet,xpw +application/vnd.intergeo,i2g +application/vnd.intu.qbo,qbo +application/vnd.intu.qfx,qfx +application/vnd.ipunplugged.rcprofile,rcprofile +application/vnd.irepository.package+xml,irp +application/vnd.is-xpr,xpr +application/vnd.isac.fcs,fcs +application/vnd.jam,jam +application/vnd.jcp.javame.midlet-rms,rms +application/vnd.jisp,jisp +application/vnd.joost.joda-archive,joda +application/vnd.kahootz,ktz +application/vnd.kde.karbon,karbon +application/vnd.kde.kchart,chrt +application/vnd.kde.kformula,kfo +application/vnd.kde.kivio,flw +application/vnd.kde.kontour,kon +application/vnd.kde.kpresenter,kpr +application/vnd.kde.kspread,ksp +application/vnd.kde.kword,kwd +application/vnd.kenameaapp,htke +application/vnd.kidspiration,kia +application/vnd.kinar,kne +application/vnd.koan,skp +application/vnd.kodak-descriptor,sse +application/vnd.las.las+xml,lasxml +application/vnd.llamagraphics.life-balance.desktop,lbd +application/vnd.llamagraphics.life-balance.exchange+xml,lbe +application/vnd.lotus-1-2-3,123 +application/vnd.lotus-approach,apr +application/vnd.lotus-freelance,pre +application/vnd.lotus-notes,nsf +application/vnd.lotus-organizer,org +application/vnd.lotus-screencam,scm +application/vnd.lotus-wordpro,lwp +application/vnd.macports.portpkg,portpkg +application/vnd.mcd,mcd +application/vnd.medcalcdata,mc1 +application/vnd.mediastation.cdkey,cdkey +application/vnd.mfer,mwf +application/vnd.mfmp,mfm +application/vnd.micrografx.flo,flo +application/vnd.micrografx.igx,igx +application/vnd.mif,mif +application/vnd.mobius.daf,daf +application/vnd.mobius.dis,dis +application/vnd.mobius.mbk,mbk +application/vnd.mobius.mqy,mqy +application/vnd.mobius.msl,msl +application/vnd.mobius.plc,plc +application/vnd.mobius.txf,txf +application/vnd.mophun.application,mpn +application/vnd.mophun.certificate,mpc +application/vnd.mozilla.xul+xml,xul +application/vnd.ms-artgalry,cil +application/vnd.ms-cab-compressed,cab +application/vnd.ms-excel,xlb xlc xll xlm xls xlw +application/vnd.ms-excel.addin.macroenabled.12,xlam +application/vnd.ms-excel.sheet.binary.macroenabled.12,xlsb +application/vnd.ms-excel.sheet.macroenabled.12,xlsm +application/vnd.ms-excel.template.macroenabled.12,xltm +application/vnd.ms-fontobject,eot +application/vnd.ms-htmlhelp,chm +application/vnd.ms-ims,ims +application/vnd.ms-lrm,lrm +application/vnd.ms-officetheme,thmx +application/vnd.ms-outlook,msg +application/vnd.ms-pki.certstore,sst +application/vnd.ms-pki.pko,pko +application/vnd.ms-pki.seccat,cat +application/vnd.ms-pki.stl,stl +application/vnd.ms-powerpoint,pot ppa pps ppt pwz +application/vnd.ms-powerpoint.addin.macroenabled.12,ppam +application/vnd.ms-powerpoint.presentation.macroenabled.12,pptm +application/vnd.ms-powerpoint.slide.macroenabled.12,sldm +application/vnd.ms-powerpoint.slideshow.macroenabled.12,ppsm +application/vnd.ms-powerpoint.template.macroenabled.12,potm +application/vnd.ms-project,mpp +application/vnd.ms-word.document.macroenabled.12,docm +application/vnd.ms-word.template.macroenabled.12,dotm +application/vnd.ms-works,wps +application/vnd.ms-wpl,wpl +application/vnd.ms-xpsdocument,xps +application/vnd.mseq,mseq +application/vnd.musician,mus +application/vnd.muvee.style,msty +application/vnd.neurolanguage.nlu,nlu +application/vnd.noblenet-directory,nnd +application/vnd.noblenet-sealer,nns +application/vnd.noblenet-web,nnw +application/vnd.nokia.configuration-message,ncm +application/vnd.nokia.n-gage.data,ngdat +application/vnd.nokia.radio-preset,rpst +application/vnd.nokia.radio-presets,rpss +application/vnd.nokia.ringing-tone,rng +application/vnd.novadigm.edm,edm +application/vnd.novadigm.edx,edx +application/vnd.novadigm.ext,ext +application/vnd.oasis.opendocument.chart,odc +application/vnd.oasis.opendocument.chart-template,otc +application/vnd.oasis.opendocument.formula,odf +application/vnd.oasis.opendocument.formula-template,odft +application/vnd.oasis.opendocument.graphics,odg +application/vnd.oasis.opendocument.graphics-template,otg +application/vnd.oasis.opendocument.image,odi +application/vnd.oasis.opendocument.image-template,oti +application/vnd.oasis.opendocument.presentation,odp +application/vnd.oasis.opendocument.presentation-template,otp +application/vnd.oasis.opendocument.spreadsheet,ods +application/vnd.oasis.opendocument.spreadsheet-template,ots +application/vnd.oasis.opendocument.text,odt +application/vnd.oasis.opendocument.text-master,odm +application/vnd.oasis.opendocument.text-template,ott +application/vnd.oasis.opendocument.text-web,oth +application/vnd.olpc-sugar,xo +application/vnd.oma.dd2+xml,dd2 +application/vnd.openofficeorg.extension,oxt +application/vnd.openxmlformats-officedocument.presentationml.presentation,pptx +application/vnd.openxmlformats-officedocument.presentationml.slide,sldx +application/vnd.openxmlformats-officedocument.presentationml.slideshow,ppsx +application/vnd.openxmlformats-officedocument.presentationml.template,potx +application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,xlsx +application/vnd.openxmlformats-officedocument.spreadsheetml.template,xltx +application/vnd.openxmlformats-officedocument.wordprocessingml.document,docx +application/vnd.openxmlformats-officedocument.wordprocessingml.template,dotx +application/vnd.osgeo.mapguide.package,mgp +application/vnd.osgi.dp,dp +application/vnd.palm,pdb +application/vnd.pawaafile,paw +application/vnd.pg.format,str +application/vnd.pg.osasli,ei6 +application/vnd.picsel,efif +application/vnd.pmi.widget,wg +application/vnd.pocketlearn,plf +application/vnd.powerbuilder6,pbd +application/vnd.previewsystems.box,box +application/vnd.proteus.magazine,mgz +application/vnd.publishare-delta-tree,qps +application/vnd.pvi.ptid1,ptid +application/vnd.quark.quarkxpress,qxd +application/vnd.realvnc.bed,bed +application/vnd.recordare.musicxml+xml,musicxml +application/vnd.recordare.musicxml,mxl +application/vnd.rig.cryptonote,cryptonote +application/vnd.rim.cod,cod +application/vnd.rn-realmedia,rm +application/vnd.rn-realplayer,rnx +application/vnd.route66.link66+xml,link66 +application/vnd.sailingtracker.track,st +application/vnd.seemail,see +application/vnd.sema,sema +application/vnd.semd,semd +application/vnd.semf,semf +application/vnd.shana.informed.formdata,ifm +application/vnd.shana.informed.formtemplate,itp +application/vnd.shana.informed.interchange,iif +application/vnd.shana.informed.package,ipk +application/vnd.simtech-mindmapper,twd +application/vnd.smaf,mmf +application/vnd.smart.teacher,teacher +application/vnd.solent.sdkm+xml,sdkm +application/vnd.spotfire.dxp,dxp +application/vnd.spotfire.sfs,sfs +application/vnd.stardivision.calc,sdc +application/vnd.stardivision.draw,sda +application/vnd.stardivision.impress,sdd +application/vnd.stardivision.math,smf +application/vnd.stardivision.writer,sdw +application/vnd.stardivision.writer-global,sgl +application/vnd.stepmania.stepchart,sm +application/vnd.sun.xml.calc,sxc +application/vnd.sun.xml.calc.template,stc +application/vnd.sun.xml.draw,sxd +application/vnd.sun.xml.draw.template,std +application/vnd.sun.xml.impress,sxi +application/vnd.sun.xml.impress.template,sti +application/vnd.sun.xml.math,sxm +application/vnd.sun.xml.writer,sxw +application/vnd.sun.xml.writer.global,sxg +application/vnd.sun.xml.writer.template,stw +application/vnd.sus-calendar,sus +application/vnd.svd,svd +application/vnd.symbian.install,sis +application/vnd.syncml+xml,xsm +application/vnd.syncml.dm+wbxml,bdm +application/vnd.syncml.dm+xml,xdm +application/vnd.tao.intent-module-archive,tao +application/vnd.tmobile-livetv,tmo +application/vnd.trid.tpt,tpt +application/vnd.triscape.mxs,mxs +application/vnd.trueapp,tra +application/vnd.ufdl,ufd +application/vnd.uiq.theme,utz +application/vnd.umajin,umj +application/vnd.unity,unityweb +application/vnd.uoml+xml,uoml +application/vnd.vcx,vcx +application/vnd.visio,vsd +application/vnd.visionary,vis +application/vnd.vsf,vsf +application/vnd.wap.wbxml,wbxml +application/vnd.wap.wmlc,wmlc +application/vnd.wap.wmlscriptc,wmlsc +application/vnd.webturbo,wtb +application/vnd.wolfram.player,nbp +application/vnd.wordperfect,wpd +application/vnd.wqd,wqd +application/vnd.wt.stf,stf +application/vnd.xara,web xar +application/vnd.xfdl,xfdl +application/vnd.yamaha.hv-dic,hvd +application/vnd.yamaha.hv-script,hvs +application/vnd.yamaha.hv-voice,hvp +application/vnd.yamaha.openscoreformat,osf +application/vnd.yamaha.openscoreformat.osfpvg+xml,osfpvg +application/vnd.yamaha.smaf-audio,saf +application/vnd.yamaha.smaf-phrase,spf +application/vnd.yellowriver-custom-menu,cmp +application/vnd.zul,zir +application/vnd.zzazz.deck+xml,zaz +application/vocaltec-media-desc,vmd +application/vocaltec-media-file,vmf +application/voicexml+xml,vxml +application/wasm,wasm +application/widget,wgt +application/winhlp,hlp +application/wordperfect,wp wp5 wp6 wpd +application/wordperfect6.0,w60 wp5 +application/wordperfect6.1,w61 +application/wsdl+xml,wsdl +application/wspolicy+xml,wspolicy +application/x-123,wk1 +application/x-7z-compressed,7z +application/x-abiword,abw +application/x-ace-compressed,ace +application/x-aim,aim +application/x-authorware-bin,aab +application/x-authorware-map,aam +application/x-authorware-seg,aas +application/x-bcpio,bcpio +application/x-binary,bin +application/x-binhex40,hqx +application/x-bittorrent,torrent +application/x-bsh,bsh sh shar +application/x-bytecode.python,pyc +application/x-bzip,bz +application/x-bzip2,boz bz2 +application/x-cdf,cdf +application/x-cdlink,vcd +application/x-chat,cha chat +application/x-chess-pgn,pgn +application/x-cmu-raster,ras +application/x-cocoa,cco +application/x-compactpro,cpt +application/x-compress,z +application/zip,zip +application/x-compressed,gz tgz z zip +application/x-conference,nsc +application/x-cpio,cpio +application/x-cpt,cpt +application/x-csh,csh +application/x-debian-package,deb +application/x-deepv,deepv +application/x-director,dcr dir dxr +application/x-doom,wad +application/x-dtbncx+xml,ncx +application/x-dtbook+xml,dtb +application/x-dtbresource+xml,res +application/x-dvi,dvi +application/x-elc,elc +application/x-envoy,env evy +application/x-esrehber,es +application/x-excel,xla xlb xlc xld xlk xll xlm xls xlt xlv xlw +application/x-font-bdf,bdf +application/x-font-ghostscript,gsf +application/x-font-linux-psf,psf +application/x-font-pcf,pcf +application/x-font-snf,snf +application/x-font-type1,pfa +application/x-frame,mif +application/x-freelance,pre +application/x-futuresplash,spl +application/x-gnumeric,gnumeric +application/x-gsp,gsp +application/x-gss,gss +application/x-gtar,gtar +application/x-gzip,gz gzip +application/x-hdf,hdf +application/x-helpfile,help hlp +application/x-httpd-imap,imap +application/x-ima,ima +application/x-internett-signup,ins +application/x-inventor,iv +application/x-ip2,ip +application/x-java-class,class +application/x-java-commerce,jcm +application/x-java-jnlp-file,jnlp +application/x-koan,skd skm skp skt +application/x-ksh,ksh +application/x-latex,latex ltx +application/x-lha,lha +application/x-lisp,lsp +application/x-livescreen,ivy +application/x-lotus,wq1 +application/x-lotusscreencam,scm +application/x-lzh,lzh +application/x-lzx,lzx +application/x-mac-binhex40,hqx +application/x-macbinary,bin +application/x-magic-cap-package-1.0,mc${'$'} +application/x-mathcad,mcd +application/x-meme,mm +application/x-midi,mid midi +application/x-mif,mif +application/x-mix-transfer,nix +application/x-mobipocket-ebook,prc +application/x-mplayer2,asx +application/x-ms-application,application +application/x-ms-wmd,wmd +application/x-ms-wmz,wmz +application/x-ms-xbap,xbap +application/x-msaccess,mdb +application/x-msbinder,obd +application/x-mscardfile,crd +application/x-msclip,clp +application/x-msdownload,exe +application/x-msexcel,xla xls xlw +application/x-msmediaview,mvb +application/x-msmetafile,wmf +application/x-msmoney,mny +application/x-mspowerpoint,ppt +application/x-mspublisher,pub +application/x-msschedule,scd +application/x-msterminal,trm +application/x-mswrite,wri +application/x-navi-animation,ani +application/x-navidoc,nvd +application/x-navimap,map +application/x-navistyle,stl +application/x-netcdf,cdf nc +application/x-newton-compatible-pkg,pkg +application/x-nokia-9000-communicator-add-on-software,aos +application/x-omc,omc +application/x-omcdatamaker,omcd +application/x-omcregerator,omcr +application/x-pagemaker,pm4 pm5 +application/x-pcl,pcl +application/x-pem-file,pem +application/x-pixclscript,plx +application/x-pkcs12,pfx +application/x-pkcs7-certificates,p7b spc +application/x-pkcs7-certreqresp,p7r +application/x-pkcs7-signature,p7a +application/x-portable-anymap,pnm +application/x-project,mpc mpt mpv mpx +application/x-qpro,wb1 +application/x-rar-compressed,rar +application/x-sdp,sdp +application/x-sea,sea +application/x-seelogo,sl +application/x-sh,sh +application/x-shar,sh shar +application/x-shockwave-flash,swf +application/x-silverlight-app,xap +application/x-sit,sit +application/x-sprite,spr sprite +application/x-stuffit,sit +application/x-stuffitx,sitx +application/x-sv4cpio,sv4cpio +application/x-sv4crc,sv4crc +application/x-tar,tar +application/x-tbook,sbk tbk +application/x-tcl,tcl +application/x-tex,tex +application/x-tex-tfm,tfm +application/x-texinfo,texi texinfo +application/x-troff,roff t tr +application/x-troff-man,man +application/x-troff-me,me +application/x-troff-ms,ms +application/x-ustar,ustar +application/x-visio,vsd vst vsw +application/x-vnd.audioexplosion.mzz,mzz +application/x-vnd.ls-xpix,xpix +application/x-vrml,vrml +application/x-wais-source,src wsrc +application/x-winhelp,hlp +application/x-wintalk,wtk +application/x-world,svr wrl +application/x-wpwin,wpd +application/x-wri,wri +application/x-x509-ca-cert,crt der +application/x-x509-user-cert,crt +application/x-xfig,fig +application/x-xpinstall,xpi +application/x-xz,xz +application/x-zip-compressed,zip +application/xcap-diff+xml,xdf +application/xenc+xml,xenc +application/xhtml+xml,xhtml +application/xml,xml +application/xml-dtd,dtd +application/xop+xml,xop +application/xslt+xml,xslt +application/xspf+xml,xspf +application/xv+xml,mxml +application/yang,yang +application/yin+xml,yin +application/zip,war +audio/aac,aac +audio/adpcm,adp +audio/aiff,aif aifc aiff +audio/basic,au snd +audio/it,it +audio/make,funk my pfunk +audio/make.my.funk,pfunk +audio/mid,rmi +audio/midi,kar mid midi +audio/mod,mod +audio/mp4,m4a mp4a +audio/mpeg,m2a mp2 mp3 mpa mpga +audio/mpeg3,mp3 +audio/nspaudio,la lma +audio/ogg,oga ogg +audio/s3m,s3m +audio/tsp-audio,tsi +audio/tsplayer,tsp +audio/vnd.dece.audio,uva +audio/vnd.digital-winds,eol +audio/vnd.dra,dra +audio/vnd.dts,dts +audio/vnd.dts.hd,dtshd +audio/vnd.lucent.voice,lvp +audio/vnd.ms-playready.media.pya,pya +audio/vnd.nuera.ecelp4800,ecelp4800 +audio/vnd.nuera.ecelp7470,ecelp7470 +audio/vnd.nuera.ecelp9600,ecelp9600 +audio/vnd.qcelp,qcp +audio/vnd.rip,rip +audio/voc,voc +audio/voxware,vox +audio/wav,wav +audio/webm,weba +audio/x-adpcm,snd +audio/x-au,au +audio/x-gsm,gsd gsm +audio/x-jam,jam +audio/x-liveaudio,lam +audio/x-mid,mid midi +audio/x-midi,midi +audio/x-mod,mod +audio/x-mpeg,mp2 +audio/x-mpegurl,m3u +audio/x-ms-wax,wax +audio/x-ms-wma,wma +audio/x-nspaudio,la lma +audio/x-pn-realaudio,ra ram rm rmm rmp +audio/x-pn-realaudio-plugin,ra rmp rpm +application/x-rpm,rpm +audio/x-psid,sid +audio/x-realaudio,ra +audio/x-twinvq,vqf +audio/x-twinvq-plugin,vqe vql +audio/x-vnd.audioexplosion.mjuicemediafile,mjf +audio/x-voc,voc +audio/xm,xm +binary/octet-stream,dat +chemical/x-cdx,cdx +chemical/x-cif,cif +chemical/x-cmdf,cmdf +chemical/x-cml,cml +chemical/x-csml,csml +chemical/x-pdb,pdb xyz +chemical/x-xyz,xyz +font/collection ,collection +font/otf,otf +font/sfnt,sfnt +font/ttf,ttf +font/woff,woff +font/woff2,woff2 +i-world/i-vrml,ivr +image/avif,avif avifs +image/bmp,bm bmp +image/cgm,cgm +image/cmu-raster,ras rast +image/fif,fif +image/florian,flo turbot +image/g3fax,g3 +image/gif,gif +image/heic,heic +image/heif,heif +image/ief,ief iefs +image/jpeg,jfif jfif-tbnl jpe jpeg jpg +image/jutvision,jut +image/ktx,ktx +image/naplps,nap naplps +image/pict,pic pict +image/pjpeg,jfif +image/png,png x-png +image/prs.btif,btif +image/svg+xml,svg +image/tiff,tif tiff +image/vasa,mcf +image/vnd.adobe.photoshop,psd +image/vnd.dece.graphic,uvi +image/vnd.djvu,djvu +image/vnd.dvb.subtitle,sub +image/vnd.dwg,dwg dxf svf +image/vnd.dxf,dxf +image/vnd.fastbidsheet,fbs +image/vnd.fpx,fpx +image/vnd.fst,fst +image/vnd.fujixerox.edmics-mmr,mmr +image/vnd.fujixerox.edmics-rlc,rlc +image/vnd.ms-modi,mdi +image/vnd.net-fpx,fpx npx +image/vnd.rn-realflash,rf +image/vnd.rn-realpix,rp +image/vnd.wap.wbmp,wbmp +image/vnd.xiff,xif +image/webp,webp +image/x-cmu-raster,ras +image/x-cmx,cmx +image/x-dwg,dwg dxf svf +image/x-freehand,fh +image/x-icon,ico +image/x-jg,art +image/x-jps,jps +image/x-niff,nif niff +image/x-pcx,pcx +image/x-pict,pct +image/x-portable-anymap,pnm +image/x-portable-bitmap,pbm +image/x-portable-graymap,pgm +image/x-portable-pixmap,ppm +image/x-quicktime,qif qti qtif +image/x-rgb,rgb +image/x-windows-bmp,bmp +image/x-xbitmap,xbm +image/x-xbm,xbm +image/x-xpixmap,pm xpm +image/x-xwd,xwd +image/x-xwindowdump,xwd +image/xbm,xbm +image/xpm,xpm +message/rfc822,eml mht mhtml mime +model/iges,iges igs +model/mesh,msh +model/vnd.collada+xml,dae +model/vnd.dwf,dwf +model/vnd.gdl,gdl +model/vnd.gtw,gtw +model/vnd.mts,mts +model/vnd.vtu,vtu +model/vrml,vrml wrl wrz +model/x-pov,pov +multipart/x-gzip,gzip +multipart/x-ustar,ustar +multipart/x-zip,zip +music/crescendo,mid midi +music/x-karaoke,kar +paleovu/x-pv,pvu +text/asp,asp +text/calendar,ics +text/css,css +text/csv,csv +text/html,acgi htm html htmls htx shtml +text/javascript,js mjs +text/mcf,mcf +text/n3,n3 +text/pascal,pas +text/plain,c c++ cc com conf cxx def f f90 for g h hh idc jav java list log lst m mar pl sdml text txt +text/plain-bas,par +text/prs.lines.tag,dsc +text/richtext,rt rtx +text/rtf,rtf +text/scriplet,wsc +text/sgml,sgm sgml +text/srt,srt +text/tab-separated-values,tsv +text/troff,t +text/turtle,ttl +text/uri-list,uni unis uri uris +text/vnd.abc,abc +text/vnd.curl,curl +text/vnd.curl.dcurl,dcurl +text/vnd.curl.mcurl,mcurl +text/vnd.curl.scurl,scurl +text/vnd.fly,fly +text/vnd.fmi.flexstor,flx +text/vnd.graphviz,gv +text/vnd.in3d.3dml,3dml +text/vnd.in3d.spot,spot +text/vnd.rn-realtext,rt +text/vnd.sun.j2me.app-descriptor,jad +text/vnd.wap.wml,wml +text/vnd.wap.wmlscript,wmls +text/vtt,vtt +text/webviewhtml,htt +text/x-asm,asm s +text/x-audiosoft-intra,aip +text/x-c,c cc cpp +text/x-component,htc +text/x-fortran,f f77 f90 for +text/x-h,h hh +text/x-java-source,jav java +text/x-la-asf,lsx +text/x-m,m +text/x-pascal,p +text/x-script,hlb +text/x-script.csh,csh +text/x-script.elisp,el +text/x-script.guile,scm +text/x-script.ksh,ksh +text/x-script.lisp,lsp +text/x-script.perl,pl +text/x-script.perl-module,pm +text/x-script.python,py +text/x-script.rexx,rexx +text/x-script.scheme,scm +text/x-script.sh,sh +text/x-script.tcl,tcl +text/x-script.tcsh,tcsh +text/x-script.zsh,zsh +text/x-server-parsed-html,shtml ssi +text/x-setext,etx +text/x-sgml,sgm sgml +text/x-speech,spc talk +text/x-uil,uil +text/x-uuencode,uu uue +text/x-vcalendar,vcs +text/x-vcard,vcf +text/xml,xml +text/yaml,yaml +video/3gpp,3gp +video/3gpp2,3g2 +video/animaflex,afl +video/avi,avi +video/avs-video,avs +video/dl,dl +video/dvd,vob +video/fli,fli +video/gl,gl +video/h261,h261 +video/h263,h263 +video/h264,h264 +video/jpeg,jpgv +video/jpm,jpm +video/mj2,mj2 +video/mp4,m4v mp4 +application/mp4,mp4 +video/mpeg,m1v m2v mp2 mpe mpeg mpg +audio/mpeg,mpg +video/msvideo,avi +video/ogg,ogv +video/quicktime,moov mov qt +video/vdo,vdo +video/vivo,viv vivo +video/vnd.dece.hd,uvh +video/vnd.dece.mobile,uvm +video/vnd.dece.pd,uvp +video/vnd.dece.sd,uvs +video/vnd.dece.video,uvv +video/vnd.fvt,fvt +video/vnd.mpegurl,mxu +video/vnd.ms-playready.media.pyv,pyv +video/vnd.rn-realvideo,rv +video/vnd.uvvu.mp4,uvu +video/vnd.vivo,viv vivo +video/vosaic,vos +video/webm,webm +video/x-amt-demorun,xdr +video/x-amt-showrun,xsr +video/x-atomic3d-feature,fmf +video/x-dl,dl +video/x-dv,dif dv +video/x-f4v,f4v +video/x-fli,fli +video/x-flv,flv +video/x-gl,gl +video/x-isvideo,isu +video/x-matroska,mkv +audio/x-matroska,mkv +video/x-motion-jpeg,mjpg +video/x-mpeg,mp2 +video/x-mpeq2a,mp2 +video/x-ms-asf,asf asx +video/x-ms-asf-plugin,asx +video/x-ms-wm,wm +video/x-ms-wmv,wmv +video/x-ms-wmx,wmx +video/x-ms-wvx,wvx +video/x-msvideo,avi +video/x-qtc,qtc +video/x-scm,scm +video/x-sgi-movie,movie mv +windows/metafile,wmf +www/mime,mime +x-conference/x-cooltalk,ice +x-music/x-midi,mid midi +x-world/x-3dmf,3dm 3dmf qd3 qd3d +x-world/x-svr,svr +x-world/x-vrml,vrml wrl wrz +x-world/x-vrt,vrt +xgl/drawing,xgz +xgl/movie,xmz +# Deprecated media types +application/ecmascript,es +application/javascript,js +application/smil,smi sml +application/vnd.frogans.fnc,fnc +application/vnd.frogans.ltf,ltf +application/vnd.ibm.modcap,afp +application/vnd.nokia.n-gage.symbian.install,n-gage +application/vnd.oasis.opendocument.database,odb """ internal fun loadMimes(): List> { - return rawMimes.lineSequence().mapNotNull { + return rawMimes.lineSequence().flatMapTo(ArrayList(INITIAL_MIMES_LIST_SIZE)) { val line = it.trim() - if (line.isEmpty()) return@mapNotNull null - - val index = line.indexOf(',') - val extension = line.substring(0, index) - val mime = line.substring(index + 1) + if (line.isEmpty() || line.startsWith("#")) return@flatMapTo emptySequence() - extension.removePrefix(".").toLowerCasePreservingASCIIRules() to mime.toContentType() - }.toList() + val (mime, extensions) = line.split(",", limit = 2) + val contentType = mime.toContentType() + extensions.splitToSequence(" ").map { ext -> + ext.toLowerCasePreservingASCIIRules() to contentType + } + } } internal val mimes: List> by lazy { loadMimes() } diff --git a/ktor-http/common/src/io/ktor/http/URLProtocol.kt b/ktor-http/common/src/io/ktor/http/URLProtocol.kt index 5095eb6fd4c..b6f9fc9e141 100644 --- a/ktor-http/common/src/io/ktor/http/URLProtocol.kt +++ b/ktor-http/common/src/io/ktor/http/URLProtocol.kt @@ -5,13 +5,15 @@ package io.ktor.http import io.ktor.util.* +import io.ktor.utils.io.* /** * Represents URL protocol * @property name of protocol (schema) * @property defaultPort default port for protocol or `-1` if not known */ -public data class URLProtocol(val name: String, val defaultPort: Int) { +@OptIn(InternalAPI::class) +public data class URLProtocol(val name: String, val defaultPort: Int) : JvmSerializable { init { require(name.all { it.isLowerCase() }) { "All characters should be lower case" } } diff --git a/ktor-http/common/src/io/ktor/http/Url.kt b/ktor-http/common/src/io/ktor/http/Url.kt index d50c96d8c4a..8f492789b28 100644 --- a/ktor-http/common/src/io/ktor/http/Url.kt +++ b/ktor-http/common/src/io/ktor/http/Url.kt @@ -2,8 +2,15 @@ * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ +@file:OptIn(InternalAPI::class) + package io.ktor.http +import io.ktor.utils.io.* +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + /** * Represents an immutable URL * @@ -18,6 +25,7 @@ package io.ktor.http * @property password password part of URL * @property trailingQuery keep trailing question character even if there are no query parameters */ +@Serializable(with = UrlSerializer::class) public class Url internal constructor( protocol: URLProtocol?, public val host: String, @@ -29,7 +37,7 @@ public class Url internal constructor( public val password: String?, public val trailingQuery: Boolean, private val urlString: String -) { +) : JvmSerializable { init { require(specifiedPort in 0..65535) { "Port must be between 0 and 65535, or $DEFAULT_PORT if not set. Provided: $specifiedPort" @@ -222,6 +230,8 @@ public class Url internal constructor( return urlString.hashCode() } + private fun writeReplace(): Any = JvmSerializerReplacement(UrlJvmSerializer, this) + public companion object } @@ -254,3 +264,23 @@ internal val Url.encodedUserAndPassword: String get() = buildString { appendUserAndPassword(encodedUser, encodedPassword) } + +public object UrlSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("io.ktor.http.Url", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Url = + Url(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: Url) { + encoder.encodeString(value.toString()) + } +} + +internal object UrlJvmSerializer : JvmSerializer { + override fun jvmSerialize(value: Url): ByteArray = + value.toString().encodeToByteArray() + + override fun jvmDeserialize(value: ByteArray): Url = + Url(value.decodeToString()) +} diff --git a/ktor-http/common/test/io/ktor/tests/http/ContentTypeLookupTest.kt b/ktor-http/common/test/io/ktor/tests/http/ContentTypeLookupTest.kt index 7a26a16f01e..dece101f2b9 100644 --- a/ktor-http/common/test/io/ktor/tests/http/ContentTypeLookupTest.kt +++ b/ktor-http/common/test/io/ktor/tests/http/ContentTypeLookupTest.kt @@ -24,6 +24,59 @@ class ContentTypeLookupTest { ), ContentType.fromFileExtension(".rpm") ) + + assertEquals( + listOf( + ContentType.parse("application/macbinary"), + ContentType.parse("application/mac-binary"), + ContentType.parse("application/octet-stream"), + ContentType.parse("application/x-binary"), + ContentType.parse("application/x-macbinary") + ), + ContentType.fromFileExtension(".bin") + ) + + assertEquals( + listOf( + ContentType.parse("application/zip"), + ContentType.parse("application/x-compressed"), + ContentType.parse("application/x-zip-compressed"), + ContentType.parse("multipart/x-zip") + ), + ContentType.fromFileExtension(".zip") + ) + + assertEquals( + listOf( + ContentType.parse("video/mpeg"), + ContentType.parse("audio/mpeg") + ), + ContentType.fromFileExtension(".mpg") + ) + + assertEquals( + listOf( + ContentType.parse("video/mp4"), + ContentType.parse("application/mp4") + ), + ContentType.fromFileExtension(".mp4") + ) + + assertEquals( + listOf( + ContentType.parse("video/x-matroska"), + ContentType.parse("audio/x-matroska"), + ), + ContentType.fromFileExtension(".mkv") + ) + + assertEquals( + listOf( + ContentType.parse("text/javascript"), + ContentType.parse("application/javascript"), + ), + ContentType.fromFileExtension(".js") + ) } @Test diff --git a/ktor-http/common/test/io/ktor/tests/http/MimesTest.kt b/ktor-http/common/test/io/ktor/tests/http/MimesTest.kt new file mode 100644 index 00000000000..af9c9f4a989 --- /dev/null +++ b/ktor-http/common/test/io/ktor/tests/http/MimesTest.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.tests.http + +import io.ktor.http.* +import kotlin.test.* + +class MimesTest { + + @Test + fun testMimeWithMultipleExtensions() { + val textPlain = "text/plain".toContentType() + val textMime = "text" to textPlain + val txtMime = "txt" to textPlain + assertTrue(mimes.contains(textMime)) + assertTrue(mimes.contains(txtMime)) + } + + @Test + fun testMimeWithSingleExtension() { + val acad = "application/acad".toContentType() + val dwgMime = "dwg" to acad + assertTrue(mimes.contains(dwgMime)) + } + + @Test + fun testMimesSizeMatchesPreallocatedListSize() { + assertEquals(INITIAL_MIMES_LIST_SIZE, mimes.size) + } +} diff --git a/ktor-http/jvm/test/io/ktor/tests/http/SerializableTest.kt b/ktor-http/jvm/test/io/ktor/tests/http/SerializableTest.kt new file mode 100644 index 00000000000..e3df54915e2 --- /dev/null +++ b/ktor-http/jvm/test/io/ktor/tests/http/SerializableTest.kt @@ -0,0 +1,22 @@ +// ktlint-disable filename +/* + * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.tests.http + +import io.ktor.http.* +import io.ktor.junit.* +import kotlin.test.* + +class SerializableTest { + @Test + fun urlTest() { + assertSerializable(Url("https://localhost/path?key=value#fragment")) + } + + @Test + fun cookieTest() { + assertSerializable(Cookie("key", "value")) + } +} diff --git a/ktor-http/ktor-http-cio/api/ktor-http-cio.klib.api b/ktor-http/ktor-http-cio/api/ktor-http-cio.klib.api index b9a6c9df8b6..2418cb5200d 100644 --- a/ktor-http/ktor-http-cio/api/ktor-http-cio.klib.api +++ b/ktor-http/ktor-http-cio/api/ktor-http-cio.klib.api @@ -40,6 +40,15 @@ final class io.ktor.http.cio/CIOHeaders : io.ktor.http/Headers { // io.ktor.http final fun names(): kotlin.collections/Set // io.ktor.http.cio/CIOHeaders.names|names(){}[0] } +final class io.ktor.http.cio/CIOMultipartDataBase : io.ktor.http.content/MultiPartData, kotlinx.coroutines/CoroutineScope { // io.ktor.http.cio/CIOMultipartDataBase|null[0] + constructor (kotlin.coroutines/CoroutineContext, io.ktor.utils.io/ByteReadChannel, kotlin/CharSequence, kotlin/Long?, kotlin/Long = ...) // io.ktor.http.cio/CIOMultipartDataBase.|(kotlin.coroutines.CoroutineContext;io.ktor.utils.io.ByteReadChannel;kotlin.CharSequence;kotlin.Long?;kotlin.Long){}[0] + + final val coroutineContext // io.ktor.http.cio/CIOMultipartDataBase.coroutineContext|{}coroutineContext[0] + final fun (): kotlin.coroutines/CoroutineContext // io.ktor.http.cio/CIOMultipartDataBase.coroutineContext.|(){}[0] + + final suspend fun readPart(): io.ktor.http.content/PartData? // io.ktor.http.cio/CIOMultipartDataBase.readPart|readPart(){}[0] +} + final class io.ktor.http.cio/ConnectionOptions { // io.ktor.http.cio/ConnectionOptions|null[0] constructor (kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin.collections/List = ...) // io.ktor.http.cio/ConnectionOptions.|(kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.collections.List){}[0] @@ -117,9 +126,44 @@ final class io.ktor.http.cio/Response : io.ktor.http.cio/HttpMessage { // io.kto final fun (): kotlin/CharSequence // io.ktor.http.cio/Response.version.|(){}[0] } +sealed class io.ktor.http.cio/MultipartEvent { // io.ktor.http.cio/MultipartEvent|null[0] + abstract fun release() // io.ktor.http.cio/MultipartEvent.release|release(){}[0] + + final class Epilogue : io.ktor.http.cio/MultipartEvent { // io.ktor.http.cio/MultipartEvent.Epilogue|null[0] + constructor (kotlinx.io/Source) // io.ktor.http.cio/MultipartEvent.Epilogue.|(kotlinx.io.Source){}[0] + + final val body // io.ktor.http.cio/MultipartEvent.Epilogue.body|{}body[0] + final fun (): kotlinx.io/Source // io.ktor.http.cio/MultipartEvent.Epilogue.body.|(){}[0] + + final fun release() // io.ktor.http.cio/MultipartEvent.Epilogue.release|release(){}[0] + } + + final class MultipartPart : io.ktor.http.cio/MultipartEvent { // io.ktor.http.cio/MultipartEvent.MultipartPart|null[0] + constructor (kotlinx.coroutines/Deferred, io.ktor.utils.io/ByteReadChannel) // io.ktor.http.cio/MultipartEvent.MultipartPart.|(kotlinx.coroutines.Deferred;io.ktor.utils.io.ByteReadChannel){}[0] + + final val body // io.ktor.http.cio/MultipartEvent.MultipartPart.body|{}body[0] + final fun (): io.ktor.utils.io/ByteReadChannel // io.ktor.http.cio/MultipartEvent.MultipartPart.body.|(){}[0] + final val headers // io.ktor.http.cio/MultipartEvent.MultipartPart.headers|{}headers[0] + final fun (): kotlinx.coroutines/Deferred // io.ktor.http.cio/MultipartEvent.MultipartPart.headers.|(){}[0] + + final fun release() // io.ktor.http.cio/MultipartEvent.MultipartPart.release|release(){}[0] + } + + final class Preamble : io.ktor.http.cio/MultipartEvent { // io.ktor.http.cio/MultipartEvent.Preamble|null[0] + constructor (kotlinx.io/Source) // io.ktor.http.cio/MultipartEvent.Preamble.|(kotlinx.io.Source){}[0] + + final val body // io.ktor.http.cio/MultipartEvent.Preamble.body|{}body[0] + final fun (): kotlinx.io/Source // io.ktor.http.cio/MultipartEvent.Preamble.body.|(){}[0] + + final fun release() // io.ktor.http.cio/MultipartEvent.Preamble.release|release(){}[0] + } +} + final fun (kotlin/CharSequence).io.ktor.http.cio.internals/parseDecLong(): kotlin/Long // io.ktor.http.cio.internals/parseDecLong|parseDecLong@kotlin.CharSequence(){}[0] final fun (kotlinx.coroutines/CoroutineScope).io.ktor.http.cio/decodeChunked(io.ktor.utils.io/ByteReadChannel): io.ktor.utils.io/WriterJob // io.ktor.http.cio/decodeChunked|decodeChunked@kotlinx.coroutines.CoroutineScope(io.ktor.utils.io.ByteReadChannel){}[0] final fun (kotlinx.coroutines/CoroutineScope).io.ktor.http.cio/decodeChunked(io.ktor.utils.io/ByteReadChannel, kotlin/Long): io.ktor.utils.io/WriterJob // io.ktor.http.cio/decodeChunked|decodeChunked@kotlinx.coroutines.CoroutineScope(io.ktor.utils.io.ByteReadChannel;kotlin.Long){}[0] +final fun (kotlinx.coroutines/CoroutineScope).io.ktor.http.cio/parseMultipart(io.ktor.utils.io/ByteReadChannel, io.ktor.http.cio/HttpHeadersMap, kotlin/Long = ...): kotlinx.coroutines.channels/ReceiveChannel // io.ktor.http.cio/parseMultipart|parseMultipart@kotlinx.coroutines.CoroutineScope(io.ktor.utils.io.ByteReadChannel;io.ktor.http.cio.HttpHeadersMap;kotlin.Long){}[0] +final fun (kotlinx.coroutines/CoroutineScope).io.ktor.http.cio/parseMultipart(io.ktor.utils.io/ByteReadChannel, kotlin/CharSequence, kotlin/Long?, kotlin/Long = ...): kotlinx.coroutines.channels/ReceiveChannel // io.ktor.http.cio/parseMultipart|parseMultipart@kotlinx.coroutines.CoroutineScope(io.ktor.utils.io.ByteReadChannel;kotlin.CharSequence;kotlin.Long?;kotlin.Long){}[0] final fun io.ktor.http.cio/encodeChunked(io.ktor.utils.io/ByteWriteChannel, kotlin.coroutines/CoroutineContext): io.ktor.utils.io/ReaderJob // io.ktor.http.cio/encodeChunked|encodeChunked(io.ktor.utils.io.ByteWriteChannel;kotlin.coroutines.CoroutineContext){}[0] final fun io.ktor.http.cio/expectHttpBody(io.ktor.http.cio/Request): kotlin/Boolean // io.ktor.http.cio/expectHttpBody|expectHttpBody(io.ktor.http.cio.Request){}[0] final fun io.ktor.http.cio/expectHttpBody(io.ktor.http/HttpMethod, kotlin/Long, kotlin/CharSequence?, io.ktor.http.cio/ConnectionOptions?, kotlin/CharSequence?): kotlin/Boolean // io.ktor.http.cio/expectHttpBody|expectHttpBody(io.ktor.http.HttpMethod;kotlin.Long;kotlin.CharSequence?;io.ktor.http.cio.ConnectionOptions?;kotlin.CharSequence?){}[0] diff --git a/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/CIOMultipartDataBase.kt b/ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/CIOMultipartDataBase.kt similarity index 88% rename from ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/CIOMultipartDataBase.kt rename to ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/CIOMultipartDataBase.kt index 0003a26548a..65c45647029 100644 --- a/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/CIOMultipartDataBase.kt +++ b/ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/CIOMultipartDataBase.kt @@ -49,7 +49,7 @@ public class CIOMultipartDataBase( val event = events.receive() eventToData(event)?.let { return it } } - } catch (t: ClosedReceiveChannelException) { + } catch (_: ClosedReceiveChannelException) { return null } } @@ -77,13 +77,7 @@ public class CIOMultipartDataBase( val body = part.body if (filename == null) { - val packet = body.readRemaining() // formFieldLimit.toLong()) -// if (!body.exhausted()) { -// val cause = IllegalStateException("Form field size limit exceeded: $formFieldLimit") -// body.cancel(cause) -// throw cause -// } - + val packet = body.readRemaining() packet.use { return PartData.FormItem(it.readText(), { part.release() }, CIOHeaders(headers)) } diff --git a/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/Multipart.kt b/ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/Multipart.kt similarity index 57% rename from ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/Multipart.kt rename to ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/Multipart.kt index 3eeab2f5afa..5c2b5c0530d 100644 --- a/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/Multipart.kt +++ b/ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/Multipart.kt @@ -6,14 +6,11 @@ package io.ktor.http.cio import io.ktor.http.cio.internals.* import io.ktor.utils.io.* -import io.ktor.utils.io.ByteString import io.ktor.utils.io.core.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlinx.io.* import kotlinx.io.bytestring.* -import java.io.EOFException -import java.nio.* /** * Represents a multipart content starting event. Every part need to be completely consumed or released via [release] @@ -56,9 +53,8 @@ public sealed class MultipartEvent { headers.getCompleted().release() } } - runBlocking { - body.discard() - } + + body.discardBlocking() } } @@ -75,11 +71,12 @@ public sealed class MultipartEvent { } } +internal expect fun ByteReadChannel.discardBlocking() + /** * Parse a multipart preamble * @return number of bytes copied */ - private suspend fun parsePreambleImpl( boundary: ByteString, input: ByteReadChannel, @@ -165,74 +162,73 @@ public fun CoroutineScope.parseMultipart( private val CrLf = ByteString("\r\n".toByteArray()) -@OptIn(ExperimentalCoroutinesApi::class, InternalAPI::class) +@OptIn(ExperimentalCoroutinesApi::class) private fun CoroutineScope.parseMultipart( boundaryPrefixed: ByteString, input: ByteReadChannel, totalLength: Long?, maxPartSize: Long -): ReceiveChannel = - produce { - val countedInput = input.counted() - val readBeforeParse = countedInput.totalBytesRead - val firstBoundary = boundaryPrefixed.substring(PrefixString.size) - - val preambleData = writer { - parsePreambleImpl(firstBoundary, countedInput, channel, 8192) - channel.flushAndClose() - }.channel.readRemaining() - - if (preambleData.remaining > 0L) { - send(MultipartEvent.Preamble(preambleData)) - } - - while (!countedInput.isClosedForRead && !countedInput.skipIfFound(PrefixString)) { - countedInput.skipIfFound(CrLf) - - val body = ByteChannel() - val headers = CompletableDeferred() - val part = MultipartEvent.MultipartPart(headers, body) - send(part) - - var headersMap: HttpHeadersMap? = null - try { - headersMap = parsePartHeadersImpl(countedInput) - if (!headers.complete(headersMap)) { - headersMap.release() - throw kotlin.coroutines.cancellation.CancellationException( - "Multipart processing has been cancelled" - ) - } - parsePartBodyImpl(boundaryPrefixed, countedInput, body, headersMap, maxPartSize) - body.close() - } catch (cause: Throwable) { - if (headers.completeExceptionally(cause)) { - headersMap?.release() - } - body.close(cause) - throw cause - } - } +): ReceiveChannel = produce { + val countedInput = input.counted() + val readBeforeParse = countedInput.totalBytesRead + val firstBoundary = boundaryPrefixed.substring(PrefixString.size) + + val preambleData = writer { + parsePreambleImpl(firstBoundary, countedInput, channel, 8193) + channel.flushAndClose() + }.channel.readRemaining() + + if (preambleData.remaining > 0L) { + send(MultipartEvent.Preamble(preambleData)) + } - // Can be followed by two carriage returns - countedInput.skipIfFound(CrLf) + while (!countedInput.isClosedForRead && !countedInput.skipIfFound(PrefixString)) { countedInput.skipIfFound(CrLf) - if (totalLength != null) { - val consumedExceptEpilogue = countedInput.totalBytesRead - readBeforeParse - val size = totalLength - consumedExceptEpilogue - if (size > Int.MAX_VALUE) throw IOException("Failed to parse multipart: prologue is too long") - if (size > 0) { - send(MultipartEvent.Epilogue(countedInput.readPacket(size.toInt()))) + val body = ByteChannel() + val headers = CompletableDeferred() + val part = MultipartEvent.MultipartPart(headers, body) + send(part) + + var headersMap: HttpHeadersMap? = null + try { + headersMap = parsePartHeadersImpl(countedInput) + if (!headers.complete(headersMap)) { + headersMap.release() + throw kotlin.coroutines.cancellation.CancellationException( + "Multipart processing has been cancelled" + ) } - } else { - val epilogueContent = countedInput.readRemaining() - if (!epilogueContent.exhausted()) { - send(MultipartEvent.Epilogue(epilogueContent)) + parsePartBodyImpl(boundaryPrefixed, countedInput, body, headersMap, maxPartSize) + body.close() + } catch (cause: Throwable) { + if (headers.completeExceptionally(cause)) { + headersMap?.release() } + body.close(cause) + throw cause } } + // Can be followed by two carriage returns + countedInput.skipIfFound(CrLf) + countedInput.skipIfFound(CrLf) + + if (totalLength != null) { + val consumedExceptEpilogue = countedInput.totalBytesRead - readBeforeParse + val size = totalLength - consumedExceptEpilogue + if (size > Int.MAX_VALUE) throw IOException("Failed to parse multipart: prologue is too long") + if (size > 0) { + send(MultipartEvent.Epilogue(countedInput.readPacket(size.toInt()))) + } + } else { + val epilogueContent = countedInput.readRemaining() + if (!epilogueContent.exhausted()) { + send(MultipartEvent.Epilogue(epilogueContent)) + } + } +} + private const val PrefixChar = '-'.code.toByte() private val PrefixString = ByteString(PrefixChar, PrefixChar) @@ -297,7 +293,7 @@ private fun findBoundary(contentType: CharSequence): Int { * Parse multipart boundary encoded in [contentType] header value * @return a buffer containing CRLF, prefix '--' and boundary bytes */ -internal fun parseBoundaryInternal(contentType: CharSequence): ByteBuffer { +internal fun parseBoundaryInternal(contentType: CharSequence): ByteArray { val boundaryParameter = findBoundary(contentType) if (boundaryParameter == -1) { @@ -305,11 +301,20 @@ internal fun parseBoundaryInternal(contentType: CharSequence): ByteBuffer { } val boundaryStart = boundaryParameter + 9 - val boundaryBytes: ByteBuffer = ByteBuffer.allocate(74) - boundaryBytes.put(0x0d) - boundaryBytes.put(0x0a) - boundaryBytes.put(PrefixChar) - boundaryBytes.put(PrefixChar) + val boundaryBytes = ByteArray(74) + var position = 0 + + fun put(value: Byte) { + if (position >= boundaryBytes.size) throw IOException( + "Failed to parse multipart: boundary shouldn't be longer than 70 characters" + ) + boundaryBytes[position++] = value + } + + put(0x0d) + put(0x0a) + put(PrefixChar) + put(PrefixChar) var state = 0 // 0 - skipping spaces, 1 - unquoted characters, 2 - quoted no escape, 3 - quoted after escape @@ -336,154 +341,39 @@ internal fun parseBoundaryInternal(contentType: CharSequence): ByteBuffer { } else -> { state = 1 - boundaryBytes.put(v.toByte()) + put(v.toByte()) } } } 1 -> { // non-quoted string if (ch == ' ' || ch == ',' || ch == ';') { // space, comma or semicolon (;) break@loop - } else if (boundaryBytes.hasRemaining()) { - boundaryBytes.put(v.toByte()) } else { - // RFC 2046, sec 5.1.1 - throw IOException("Failed to parse multipart: boundary shouldn't be longer than 70 characters") + put(v.toByte()) } } + 2 -> { if (ch == '\\') { state = 3 } else if (ch == '"') { break@loop - } else if (boundaryBytes.hasRemaining()) { - boundaryBytes.put(v.toByte()) } else { - // RFC 2046, sec 5.1.1 - throw IOException("Failed to parse multipart: boundary shouldn't be longer than 70 characters") + put(v.toByte()) } } 3 -> { - if (boundaryBytes.hasRemaining()) { - boundaryBytes.put(v.toByte()) - state = 2 - } else { - // RFC 2046, sec 5.1.1 - throw IOException("Failed to parse multipart: boundary shouldn't be longer than 70 characters") - } + put(v.toByte()) + state = 2 } } } - boundaryBytes.flip() - - if (boundaryBytes.remaining() == 4) { + if (position == 4) { throw IOException("Empty multipart boundary is not allowed") } - return boundaryBytes -} - -/** - * Tries to skip the specified [delimiter] or fails if encounters bytes differs from the required. - * @return `true` if the delimiter was found and skipped or `false` when EOF. - */ -internal suspend fun ByteReadChannel.skipDelimiterOrEof(delimiter: ByteBuffer): Boolean { - require(delimiter.hasRemaining()) - require(delimiter.remaining() <= DEFAULT_BUFFER_SIZE) { - "Delimiter of ${delimiter.remaining()} bytes is too long: at most $DEFAULT_BUFFER_SIZE bytes could be checked" - } - - var found = false - - lookAhead { - found = tryEnsureDelimiter(delimiter) == delimiter.remaining() - } - - if (found) { - return true - } - - return trySkipDelimiterSuspend(delimiter) -} - -private suspend fun ByteReadChannel.trySkipDelimiterSuspend(delimiter: ByteBuffer): Boolean { - var result = true - - lookAheadSuspend { - if (!awaitAtLeast(delimiter.remaining()) && !awaitAtLeast(1)) { - result = false - return@lookAheadSuspend - } - if (tryEnsureDelimiter(delimiter) != delimiter.remaining()) throw IOException("Broken delimiter occurred") - } - - return result -} - -private fun LookAheadSession.tryEnsureDelimiter(delimiter: ByteBuffer): Int { - val found = startsWithDelimiter(delimiter) - if (found == -1) throw IOException("Failed to skip delimiter: actual bytes differ from delimiter bytes") - if (found < delimiter.remaining()) return found - - consumed(delimiter.remaining()) - return delimiter.remaining() -} - -@Suppress("LoopToCallChain") -private fun ByteBuffer.startsWith( - prefix: ByteBuffer, - prefixSkip: Int = 0 -): Boolean { - val size = minOf(remaining(), prefix.remaining() - prefixSkip) - if (size <= 0) return false - - val position = position() - val prefixPosition = prefix.position() + prefixSkip - - for (i in 0 until size) { - if (get(position + i) != prefix.get(prefixPosition + i)) return false - } - - return true -} - -/** - * @return Number of bytes of the delimiter found (possibly 0 if no bytes available yet) or -1 if it doesn't start - */ -private fun LookAheadSession.startsWithDelimiter(delimiter: ByteBuffer): Int { - val buffer = request(0, 1) ?: return 0 - val index = buffer.indexOfPartial(delimiter) - if (index != 0) return -1 - - val found = minOf(buffer.remaining() - index, delimiter.remaining()) - val notKnown = delimiter.remaining() - found - - if (notKnown > 0) { - val next = request(index + found, notKnown) ?: return found - if (!next.startsWith(delimiter, found)) return -1 - } - - return delimiter.remaining() -} - -@Suppress("LoopToCallChain") -private fun ByteBuffer.indexOfPartial(sub: ByteBuffer): Int { - val subPosition = sub.position() - val subSize = sub.remaining() - val first = sub[subPosition] - val limit = limit() - - outer@ for (idx in position() until limit) { - if (get(idx) == first) { - for (j in 1 until subSize) { - if (idx + j == limit) break - if (get(idx + j) != sub.get(subPosition + j)) continue@outer - } - return idx - position() - } - } - - return -1 + return boundaryBytes.copyOfRange(0, position) } private fun throwLimitExceeded(actual: Long, limit: Long): Nothing = diff --git a/ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/RequestResponseBuilderCommon.kt b/ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/RequestResponseBuilderCommon.kt index 98d7c72e043..082354377aa 100644 --- a/ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/RequestResponseBuilderCommon.kt +++ b/ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/RequestResponseBuilderCommon.kt @@ -5,7 +5,6 @@ package io.ktor.http.cio import io.ktor.http.* -import io.ktor.utils.io.core.* import kotlinx.io.* /** @@ -53,7 +52,3 @@ public expect class RequestResponseBuilder() { */ public fun release() } - -private const val SP: Byte = 0x20 -private const val CR: Byte = 0x0d -private const val LF: Byte = 0x0a diff --git a/ktor-http/ktor-http-cio/jsAndWasmShared/src/io/ktor/http/cio/MultipartJsAndWasm.kt b/ktor-http/ktor-http-cio/jsAndWasmShared/src/io/ktor/http/cio/MultipartJsAndWasm.kt new file mode 100644 index 00000000000..2fb66098744 --- /dev/null +++ b/ktor-http/ktor-http-cio/jsAndWasmShared/src/io/ktor/http/cio/MultipartJsAndWasm.kt @@ -0,0 +1,11 @@ +package io.ktor.http.cio + +import io.ktor.utils.io.* + +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +internal actual fun ByteReadChannel.discardBlocking() { + cancel() +} diff --git a/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/RequestResponseBuilder.kt b/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/RequestResponseBuilder.kt index c38581fd8dd..8511dc41dbd 100644 --- a/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/RequestResponseBuilder.kt +++ b/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/RequestResponseBuilder.kt @@ -5,7 +5,6 @@ package io.ktor.http.cio import io.ktor.http.* -import io.ktor.utils.io.* import io.ktor.utils.io.core.* import kotlinx.io.* import java.nio.* diff --git a/ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/MultipartTest.kt b/ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/MultipartTest.kt index ca9372fbbfc..cc539c2cfac 100644 --- a/ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/MultipartTest.kt +++ b/ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/MultipartTest.kt @@ -432,11 +432,7 @@ class MultipartTest { private fun testBoundary(expectedBoundary: String, headerValue: String) { val boundary = parseBoundaryInternal(headerValue) - val actualBoundary = String( - boundary.array(), - boundary.arrayOffset() + boundary.position(), - boundary.remaining() - ) + val actualBoundary = String(boundary) assertEquals(expectedBoundary, actualBoundary) } diff --git a/ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/TrySkipDelimiterTest.kt b/ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/TrySkipDelimiterTest.kt deleted file mode 100644 index a2d7ff24930..00000000000 --- a/ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/TrySkipDelimiterTest.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ - -package io.ktor.tests.http.cio - -import io.ktor.http.cio.* -import io.ktor.utils.io.* -import kotlinx.coroutines.* -import kotlinx.coroutines.test.* -import java.nio.* -import kotlin.test.* - -class TrySkipDelimiterTest { - private val ch = ByteChannel() - - @Test - fun testSmoke(): Unit = runTest { - ch.writeFully(byteArrayOf(1, 2, 3)) - ch.close() - - val delimiter = ByteBuffer.wrap(byteArrayOf(1, 2)) - assertTrue(ch.skipDelimiterOrEof(delimiter)) - assertEquals(3, ch.readByte()) - assertTrue(ch.isClosedForRead) - } - - @OptIn(InternalAPI::class) - @Test - fun testSmokeWithOffsetShift(): Unit = runTest { - ch.writeFully(byteArrayOf(9, 1, 2, 3)) - ch.close() - - val delimiter = ByteBuffer.wrap(byteArrayOf(1, 2)) - ch.discard(1) - assertTrue(ch.skipDelimiterOrEof(delimiter)) - assertEquals(3, ch.readByte()) - assertTrue(ch.isClosedForRead) - } - - @OptIn(InternalAPI::class) - @Test - fun testEmpty(): Unit = runTest { - ch.close() - - val delimiter = ByteBuffer.wrap(byteArrayOf(1, 2)) - assertFalse(ch.skipDelimiterOrEof(delimiter)) - } - - @OptIn(InternalAPI::class) - @Test - fun testFull(): Unit = runTest { - ch.writeFully(byteArrayOf(1, 2)) - ch.close() - - val delimiter = ByteBuffer.wrap(byteArrayOf(1, 2)) - assertTrue(ch.skipDelimiterOrEof(delimiter)) - assertTrue(ch.isClosedForRead) - } - - @OptIn(InternalAPI::class) - @Test - fun testIncomplete(): Unit = runTest { - ch.writeFully(byteArrayOf(1, 2)) - ch.close() - - val delimiter = ByteBuffer.wrap(byteArrayOf(1, 2, 3)) - assertFails { - ch.skipDelimiterOrEof(delimiter) - } - } - - @OptIn(InternalAPI::class) - @Test - fun testOtherBytes(): Unit = runTest { - ch.writeFully(byteArrayOf(7, 8)) - ch.close() - - val delimiter = ByteBuffer.wrap(byteArrayOf(1, 2)) - - assertFails { - ch.skipDelimiterOrEof(delimiter) - } - - // content shouldn't be consumed - assertEquals(7, ch.readByte()) - assertEquals(8, ch.readByte()) - assertTrue(ch.isClosedForRead) - } - - @Test - fun testTimeSplit(): Unit = runTest { - val writer = launch(CoroutineName("writer"), start = CoroutineStart.LAZY) { - ch.writeByte(2) - ch.close() - } - - ch.writeByte(1) - ch.flush() - writer.start() - - val delimiter = ByteBuffer.wrap(byteArrayOf(1, 2)) - - assertTrue(ch.skipDelimiterOrEof(delimiter)) - - assertTrue(ch.isClosedForRead) - } - - @Test - fun testTimeSplitNonClosed(): Unit = runTest { - val writer = launch(CoroutineName("writer"), start = CoroutineStart.LAZY) { - ch.writeByte(2) - ch.flush() - } - - ch.writeByte(1) - ch.flush() - writer.start() - - val delimiter = ByteBuffer.wrap(byteArrayOf(1, 2)) - - assertTrue(ch.skipDelimiterOrEof(delimiter)) - assertFalse(ch.isClosedForRead) - ch.cancel() - } - - @Test - fun testTimeSplitWrongBytes(): Unit = runTest { - val writer = launch(CoroutineName("writer"), start = CoroutineStart.LAZY) { - ch.writeByte(33) - ch.flush() - } - - ch.writeByte(1) - ch.flush() - writer.start() - - val delimiter = ByteBuffer.wrap(byteArrayOf(1, 2)) - - assertFails { - ch.skipDelimiterOrEof(delimiter) - } - - assertEquals(2, ch.availableForRead) - } - - @Test - fun testSkipTooLongDelimiter(): Unit = runTest { - assertFails { - ch.skipDelimiterOrEof(ByteBuffer.allocate(DEFAULT_BUFFER_SIZE * 2)) - } - } -} diff --git a/ktor-http/ktor-http-cio/jvmAndPosix/src/MultipartJvmAndPosix.kt b/ktor-http/ktor-http-cio/jvmAndPosix/src/MultipartJvmAndPosix.kt new file mode 100644 index 00000000000..b3f276a0f98 --- /dev/null +++ b/ktor-http/ktor-http-cio/jvmAndPosix/src/MultipartJvmAndPosix.kt @@ -0,0 +1,14 @@ +package io.ktor.http.cio + +import io.ktor.utils.io.* +import kotlinx.coroutines.* + +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +internal actual fun ByteReadChannel.discardBlocking() { + runBlocking { + discard() + } +} diff --git a/ktor-io/api/ktor-io.api b/ktor-io/api/ktor-io.api index a9c0b9bec17..715e6bfc794 100644 --- a/ktor-io/api/ktor-io.api +++ b/ktor-io/api/ktor-io.api @@ -210,6 +210,17 @@ public final class io/ktor/utils/io/CountedByteWriteChannelKt { public static final fun counted (Lio/ktor/utils/io/ByteWriteChannel;)Lio/ktor/utils/io/CountedByteWriteChannel; } +public final class io/ktor/utils/io/DefaultJvmSerializerReplacement : java/io/Externalizable { + public static final field Companion Lio/ktor/utils/io/DefaultJvmSerializerReplacement$Companion; + public fun ()V + public fun (Lio/ktor/utils/io/JvmSerializer;Ljava/lang/Object;)V + public fun readExternal (Ljava/io/ObjectInput;)V + public fun writeExternal (Ljava/io/ObjectOutput;)V +} + +public final class io/ktor/utils/io/DefaultJvmSerializerReplacement$Companion { +} + public final class io/ktor/utils/io/DeprecationKt { public static final fun readText (Lkotlinx/io/Source;)Ljava/lang/String; public static final fun release (Lkotlinx/io/Sink;)V @@ -218,6 +229,15 @@ public final class io/ktor/utils/io/DeprecationKt { public abstract interface annotation class io/ktor/utils/io/InternalAPI : java/lang/annotation/Annotation { } +public final class io/ktor/utils/io/JvmSerializable_jvmKt { + public static final fun JvmSerializerReplacement (Lio/ktor/utils/io/JvmSerializer;Ljava/lang/Object;)Ljava/lang/Object; +} + +public abstract interface class io/ktor/utils/io/JvmSerializer : java/io/Serializable { + public abstract fun jvmDeserialize ([B)Ljava/lang/Object; + public abstract fun jvmSerialize (Ljava/lang/Object;)[B +} + public abstract interface annotation class io/ktor/utils/io/KtorDsl : java/lang/annotation/Annotation { } diff --git a/ktor-io/api/ktor-io.klib.api b/ktor-io/api/ktor-io.klib.api index 882e4489235..2f3e0666a3f 100644 --- a/ktor-io/api/ktor-io.klib.api +++ b/ktor-io/api/ktor-io.klib.api @@ -53,7 +53,12 @@ abstract interface <#A: kotlin/Any> io.ktor.utils.io.pool/ObjectPool : kotlin/Au open fun close() // io.ktor.utils.io.pool/ObjectPool.close|close(){}[0] } -abstract interface io.ktor.utils.io.core/Closeable { // io.ktor.utils.io.core/Closeable|null[0] +abstract interface <#A: kotlin/Any?> io.ktor.utils.io/JvmSerializer : io.ktor.utils.io/JvmSerializable { // io.ktor.utils.io/JvmSerializer|null[0] + abstract fun jvmDeserialize(kotlin/ByteArray): #A // io.ktor.utils.io/JvmSerializer.jvmDeserialize|jvmDeserialize(kotlin.ByteArray){}[0] + abstract fun jvmSerialize(#A): kotlin/ByteArray // io.ktor.utils.io/JvmSerializer.jvmSerialize|jvmSerialize(1:0){}[0] +} + +abstract interface io.ktor.utils.io.core/Closeable : kotlin/AutoCloseable { // io.ktor.utils.io.core/Closeable|null[0] abstract fun close() // io.ktor.utils.io.core/Closeable.close|close(){}[0] } @@ -97,6 +102,8 @@ abstract interface io.ktor.utils.io/ChannelJob { // io.ktor.utils.io/ChannelJob| abstract fun (): kotlinx.coroutines/Job // io.ktor.utils.io/ChannelJob.job.|(){}[0] } +abstract interface io.ktor.utils.io/JvmSerializable // io.ktor.utils.io/JvmSerializable|null[0] + abstract class <#A: kotlin/Any> io.ktor.utils.io.pool/DefaultPool : io.ktor.utils.io.pool/ObjectPool<#A> { // io.ktor.utils.io.pool/DefaultPool|null[0] constructor (kotlin/Int) // io.ktor.utils.io.pool/DefaultPool.|(kotlin.Int){}[0] @@ -399,6 +406,7 @@ final fun (kotlinx.io/Source).io.ktor.utils.io.core/readTextExactCharacters(kotl final fun (kotlinx.io/Source).io.ktor.utils.io.core/release() // io.ktor.utils.io.core/release|release@kotlinx.io.Source(){}[0] final fun (kotlinx.io/Source).io.ktor.utils.io.core/takeWhile(kotlin/Function1) // io.ktor.utils.io.core/takeWhile|takeWhile@kotlinx.io.Source(kotlin.Function1){}[0] final fun (kotlinx.io/Source).io.ktor.utils.io/readText(): kotlin/String // io.ktor.utils.io/readText|readText@kotlinx.io.Source(){}[0] +final fun <#A: kotlin/Any> io.ktor.utils.io/JvmSerializerReplacement(io.ktor.utils.io/JvmSerializer<#A>, #A): kotlin/Any // io.ktor.utils.io/JvmSerializerReplacement|JvmSerializerReplacement(io.ktor.utils.io.JvmSerializer<0:0>;0:0){0§}[0] final fun <#A: kotlin/Any?> (kotlinx.io/Sink).io.ktor.utils.io.core/preview(kotlin/Function1): #A // io.ktor.utils.io.core/preview|preview@kotlinx.io.Sink(kotlin.Function1){0§}[0] final fun <#A: kotlin/Any?> (kotlinx.io/Source).io.ktor.utils.io.core/preview(kotlin/Function1): #A // io.ktor.utils.io.core/preview|preview@kotlinx.io.Source(kotlin.Function1){0§}[0] final fun <#A: kotlin/Any?> io.ktor.utils.io.core/withMemory(kotlin/Int, kotlin/Function1): #A // io.ktor.utils.io.core/withMemory|withMemory(kotlin.Int;kotlin.Function1){0§}[0] diff --git a/ktor-io/common/src/io/ktor/utils/io/ByteChannel.kt b/ktor-io/common/src/io/ktor/utils/io/ByteChannel.kt index 822cf1542b1..816ed2c176b 100644 --- a/ktor-io/common/src/io/ktor/utils/io/ByteChannel.kt +++ b/ktor-io/common/src/io/ktor/utils/io/ByteChannel.kt @@ -272,5 +272,4 @@ public class ByteChannel(public val autoFlush: Boolean = false) : ByteReadChanne public class ConcurrentIOException( taskName: String, cause: Throwable? = null -) : IllegalStateException("Concurrent $taskName attempts", cause) { -} +) : IllegalStateException("Concurrent $taskName attempts", cause) diff --git a/ktor-io/common/src/io/ktor/utils/io/JvmSerializable.kt b/ktor-io/common/src/io/ktor/utils/io/JvmSerializable.kt new file mode 100644 index 00000000000..ae6f583e692 --- /dev/null +++ b/ktor-io/common/src/io/ktor/utils/io/JvmSerializable.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.utils.io + +/** Alias for `java.io.Serializable` on JVM. Empty interface otherwise. */ +@InternalAPI +public expect interface JvmSerializable + +@InternalAPI +public interface JvmSerializer : JvmSerializable { + public fun jvmSerialize(value: T): ByteArray + public fun jvmDeserialize(value: ByteArray): T +} + +@InternalAPI +public expect fun JvmSerializerReplacement(serializer: JvmSerializer, value: T): Any + +internal object DummyJvmSimpleSerializerReplacement diff --git a/ktor-io/common/src/io/ktor/utils/io/core/Closeable.kt b/ktor-io/common/src/io/ktor/utils/io/core/Closeable.kt index 90567a0231f..043fab49be2 100644 --- a/ktor-io/common/src/io/ktor/utils/io/core/Closeable.kt +++ b/ktor-io/common/src/io/ktor/utils/io/core/Closeable.kt @@ -4,25 +4,9 @@ package io.ktor.utils.io.core -public expect interface Closeable { - public fun close() -} +import kotlin.use as stdlibUse -public inline fun T.use(block: (T) -> R): R { - var closed = false - try { - return block(this) - } catch (cause: Throwable) { - closed = true - try { - this?.close() - } catch (closeException: Throwable) { - cause.addSuppressed(closeException) - } - throw cause - } finally { - if (!closed) { - this?.close() - } - } -} +public expect interface Closeable : AutoCloseable + +@Deprecated("Use stdlib implementation instead. Remove import of this function", ReplaceWith("stdlibUse(block)")) +public inline fun T.use(block: (T) -> R): R = stdlibUse(block) diff --git a/ktor-io/js/src/io/ktor/utils/io/core/Closeable.js.kt b/ktor-io/js/src/io/ktor/utils/io/core/Closeable.js.kt index 09561abbac7..63ae1eeb0f4 100644 --- a/ktor-io/js/src/io/ktor/utils/io/core/Closeable.js.kt +++ b/ktor-io/js/src/io/ktor/utils/io/core/Closeable.js.kt @@ -4,6 +4,6 @@ package io.ktor.utils.io.core -public actual interface Closeable { - public actual fun close() +public actual interface Closeable : AutoCloseable { + override fun close() } diff --git a/ktor-io/jsAndWasmShared/src/io/ktor/utils/io/JvmSerializable.jsAndWasmShared.kt b/ktor-io/jsAndWasmShared/src/io/ktor/utils/io/JvmSerializable.jsAndWasmShared.kt new file mode 100644 index 00000000000..44454f494a0 --- /dev/null +++ b/ktor-io/jsAndWasmShared/src/io/ktor/utils/io/JvmSerializable.jsAndWasmShared.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.utils.io + +@InternalAPI +public actual interface JvmSerializable + +@InternalAPI +public actual fun JvmSerializerReplacement(serializer: JvmSerializer, value: T): Any = + DummyJvmSimpleSerializerReplacement diff --git a/ktor-io/jvm/src/io/ktor/utils/io/JvmSerializable.jvm.kt b/ktor-io/jvm/src/io/ktor/utils/io/JvmSerializable.jvm.kt new file mode 100644 index 00000000000..608cc65229a --- /dev/null +++ b/ktor-io/jvm/src/io/ktor/utils/io/JvmSerializable.jvm.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.utils.io + +import java.io.* + +@InternalAPI +public actual typealias JvmSerializable = Serializable + +@Suppress("UNCHECKED_CAST") +@InternalAPI +public actual fun JvmSerializerReplacement(serializer: JvmSerializer, value: T): Any = + DefaultJvmSerializerReplacement(serializer, value) + +@OptIn(InternalAPI::class) +@PublishedApi // IMPORTANT: changing the class name would result in serialization incompatibility +internal class DefaultJvmSerializerReplacement( + private var serializer: JvmSerializer?, + private var value: T? +) : Externalizable { + constructor() : this(null, null) + + override fun writeExternal(out: ObjectOutput) { + out.writeObject(serializer) + out.writeObject(serializer!!.jvmSerialize(value!!)) + } + + @Suppress("UNCHECKED_CAST") + override fun readExternal(`in`: ObjectInput) { + serializer = `in`.readObject() as JvmSerializer + value = serializer!!.jvmDeserialize(`in`.readObject() as ByteArray) + } + + private fun readResolve(): Any = + value!! + + companion object { + private const val serialVersionUID: Long = 0L + } +} diff --git a/ktor-io/posix/src/io/ktor/utils/io/JvmSerializable.posix.kt b/ktor-io/posix/src/io/ktor/utils/io/JvmSerializable.posix.kt new file mode 100644 index 00000000000..44454f494a0 --- /dev/null +++ b/ktor-io/posix/src/io/ktor/utils/io/JvmSerializable.posix.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.utils.io + +@InternalAPI +public actual interface JvmSerializable + +@InternalAPI +public actual fun JvmSerializerReplacement(serializer: JvmSerializer, value: T): Any = + DummyJvmSimpleSerializerReplacement diff --git a/ktor-io/posix/src/io/ktor/utils/io/core/Closeable.posix.kt b/ktor-io/posix/src/io/ktor/utils/io/core/Closeable.posix.kt index 09561abbac7..63ae1eeb0f4 100644 --- a/ktor-io/posix/src/io/ktor/utils/io/core/Closeable.posix.kt +++ b/ktor-io/posix/src/io/ktor/utils/io/core/Closeable.posix.kt @@ -4,6 +4,6 @@ package io.ktor.utils.io.core -public actual interface Closeable { - public actual fun close() +public actual interface Closeable : AutoCloseable { + override fun close() } diff --git a/ktor-io/wasmJs/src/io/ktor/utils/io/core/Closeable.wasmJs.kt b/ktor-io/wasmJs/src/io/ktor/utils/io/core/Closeable.wasmJs.kt index 09561abbac7..63ae1eeb0f4 100644 --- a/ktor-io/wasmJs/src/io/ktor/utils/io/core/Closeable.wasmJs.kt +++ b/ktor-io/wasmJs/src/io/ktor/utils/io/core/Closeable.wasmJs.kt @@ -4,6 +4,6 @@ package io.ktor.utils.io.core -public actual interface Closeable { - public actual fun close() +public actual interface Closeable : AutoCloseable { + override fun close() } diff --git a/ktor-network/api/ktor-network.api b/ktor-network/api/ktor-network.api index 13d0d4eb9fd..7fb6a096f8a 100644 --- a/ktor-network/api/ktor-network.api +++ b/ktor-network/api/ktor-network.api @@ -341,7 +341,9 @@ public final class io/ktor/network/sockets/TypeOfService$Companion { public final class io/ktor/network/sockets/UDPSocketBuilder : io/ktor/network/sockets/Configurable { public final fun bind (Lio/ktor/network/sockets/SocketAddress;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun bind (Ljava/lang/String;ILkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun bind$default (Lio/ktor/network/sockets/UDPSocketBuilder;Lio/ktor/network/sockets/SocketAddress;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun bind$default (Lio/ktor/network/sockets/UDPSocketBuilder;Ljava/lang/String;ILkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public synthetic fun configure (Lkotlin/jvm/functions/Function1;)Lio/ktor/network/sockets/Configurable; public fun configure (Lkotlin/jvm/functions/Function1;)Lio/ktor/network/sockets/UDPSocketBuilder; public final fun connect (Lio/ktor/network/sockets/SocketAddress;Lio/ktor/network/sockets/SocketAddress;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/ktor-network/api/ktor-network.klib.api b/ktor-network/api/ktor-network.klib.api index d2cdca9a47b..3af84ac69cc 100644 --- a/ktor-network/api/ktor-network.klib.api +++ b/ktor-network/api/ktor-network.klib.api @@ -1,5 +1,6 @@ // Klib ABI Dump -// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] +// Alias: native => [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] // Rendering settings: // - Signature version: 2 // - Show manifest properties: true @@ -36,11 +37,6 @@ abstract interface <#A: out io.ktor.network.sockets/Configurable<#A, #B>, #B: io open fun configure(kotlin/Function1<#B, kotlin/Unit>): #A // io.ktor.network.sockets/Configurable.configure|configure(kotlin.Function1<1:1,kotlin.Unit>){}[0] } -abstract interface io.ktor.network.selector/Selectable { // io.ktor.network.selector/Selectable|null[0] - abstract val descriptor // io.ktor.network.selector/Selectable.descriptor|{}descriptor[0] - abstract fun (): kotlin/Int // io.ktor.network.selector/Selectable.descriptor.|(){}[0] -} - abstract interface io.ktor.network.selector/SelectorManager : io.ktor.utils.io.core/Closeable, kotlinx.coroutines/CoroutineScope { // io.ktor.network.selector/SelectorManager|null[0] abstract fun notifyClosed(io.ktor.network.selector/Selectable) // io.ktor.network.selector/SelectorManager.notifyClosed|notifyClosed(io.ktor.network.selector.Selectable){}[0] abstract suspend fun select(io.ktor.network.selector/Selectable, io.ktor.network.selector/SelectInterest) // io.ktor.network.selector/SelectorManager.select|select(io.ktor.network.selector.Selectable;io.ktor.network.selector.SelectInterest){}[0] @@ -103,10 +99,6 @@ final class io.ktor.network.selector/ClosedChannelCancellationException : kotlin constructor () // io.ktor.network.selector/ClosedChannelCancellationException.|(){}[0] } -final class io.ktor.network.selector/SocketError : kotlin/IllegalStateException { // io.ktor.network.selector/SocketError|null[0] - constructor () // io.ktor.network.selector/SocketError.|(){}[0] -} - final class io.ktor.network.sockets/Connection { // io.ktor.network.sockets/Connection|null[0] constructor (io.ktor.network.sockets/Socket, io.ktor.utils.io/ByteReadChannel, io.ktor.utils.io/ByteWriteChannel) // io.ktor.network.sockets/Connection.|(io.ktor.network.sockets.Socket;io.ktor.utils.io.ByteReadChannel;io.ktor.utils.io.ByteWriteChannel){}[0] @@ -174,6 +166,7 @@ final class io.ktor.network.sockets/UDPSocketBuilder : io.ktor.network.sockets/C final fun (io.ktor.network.sockets/SocketOptions.UDPSocketOptions) // io.ktor.network.sockets/UDPSocketBuilder.options.|(io.ktor.network.sockets.SocketOptions.UDPSocketOptions){}[0] final suspend fun bind(io.ktor.network.sockets/SocketAddress? = ..., kotlin/Function1 = ...): io.ktor.network.sockets/BoundDatagramSocket // io.ktor.network.sockets/UDPSocketBuilder.bind|bind(io.ktor.network.sockets.SocketAddress?;kotlin.Function1){}[0] + final suspend fun bind(kotlin/String = ..., kotlin/Int = ..., kotlin/Function1 = ...): io.ktor.network.sockets/BoundDatagramSocket // io.ktor.network.sockets/UDPSocketBuilder.bind|bind(kotlin.String;kotlin.Int;kotlin.Function1){}[0] final suspend fun connect(io.ktor.network.sockets/SocketAddress, io.ktor.network.sockets/SocketAddress? = ..., kotlin/Function1 = ...): io.ktor.network.sockets/ConnectedDatagramSocket // io.ktor.network.sockets/UDPSocketBuilder.connect|connect(io.ktor.network.sockets.SocketAddress;io.ktor.network.sockets.SocketAddress?;kotlin.Function1){}[0] } @@ -284,3 +277,17 @@ final fun <#A: io.ktor.network.sockets/Configurable<#A, *>> (#A).io.ktor.network final fun io.ktor.network.selector/SelectorManager(kotlin.coroutines/CoroutineContext = ...): io.ktor.network.selector/SelectorManager // io.ktor.network.selector/SelectorManager|SelectorManager(kotlin.coroutines.CoroutineContext){}[0] final fun io.ktor.network.sockets/aSocket(io.ktor.network.selector/SelectorManager): io.ktor.network.sockets/SocketBuilder // io.ktor.network.sockets/aSocket|aSocket(io.ktor.network.selector.SelectorManager){}[0] final suspend fun (io.ktor.network.sockets/ASocket).io.ktor.network.sockets/awaitClosed() // io.ktor.network.sockets/awaitClosed|awaitClosed@io.ktor.network.sockets.ASocket(){}[0] + +// Targets: [native] +abstract interface io.ktor.network.selector/Selectable { // io.ktor.network.selector/Selectable|null[0] + abstract val descriptor // io.ktor.network.selector/Selectable.descriptor|{}descriptor[0] + abstract fun (): kotlin/Int // io.ktor.network.selector/Selectable.descriptor.|(){}[0] +} + +// Targets: [native] +final class io.ktor.network.selector/SocketError : kotlin/IllegalStateException { // io.ktor.network.selector/SocketError|null[0] + constructor () // io.ktor.network.selector/SocketError.|(){}[0] +} + +// Targets: [js, wasmJs] +abstract interface io.ktor.network.selector/Selectable // io.ktor.network.selector/Selectable|null[0] diff --git a/ktor-network/build.gradle.kts b/ktor-network/build.gradle.kts index a3c871ddec5..b2ac809bb7d 100644 --- a/ktor-network/build.gradle.kts +++ b/ktor-network/build.gradle.kts @@ -10,13 +10,13 @@ kotlin { } sourceSets { - jvmAndPosixMain { + commonMain { dependencies { api(project(":ktor-utils")) } } - jvmAndPosixTest { + commonTest { dependencies { api(project(":ktor-test-dispatcher")) } diff --git a/ktor-network/jvmAndPosix/src/io/ktor/network/selector/Selectable.kt b/ktor-network/common/src/io/ktor/network/selector/Selectable.kt similarity index 100% rename from ktor-network/jvmAndPosix/src/io/ktor/network/selector/Selectable.kt rename to ktor-network/common/src/io/ktor/network/selector/Selectable.kt diff --git a/ktor-network/jvmAndPosix/src/io/ktor/network/selector/SelectorManagerCommon.kt b/ktor-network/common/src/io/ktor/network/selector/SelectorManagerCommon.kt similarity index 91% rename from ktor-network/jvmAndPosix/src/io/ktor/network/selector/SelectorManagerCommon.kt rename to ktor-network/common/src/io/ktor/network/selector/SelectorManagerCommon.kt index 72790f980ae..214c1571ab6 100644 --- a/ktor-network/jvmAndPosix/src/io/ktor/network/selector/SelectorManagerCommon.kt +++ b/ktor-network/common/src/io/ktor/network/selector/SelectorManagerCommon.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + package io.ktor.network.selector import io.ktor.utils.io.core.* @@ -7,7 +11,6 @@ import kotlin.coroutines.* /** * Creates the selector manager for current platform. */ -@Suppress("FunctionName") public expect fun SelectorManager( dispatcher: CoroutineContext = EmptyCoroutineContext ): SelectorManager diff --git a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/Builders.kt b/ktor-network/common/src/io/ktor/network/sockets/Builders.kt similarity index 100% rename from ktor-network/jvmAndPosix/src/io/ktor/network/sockets/Builders.kt rename to ktor-network/common/src/io/ktor/network/sockets/Builders.kt diff --git a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/Datagram.kt b/ktor-network/common/src/io/ktor/network/sockets/Datagram.kt similarity index 92% rename from ktor-network/jvmAndPosix/src/io/ktor/network/sockets/Datagram.kt rename to ktor-network/common/src/io/ktor/network/sockets/Datagram.kt index 003f542ce60..ea916b86f9f 100644 --- a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/Datagram.kt +++ b/ktor-network/common/src/io/ktor/network/sockets/Datagram.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.sockets diff --git a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/SocketAddress.kt b/ktor-network/common/src/io/ktor/network/sockets/SocketAddress.kt similarity index 96% rename from ktor-network/jvmAndPosix/src/io/ktor/network/sockets/SocketAddress.kt rename to ktor-network/common/src/io/ktor/network/sockets/SocketAddress.kt index eb66b7062bc..d7df452da83 100644 --- a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/SocketAddress.kt +++ b/ktor-network/common/src/io/ktor/network/sockets/SocketAddress.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.network.sockets diff --git a/ktor-network/common/src/io/ktor/network/sockets/SocketEngine.kt b/ktor-network/common/src/io/ktor/network/sockets/SocketEngine.kt new file mode 100644 index 00000000000..479cb567153 --- /dev/null +++ b/ktor-network/common/src/io/ktor/network/sockets/SocketEngine.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.sockets + +import io.ktor.network.selector.* + +internal expect suspend fun tcpConnect( + selector: SelectorManager, + remoteAddress: SocketAddress, + socketOptions: SocketOptions.TCPClientSocketOptions +): Socket + +internal expect suspend fun tcpBind( + selector: SelectorManager, + localAddress: SocketAddress?, + socketOptions: SocketOptions.AcceptorOptions +): ServerSocket + +internal expect suspend fun udpConnect( + selector: SelectorManager, + remoteAddress: SocketAddress, + localAddress: SocketAddress?, + options: SocketOptions.UDPSocketOptions +): ConnectedDatagramSocket + +internal expect suspend fun udpBind( + selector: SelectorManager, + localAddress: SocketAddress?, + options: SocketOptions.UDPSocketOptions +): BoundDatagramSocket diff --git a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/SocketOptions.kt b/ktor-network/common/src/io/ktor/network/sockets/SocketOptions.kt similarity index 96% rename from ktor-network/jvmAndPosix/src/io/ktor/network/sockets/SocketOptions.kt rename to ktor-network/common/src/io/ktor/network/sockets/SocketOptions.kt index 539536086da..5bf016ac4e7 100644 --- a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/SocketOptions.kt +++ b/ktor-network/common/src/io/ktor/network/sockets/SocketOptions.kt @@ -4,12 +4,11 @@ package io.ktor.network.sockets -internal const val INFINITE_TIMEOUT_MS = Long.MAX_VALUE +private const val INFINITE_TIMEOUT_MS = Long.MAX_VALUE /** * Socket options builder */ -@OptIn(ExperimentalUnsignedTypes::class) public sealed class SocketOptions( protected val customOptions: MutableMap ) { @@ -30,7 +29,7 @@ public sealed class SocketOptions( } } - internal fun acceptor(): AcceptorOptions { + internal fun tcpAccept(): AcceptorOptions { return AcceptorOptions(HashMap(customOptions)).apply { copyCommon(this@SocketOptions) } @@ -113,7 +112,7 @@ public sealed class SocketOptions( } } - internal fun tcp(): TCPClientSocketOptions { + internal fun tcpConnect(): TCPClientSocketOptions { return TCPClientSocketOptions(HashMap(customOptions)).apply { copyCommon(this@PeerSocketOptions) } diff --git a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/Sockets.kt b/ktor-network/common/src/io/ktor/network/sockets/Sockets.kt similarity index 96% rename from ktor-network/jvmAndPosix/src/io/ktor/network/sockets/Sockets.kt rename to ktor-network/common/src/io/ktor/network/sockets/Sockets.kt index cdaca9436ca..c8a9fa005b7 100644 --- a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/Sockets.kt +++ b/ktor-network/common/src/io/ktor/network/sockets/Sockets.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.sockets diff --git a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/TcpSocketBuilder.kt b/ktor-network/common/src/io/ktor/network/sockets/TcpSocketBuilder.kt similarity index 88% rename from ktor-network/jvmAndPosix/src/io/ktor/network/sockets/TcpSocketBuilder.kt rename to ktor-network/common/src/io/ktor/network/sockets/TcpSocketBuilder.kt index 99342ff9e32..ec88283f44e 100644 --- a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/TcpSocketBuilder.kt +++ b/ktor-network/common/src/io/ktor/network/sockets/TcpSocketBuilder.kt @@ -37,7 +37,7 @@ public class TcpSocketBuilder internal constructor( public suspend fun connect( remoteAddress: SocketAddress, configure: SocketOptions.TCPClientSocketOptions.() -> Unit = {} - ): Socket = connect(selector, remoteAddress, options.tcp().apply(configure)) + ): Socket = tcpConnect(selector, remoteAddress, options.tcpConnect().apply(configure)) /** * Bind server socket to listen to [localAddress]. @@ -45,5 +45,5 @@ public class TcpSocketBuilder internal constructor( public suspend fun bind( localAddress: SocketAddress? = null, configure: SocketOptions.AcceptorOptions.() -> Unit = {} - ): ServerSocket = bind(selector, localAddress, options.acceptor().apply(configure)) + ): ServerSocket = tcpBind(selector, localAddress, options.tcpAccept().apply(configure)) } diff --git a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/TypeOfService.kt b/ktor-network/common/src/io/ktor/network/sockets/TypeOfService.kt similarity index 87% rename from ktor-network/jvmAndPosix/src/io/ktor/network/sockets/TypeOfService.kt rename to ktor-network/common/src/io/ktor/network/sockets/TypeOfService.kt index df2fb6324e0..f080844b264 100644 --- a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/TypeOfService.kt +++ b/ktor-network/common/src/io/ktor/network/sockets/TypeOfService.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.sockets diff --git a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/UDPSocketBuilder.kt b/ktor-network/common/src/io/ktor/network/sockets/UDPSocketBuilder.kt similarity index 67% rename from ktor-network/jvmAndPosix/src/io/ktor/network/sockets/UDPSocketBuilder.kt rename to ktor-network/common/src/io/ktor/network/sockets/UDPSocketBuilder.kt index 5fb65194de3..7b680260c45 100644 --- a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/UDPSocketBuilder.kt +++ b/ktor-network/common/src/io/ktor/network/sockets/UDPSocketBuilder.kt @@ -19,7 +19,16 @@ public class UDPSocketBuilder internal constructor( public suspend fun bind( localAddress: SocketAddress? = null, configure: SocketOptions.UDPSocketOptions.() -> Unit = {} - ): BoundDatagramSocket = bindUDP(selector, localAddress, options.udp().apply(configure)) + ): BoundDatagramSocket = udpBind(selector, localAddress, options.udp().apply(configure)) + + /** + * Bind server socket at [port] to listen to [hostname]. + */ + public suspend fun bind( + hostname: String = "0.0.0.0", + port: Int = 0, + configure: SocketOptions.UDPSocketOptions.() -> Unit = {} + ): BoundDatagramSocket = bind(InetSocketAddress(hostname, port), configure) /** * Create a datagram socket to listen datagrams at [localAddress] and set to [remoteAddress]. @@ -28,18 +37,5 @@ public class UDPSocketBuilder internal constructor( remoteAddress: SocketAddress, localAddress: SocketAddress? = null, configure: SocketOptions.UDPSocketOptions.() -> Unit = {} - ): ConnectedDatagramSocket = connectUDP(selector, remoteAddress, localAddress, options.udp().apply(configure)) + ): ConnectedDatagramSocket = udpConnect(selector, remoteAddress, localAddress, options.udp().apply(configure)) } - -internal expect fun connectUDP( - selector: SelectorManager, - remoteAddress: SocketAddress, - localAddress: SocketAddress?, - options: SocketOptions.UDPSocketOptions -): ConnectedDatagramSocket - -internal expect fun bindUDP( - selector: SelectorManager, - localAddress: SocketAddress?, - options: SocketOptions.UDPSocketOptions -): BoundDatagramSocket diff --git a/ktor-network/jvmAndPosix/test/io/ktor/network/sockets/tests/TCPSocketTest.kt b/ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt similarity index 72% rename from ktor-network/jvmAndPosix/test/io/ktor/network/sockets/tests/TCPSocketTest.kt rename to ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt index bece892c452..3687478c547 100644 --- a/ktor-network/jvmAndPosix/test/io/ktor/network/sockets/tests/TCPSocketTest.kt +++ b/ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.network.sockets.tests @@ -7,7 +7,6 @@ package io.ktor.network.sockets.tests import io.ktor.network.sockets.* import io.ktor.utils.io.* import io.ktor.utils.io.CancellationException -import io.ktor.utils.io.core.* import kotlinx.coroutines.* import kotlinx.io.* import kotlin.test.* @@ -63,48 +62,49 @@ class TCPSocketTest { if (!supportsUnixDomainSockets()) return@testSockets val socketPath = createTempFilePath("ktor-echo-test") + try { + val tcp = aSocket(selector).tcp() + val server = tcp.bind(UnixSocketAddress(socketPath)) - val tcp = aSocket(selector).tcp() - val server = tcp.bind(UnixSocketAddress(socketPath)) + val serverConnectionPromise = async { + server.accept() + } - val serverConnectionPromise = async { - server.accept() - } + val clientConnection = tcp.connect(UnixSocketAddress(socketPath)) + val serverConnection = serverConnectionPromise.await() - val clientConnection = tcp.connect(UnixSocketAddress(socketPath)) - val serverConnection = serverConnectionPromise.await() + val clientOutput = clientConnection.openWriteChannel() + try { + clientOutput.writeStringUtf8("Hello, world\n") + clientOutput.flush() + } finally { + clientOutput.flushAndClose() + } - val clientOutput = clientConnection.openWriteChannel() - try { - clientOutput.writeStringUtf8("Hello, world\n") - clientOutput.flush() - } finally { - clientOutput.flushAndClose() - } + val serverInput = serverConnection.openReadChannel() + val message = serverInput.readUTF8Line() + assertEquals("Hello, world", message) - val serverInput = serverConnection.openReadChannel() - val message = serverInput.readUTF8Line() - assertEquals("Hello, world", message) + val serverOutput = serverConnection.openWriteChannel() + try { + serverOutput.writeStringUtf8("Hello From Server\n") + serverOutput.flush() - val serverOutput = serverConnection.openWriteChannel() - try { - serverOutput.writeStringUtf8("Hello From Server\n") - serverOutput.flush() + val clientInput = clientConnection.openReadChannel() + val echo = clientInput.readUTF8Line() - val clientInput = clientConnection.openReadChannel() - val echo = clientInput.readUTF8Line() + assertEquals("Hello From Server", echo) + } finally { + serverOutput.flushAndClose() + } - assertEquals("Hello From Server", echo) + serverConnection.close() + clientConnection.close() + + server.close() } finally { - serverOutput.flushAndClose() + removeFile(socketPath) } - - serverConnection.close() - clientConnection.close() - - server.close() - - removeFile(socketPath) } @Test diff --git a/ktor-network/common/test/io/ktor/network/sockets/tests/TestUtils.kt b/ktor-network/common/test/io/ktor/network/sockets/tests/TestUtils.kt new file mode 100644 index 00000000000..d80261d204c --- /dev/null +++ b/ktor-network/common/test/io/ktor/network/sockets/tests/TestUtils.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.sockets.tests + +import io.ktor.network.selector.* +import io.ktor.test.dispatcher.* +import io.ktor.utils.io.core.* +import kotlinx.coroutines.* +import kotlinx.coroutines.test.* +import kotlinx.io.files.* +import kotlin.time.* +import kotlin.time.Duration.Companion.minutes +import kotlin.uuid.* + +internal fun testSockets( + timeout: Duration = 1.minutes, + block: suspend CoroutineScope.(SelectorManager) -> Unit +): TestResult = runTestWithRealTime(timeout = timeout) { + SelectorManager().use { selector -> + block(selector) + } +} + +internal expect fun Any.supportsUnixDomainSockets(): Boolean + +@OptIn(ExperimentalUuidApi::class) +internal fun createTempFilePath(basename: String): String { + return Path(SystemTemporaryDirectory, "$basename-${Uuid.random()}").toString() +} + +internal fun removeFile(path: String) { + SystemFileSystem.delete(Path(path), mustExist = false) +} diff --git a/ktor-network/gradle.properties b/ktor-network/gradle.properties new file mode 100644 index 00000000000..d12c10bfad8 --- /dev/null +++ b/ktor-network/gradle.properties @@ -0,0 +1,5 @@ +# +# Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. +# +target.js.browser=false +target.wasmJs.browser=false diff --git a/ktor-network/ios/test/io/ktor/network/sockets/tests/TestUtilsIos.kt b/ktor-network/ios/test/io/ktor/network/sockets/tests/TestUtilsIos.kt deleted file mode 100644 index 190092748d8..00000000000 --- a/ktor-network/ios/test/io/ktor/network/sockets/tests/TestUtilsIos.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.network.sockets.tests - -internal actual fun Any.supportsUnixDomainSockets(): Boolean = false diff --git a/ktor-network/js/src/io/ktor/network/sockets/nodejs/node.net.js.kt b/ktor-network/js/src/io/ktor/network/sockets/nodejs/node.net.js.kt new file mode 100644 index 00000000000..7a89dc616b7 --- /dev/null +++ b/ktor-network/js/src/io/ktor/network/sockets/nodejs/node.net.js.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.sockets.nodejs + +import io.ktor.network.sockets.* +import org.khronos.webgl.* + +internal actual fun nodeNet(): NodeNet? = js("eval('require')('node:net')").unsafeCast() + +internal actual fun TcpCreateConnectionOptions( + block: TcpCreateConnectionOptions.() -> Unit +): TcpCreateConnectionOptions = createObject(block) + +internal actual fun IpcCreateConnectionOptions( + block: IpcCreateConnectionOptions.() -> Unit +): IpcCreateConnectionOptions = createObject(block) + +internal actual fun CreateServerOptions( + block: CreateServerOptions.() -> Unit +): CreateServerOptions = createObject(block) + +internal actual fun ServerListenOptions( + block: ServerListenOptions.() -> Unit +): ServerListenOptions = createObject(block) + +private fun createObject(block: T.() -> Unit): T = js("{}").unsafeCast().apply(block) + +internal actual fun JsError.toThrowable(): Throwable = unsafeCast() +internal actual fun Throwable.toJsError(): JsError? = unsafeCast() + +internal actual fun ByteArray.toJsBuffer(fromIndex: Int, toIndex: Int): JsBuffer { + return unsafeCast().subarray(fromIndex, toIndex).unsafeCast() +} + +internal actual fun JsBuffer.toByteArray(): ByteArray { + return Int8Array(unsafeCast()).unsafeCast() +} + +internal actual fun ServerLocalAddressInfo.toSocketAddress(): SocketAddress { + if (jsTypeOf(this) == "string") return UnixSocketAddress(unsafeCast()) + val info = unsafeCast() + return InetSocketAddress(info.address, info.port) +} diff --git a/ktor-network/androidNative/test/io/ktor/network/sockets/tests/TestUtils.androidNative.kt b/ktor-network/jsAndWasmShared/src/io/ktor/network/selector/Selectable.jsAndWasmShared.kt similarity index 54% rename from ktor-network/androidNative/test/io/ktor/network/sockets/tests/TestUtils.androidNative.kt rename to ktor-network/jsAndWasmShared/src/io/ktor/network/selector/Selectable.jsAndWasmShared.kt index 5006b7bb955..d462ef91350 100644 --- a/ktor-network/androidNative/test/io/ktor/network/sockets/tests/TestUtils.androidNative.kt +++ b/ktor-network/jsAndWasmShared/src/io/ktor/network/selector/Selectable.jsAndWasmShared.kt @@ -2,6 +2,6 @@ * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -package io.ktor.network.sockets.tests +package io.ktor.network.selector -internal actual fun Any.supportsUnixDomainSockets(): Boolean = false +public actual interface Selectable diff --git a/ktor-network/jsAndWasmShared/src/io/ktor/network/selector/SelectorManager.jsAndWasmShared.kt b/ktor-network/jsAndWasmShared/src/io/ktor/network/selector/SelectorManager.jsAndWasmShared.kt new file mode 100644 index 00000000000..4331d052a50 --- /dev/null +++ b/ktor-network/jsAndWasmShared/src/io/ktor/network/selector/SelectorManager.jsAndWasmShared.kt @@ -0,0 +1,63 @@ +/* +* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. +*/ + +package io.ktor.network.selector + +import io.ktor.utils.io.core.* +import kotlinx.coroutines.* +import kotlin.coroutines.* + +public actual fun SelectorManager(dispatcher: CoroutineContext): SelectorManager = NoopSelectorManager + +public actual interface SelectorManager : CoroutineScope, Closeable { + /** + * Notifies the selector that selectable has been closed. + */ + public actual fun notifyClosed(selectable: Selectable) + + /** + * Suspends until [interest] is selected for [selectable] + * May cause manager to allocate and run selector instance if not yet created. + * + * Only one selection is allowed per [interest] per [selectable] but you can + * select for different interests for the same selectable simultaneously. + * In other words you can select for read and write at the same time but should never + * try to read twice for the same selectable. + */ + public actual suspend fun select( + selectable: Selectable, + interest: SelectInterest + ) + + public actual companion object +} + +/** + * Select interest kind + */ +public actual enum class SelectInterest { + READ, WRITE, ACCEPT, CONNECT; + + public actual companion object { + public actual val AllInterests: Array + get() = entries.toTypedArray() + } +} + +// TODO: how coroutine context should be used? +private object NoopSelectorManager : SelectorManager { + override val coroutineContext: CoroutineContext get() = EmptyCoroutineContext + + override fun notifyClosed(selectable: Selectable) { + error("not supported") + } + + override suspend fun select(selectable: Selectable, interest: SelectInterest) { + error("not supported") + } + + override fun close() { + // no-op so it can be called in common code + } +} diff --git a/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/ServerSocketContext.kt b/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/ServerSocketContext.kt new file mode 100644 index 00000000000..f14801e28e8 --- /dev/null +++ b/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/ServerSocketContext.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.sockets + +import io.ktor.network.sockets.nodejs.* +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* +import kotlinx.io.* +import kotlin.coroutines.* + +internal class ServerSocketContext( + private val server: Server, + private val localAddress: SocketAddress?, + parentContext: Job? +) { + private val incomingSockets = Channel(Channel.UNLIMITED) + private val serverContext = SupervisorJob(parentContext) + + fun initiate(cont: CancellableContinuation) { + cont.invokeOnCancellation { + server.close() + + serverContext.cancel() + incomingSockets.cancel() + } + + server.onConnection { socket -> + val context = SocketContext(socket, localAddress, serverContext) + context.initiate(null) + incomingSockets.trySend(context.createSocket()) + } + server.onClose { + if (cont.isActive) { + cont.resumeWithException(IOException("Failed to bind")) + } else { + serverContext.job.cancel("Server closed") + } + } + server.onError { error -> + if (cont.isActive) { + cont.resumeWithException(IOException("Failed to bind", error.toThrowable())) + } else { + serverContext.job.cancel("Server failed", error.toThrowable()) + } + } + server.onListening { + cont.resume(ServerSocketImpl(server.address()!!.toSocketAddress(), serverContext, incomingSockets, server)) + } + server.listen(ServerListenOptions(localAddress)) + } +} + +private class ServerSocketImpl( + override val localAddress: SocketAddress, + override val socketContext: Job, + private val incoming: ReceiveChannel, + private val server: Server +) : ServerSocket { + override suspend fun accept(): Socket = incoming.receive() + + init { + socketContext.invokeOnCompletion { + server.close() + incoming.cancel() + } + } + + override fun close() { + socketContext.cancel("Server socket closed") + } +} diff --git a/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/SocketContext.kt b/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/SocketContext.kt new file mode 100644 index 00000000000..909d86b0ed2 --- /dev/null +++ b/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/SocketContext.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.sockets + +import io.ktor.network.sockets.nodejs.* +import io.ktor.utils.io.* +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* +import kotlinx.io.* +import kotlin.coroutines.* +import io.ktor.network.sockets.nodejs.Socket as NodejsSocket + +internal class SocketContext( + private val socket: NodejsSocket, + private val address: SocketAddress?, + parentContext: Job? +) { + private val incomingFrames: Channel = Channel(Channel.UNLIMITED) + private val socketContext = Job(parentContext) + + fun initiate(connectCont: CancellableContinuation?) { + connectCont?.invokeOnCancellation { + socket.destroy(it?.toJsError()) + + socketContext.cancel() + incomingFrames.cancel() + } + socket.onError { error -> + when (connectCont?.isActive) { + true -> connectCont.resumeWithException(IOException("Failed to connect", error.toThrowable())) + else -> socketContext.job.cancel("Socket error", error.toThrowable()) + } + } + socket.onTimeout { + when (connectCont?.isActive) { + true -> connectCont.resumeWithException(SocketTimeoutException("timeout")) + else -> socketContext.job.cancel("Socket timeout", SocketTimeoutException("timeout")) + } + } + socket.onEnd { + incomingFrames.close() + } + socket.onClose { + socketContext.job.cancel("Socket closed") + } + socket.onData { data -> + incomingFrames.trySend(data) + } + + if (connectCont != null) { + socket.onConnect { + connectCont.resume(createSocket()) + } + } + } + + // Socket real address could be resolved only after the ` connect ` event. + // Also, Node.js doesn't give access to unix address from the ` socket ` object, + // so we need to store it. + fun createSocket(): Socket = SocketImpl( + localAddress = when (address) { + is UnixSocketAddress -> address + else -> InetSocketAddress(socket.localAddress, socket.localPort) + }, + remoteAddress = when (address) { + is UnixSocketAddress -> address + else -> InetSocketAddress(socket.remoteAddress, socket.remotePort) + }, + coroutineContext = socketContext, + incoming = incomingFrames, + socket = socket + ) +} + +private class SocketImpl( + override val localAddress: SocketAddress, + override val remoteAddress: SocketAddress, + override val coroutineContext: CoroutineContext, + private val incoming: ReceiveChannel, + private val socket: NodejsSocket +) : Socket { + override val socketContext: Job get() = coroutineContext.job + + init { + socketContext.invokeOnCompletion { + socket.destroy(it?.toJsError()) + incoming.cancel(CancellationException("Socket closed", it)) + } + } + + override fun attachForReading(channel: ByteChannel): WriterJob = writer(Dispatchers.Unconfined, channel = channel) { + incoming.consumeEach { buffer -> + channel.writeByteArray(buffer.toByteArray()) + channel.flush() + } + } + + override fun attachForWriting(channel: ByteChannel): ReaderJob = reader(Dispatchers.Unconfined, channel = channel) { + while (true) { + val result = channel.read { bytes, startIndex, endIndex -> + socket.write(bytes.toJsBuffer(startIndex, endIndex)) + endIndex - startIndex + } + if (result == -1) { + socket.end() + break + } + } + } + + override fun close() { + socketContext.cancel("Socket closed") + } +} diff --git a/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/SocketEngine.jsAndWasmShared.kt b/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/SocketEngine.jsAndWasmShared.kt new file mode 100644 index 00000000000..9a404891f34 --- /dev/null +++ b/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/SocketEngine.jsAndWasmShared.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.sockets + +import io.ktor.network.selector.* +import io.ktor.network.sockets.nodejs.* +import kotlinx.coroutines.* + +internal actual suspend fun tcpConnect( + selector: SelectorManager, + remoteAddress: SocketAddress, + socketOptions: SocketOptions.TCPClientSocketOptions +): Socket = suspendCancellableCoroutine { cont -> + val socket = nodeNet.createConnection(CreateConnectionOptions(remoteAddress, socketOptions)) + SocketContext(socket, remoteAddress, null).initiate(cont) +} + +internal actual suspend fun tcpBind( + selector: SelectorManager, + localAddress: SocketAddress?, + socketOptions: SocketOptions.AcceptorOptions +): ServerSocket = suspendCancellableCoroutine { cont -> + val server = nodeNet.createServer(CreateServerOptions {}) + ServerSocketContext(server, localAddress, null).initiate(cont) +} + +internal actual suspend fun udpConnect( + selector: SelectorManager, + remoteAddress: SocketAddress, + localAddress: SocketAddress?, + options: SocketOptions.UDPSocketOptions +): ConnectedDatagramSocket = error("UDP sockets are unsupported on WASM/JS") + +internal actual suspend fun udpBind( + selector: SelectorManager, + localAddress: SocketAddress?, + options: SocketOptions.UDPSocketOptions +): BoundDatagramSocket = error("UDP sockets are unsupported on WASM/JS") diff --git a/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/nodejs/node.net.kt b/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/nodejs/node.net.kt new file mode 100644 index 00000000000..ed3541b0cde --- /dev/null +++ b/ktor-network/jsAndWasmShared/src/io/ktor/network/sockets/nodejs/node.net.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.sockets.nodejs + +import io.ktor.network.sockets.* + +// js.Error +internal external interface JsError { + val message: String? +} + +internal expect fun JsError.toThrowable(): Throwable +internal expect fun Throwable.toJsError(): JsError? + +internal external interface JsBuffer // Int8Array + +internal expect fun ByteArray.toJsBuffer(fromIndex: Int, toIndex: Int): JsBuffer +internal expect fun JsBuffer.toByteArray(): ByteArray + +internal external interface NodeNet { + fun createConnection(options: CreateConnectionOptions): Socket + fun createServer(options: CreateServerOptions): Server +} + +internal expect fun nodeNet(): NodeNet? + +internal val nodeNet by lazy { + requireNotNull(runCatching { nodeNet() }.getOrNull()) { + "Node.js net module is not available. Please verify that you are using Node.js" + } +} + +internal fun CreateConnectionOptions( + remoteAddress: SocketAddress, + socketOptions: SocketOptions.TCPClientSocketOptions +): CreateConnectionOptions = when (remoteAddress) { + is InetSocketAddress -> TcpCreateConnectionOptions { + host = remoteAddress.hostname + port = remoteAddress.port + noDelay = socketOptions.noDelay + timeout = when (socketOptions.socketTimeout) { + Long.MAX_VALUE -> Int.MAX_VALUE + else -> socketOptions.socketTimeout.toInt() + } + keepAlive = socketOptions.keepAlive + } + + is UnixSocketAddress -> IpcCreateConnectionOptions { + path = remoteAddress.path + timeout = when (socketOptions.socketTimeout) { + Long.MAX_VALUE -> Int.MAX_VALUE + else -> socketOptions.socketTimeout.toInt() + } + } +} + +internal external interface CreateConnectionOptions { + var timeout: Int? + var allowHalfOpen: Boolean? +} + +internal expect fun TcpCreateConnectionOptions( + block: TcpCreateConnectionOptions.() -> Unit +): TcpCreateConnectionOptions + +internal external interface TcpCreateConnectionOptions : CreateConnectionOptions { + var port: Int + var host: String? + + var localAddress: String? + var localPort: Int? + var family: Int? // ip stack + var noDelay: Boolean? + var keepAlive: Boolean? +} + +internal expect fun IpcCreateConnectionOptions( + block: IpcCreateConnectionOptions.() -> Unit +): IpcCreateConnectionOptions + +internal external interface IpcCreateConnectionOptions : CreateConnectionOptions { + var path: String +} + +internal external interface Socket { + val localAddress: String + val localPort: Int + + val remoteAddress: String + val remotePort: Int + + fun write(buffer: JsBuffer): Boolean + + fun destroy(error: JsError?) + + // sends FIN + fun end() + + fun on(event: String /* "close" */, listener: (hadError: Boolean) -> Unit) + fun on(event: String /* "connect", "end", "timeout", */, listener: () -> Unit) + fun on(event: String /* "data" */, listener: (data: JsBuffer) -> Unit) + fun on(event: String /* "error" */, listener: (error: JsError) -> Unit) +} + +internal fun Socket.onClose(block: (hadError: Boolean) -> Unit): Unit = on("close", block) +internal fun Socket.onConnect(block: () -> Unit): Unit = on("connect", block) +internal fun Socket.onEnd(block: () -> Unit): Unit = on("end", block) +internal fun Socket.onTimeout(block: () -> Unit): Unit = on("timeout", block) +internal fun Socket.onData(block: (data: JsBuffer) -> Unit): Unit = on("data", block) +internal fun Socket.onError(block: (error: JsError) -> Unit): Unit = on("error", block) + +internal expect fun CreateServerOptions( + block: CreateServerOptions.() -> Unit +): CreateServerOptions + +internal external interface CreateServerOptions { + var allowHalfOpen: Boolean? + var keepAlive: Boolean? + var noDelay: Boolean? +} + +internal external interface Server { + fun address(): ServerLocalAddressInfo? + fun listen(options: ServerListenOptions) + + // stop accepting new connections + fun close() + + fun on(event: String /* "close", "listening" */, listener: () -> Unit) + fun on(event: String /* "connection" */, listener: (socket: Socket) -> Unit) + fun on(event: String /* "error" */, listener: (error: JsError) -> Unit) +} + +internal fun Server.onClose(block: () -> Unit): Unit = on("close", block) +internal fun Server.onListening(block: () -> Unit): Unit = on("listening", block) +internal fun Server.onConnection(block: (socket: Socket) -> Unit): Unit = on("connection", block) +internal fun Server.onError(block: (error: JsError) -> Unit): Unit = on("error", block) + +internal fun ServerListenOptions(localAddress: SocketAddress?): ServerListenOptions = ServerListenOptions { + when (localAddress) { + is InetSocketAddress -> { + port = localAddress.port + host = localAddress.hostname + } + + is UnixSocketAddress -> { + path = localAddress.path + } + + null -> { + host = "0.0.0.0" + port = 0 + } + } +} + +internal expect fun ServerListenOptions( + block: ServerListenOptions.() -> Unit +): ServerListenOptions + +internal external interface ServerListenOptions { + var port: Int? + var host: String? + var path: String? +} + +internal external interface ServerLocalAddressInfo + +internal external interface TcpServerLocalAddressInfo : ServerLocalAddressInfo { + val address: String + val family: String + val port: Int +} + +internal expect fun ServerLocalAddressInfo.toSocketAddress(): SocketAddress diff --git a/ktor-network/linux/test/io/ktor/network/sockets/tests/TestUtilsLinux.kt b/ktor-network/jsAndWasmShared/test/io/ktor/network/sockets/tests/TestUtils.kt similarity index 72% rename from ktor-network/linux/test/io/ktor/network/sockets/tests/TestUtilsLinux.kt rename to ktor-network/jsAndWasmShared/test/io/ktor/network/sockets/tests/TestUtils.kt index a283858bbb3..51820258638 100644 --- a/ktor-network/linux/test/io/ktor/network/sockets/tests/TestUtilsLinux.kt +++ b/ktor-network/jsAndWasmShared/test/io/ktor/network/sockets/tests/TestUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.network.sockets.tests diff --git a/ktor-network/jvm/src/io/ktor/network/selector/SelectorManager.kt b/ktor-network/jvm/src/io/ktor/network/selector/SelectorManager.kt index d47045bf021..6041df1ce3b 100644 --- a/ktor-network/jvm/src/io/ktor/network/selector/SelectorManager.kt +++ b/ktor-network/jvm/src/io/ktor/network/selector/SelectorManager.kt @@ -64,7 +64,6 @@ public inline fun SelectorManager.buildOrClose( * Select interest kind * @property [flag] to be set in NIO selector */ - public actual enum class SelectInterest(public val flag: Int) { READ(SelectionKey.OP_READ), WRITE(SelectionKey.OP_WRITE), diff --git a/ktor-network/jvm/src/io/ktor/network/sockets/ConnectUtilsJvm.kt b/ktor-network/jvm/src/io/ktor/network/sockets/ConnectUtilsJvm.kt index 95827ae803f..7ac5c105c77 100644 --- a/ktor-network/jvm/src/io/ktor/network/sockets/ConnectUtilsJvm.kt +++ b/ktor-network/jvm/src/io/ktor/network/sockets/ConnectUtilsJvm.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.sockets @@ -9,7 +9,7 @@ import java.net.* import java.nio.channels.* import java.nio.channels.spi.* -internal actual suspend fun connect( +internal actual suspend fun tcpConnect( selector: SelectorManager, remoteAddress: SocketAddress, socketOptions: SocketOptions.TCPClientSocketOptions @@ -22,7 +22,7 @@ internal actual suspend fun connect( } } -internal actual fun bind( +internal actual suspend fun tcpBind( selector: SelectorManager, localAddress: SocketAddress?, socketOptions: SocketOptions.AcceptorOptions diff --git a/ktor-network/jvm/src/io/ktor/network/sockets/UDPSocketBuilderJvm.kt b/ktor-network/jvm/src/io/ktor/network/sockets/UDPSocketBuilderJvm.kt index deb8bd96365..0b465b7cb58 100644 --- a/ktor-network/jvm/src/io/ktor/network/sockets/UDPSocketBuilderJvm.kt +++ b/ktor-network/jvm/src/io/ktor/network/sockets/UDPSocketBuilderJvm.kt @@ -6,7 +6,7 @@ package io.ktor.network.sockets import io.ktor.network.selector.* -internal actual fun connectUDP( +internal actual suspend fun udpConnect( selector: SelectorManager, remoteAddress: SocketAddress, localAddress: SocketAddress?, @@ -25,7 +25,7 @@ internal actual fun connectUDP( return DatagramSocketImpl(this, selector) } -internal actual fun bindUDP( +internal actual suspend fun udpBind( selector: SelectorManager, localAddress: SocketAddress?, options: SocketOptions.UDPSocketOptions diff --git a/ktor-network/jvm/test/io/ktor/network/sockets/tests/TestUtilsJvm.kt b/ktor-network/jvm/test/io/ktor/network/sockets/tests/TestUtilsJvm.kt index 55f12746864..8d4a8a5e72e 100644 --- a/ktor-network/jvm/test/io/ktor/network/sockets/tests/TestUtilsJvm.kt +++ b/ktor-network/jvm/test/io/ktor/network/sockets/tests/TestUtilsJvm.kt @@ -1,12 +1,9 @@ /* - * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.network.sockets.tests -import java.nio.file.* -import kotlin.io.path.* - private const val UNIX_DOMAIN_SOCKET_ADDRESS_CLASS = "java.net.UnixDomainSocketAddress" internal actual fun Any.supportsUnixDomainSockets(): Boolean { @@ -17,13 +14,3 @@ internal actual fun Any.supportsUnixDomainSockets(): Boolean { false } } - -internal actual fun createTempFilePath(basename: String): String { - val tempFile = Files.createTempFile(basename, "") - tempFile.deleteIfExists() - return tempFile.toString() -} - -internal actual fun removeFile(path: String) { - Files.deleteIfExists(Path(path)) -} diff --git a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/ConnectUtils.kt b/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/ConnectUtils.kt deleted file mode 100644 index 15db7cce981..00000000000 --- a/ktor-network/jvmAndPosix/src/io/ktor/network/sockets/ConnectUtils.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ - -package io.ktor.network.sockets - -import io.ktor.network.selector.* - -internal expect suspend fun connect( - selector: SelectorManager, - remoteAddress: SocketAddress, - socketOptions: SocketOptions.TCPClientSocketOptions -): Socket - -internal expect fun bind( - selector: SelectorManager, - localAddress: SocketAddress?, - socketOptions: SocketOptions.AcceptorOptions -): ServerSocket diff --git a/ktor-network/jvmAndPosix/test/io/ktor/network/sockets/tests/TestUtils.kt b/ktor-network/jvmAndPosix/test/io/ktor/network/sockets/tests/TestUtils.kt deleted file mode 100644 index 3e7f36121bb..00000000000 --- a/ktor-network/jvmAndPosix/test/io/ktor/network/sockets/tests/TestUtils.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.network.sockets.tests - -import io.ktor.network.selector.* -import io.ktor.test.dispatcher.* -import io.ktor.util.* -import io.ktor.utils.io.core.* -import kotlinx.coroutines.* -import kotlin.time.* -import kotlin.time.Duration.Companion.seconds - -internal fun testSockets(timeout: Duration = 1.seconds, block: suspend CoroutineScope.(SelectorManager) -> Unit) { - if (!PlatformUtils.IS_JVM && !PlatformUtils.IS_NATIVE) return - testSuspend { - withTimeout(timeout) { - SelectorManager().use { selector -> - block(selector) - } - } - } -} - -internal expect fun Any.supportsUnixDomainSockets(): Boolean - -internal expect fun createTempFilePath(basename: String): String -internal expect fun removeFile(path: String) diff --git a/ktor-network/jvmAndPosix/test/io/ktor/network/sockets/tests/UDPSocketTest.kt b/ktor-network/jvmAndPosix/test/io/ktor/network/sockets/tests/UDPSocketTest.kt index 5c9d784b5d7..9ed72e30ba6 100644 --- a/ktor-network/jvmAndPosix/test/io/ktor/network/sockets/tests/UDPSocketTest.kt +++ b/ktor-network/jvmAndPosix/test/io/ktor/network/sockets/tests/UDPSocketTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.network.sockets.tests @@ -11,13 +11,14 @@ import kotlinx.coroutines.* import kotlinx.io.* import kotlin.random.* import kotlin.test.* +import kotlin.use class UDPSocketTest { private val done = atomic(0) @Test - fun testBroadcastFails(): Unit = testSockets { selector -> + fun testBroadcastFails() = testSockets { selector -> if (isJvmWindows()) { return@testSockets } @@ -102,7 +103,7 @@ class UDPSocketTest { } @Test - fun testClose(): Unit = testSockets { selector -> + fun testClose() = testSockets { selector -> val socket = aSocket(selector) .udp() .bind() @@ -193,10 +194,10 @@ class UDPSocketTest { } @Test - fun testSendReceive(): Unit = testSockets { selector -> + fun testSendReceive() = testSockets { selector -> aSocket(selector) .udp() - .bind(InetSocketAddress("127.0.0.1", 8000)) { + .bind("127.0.0.1", 8000) { reuseAddress = true } .use { socket -> @@ -223,7 +224,7 @@ class UDPSocketTest { } @Test - fun testSendReceiveLarge(): Unit = testSockets { selector -> + fun testSendReceiveLarge() = testSockets { selector -> val datagramSize = 10000 // must be larger than Segment.SIZE (8192) for this test val largeData = Random.nextBytes(datagramSize) diff --git a/ktor-network/ktor-network-tls/api/ktor-network-tls.klib.api b/ktor-network/ktor-network-tls/api/ktor-network-tls.klib.api index 980bd2d90a5..4b5d8c1eb10 100644 --- a/ktor-network/ktor-network-tls/api/ktor-network-tls.klib.api +++ b/ktor-network/ktor-network-tls/api/ktor-network-tls.klib.api @@ -1,5 +1,5 @@ // Klib ABI Dump -// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] // Rendering settings: // - Signature version: 2 // - Show manifest properties: true diff --git a/ktor-network/ktor-network-tls/build.gradle.kts b/ktor-network/ktor-network-tls/build.gradle.kts index e4c2e74679e..92422408839 100644 --- a/ktor-network/ktor-network-tls/build.gradle.kts +++ b/ktor-network/ktor-network-tls/build.gradle.kts @@ -1,5 +1,9 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + kotlin.sourceSets { - jvmAndPosixMain { + commonMain { dependencies { api(project(":ktor-http")) api(project(":ktor-network")) diff --git a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/CipherSuites.kt b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/CipherSuites.kt similarity index 96% rename from ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/CipherSuites.kt rename to ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/CipherSuites.kt index 4e95b5b59f2..9c96e828c80 100644 --- a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/CipherSuites.kt +++ b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/CipherSuites.kt @@ -1,12 +1,11 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.tls import io.ktor.network.tls.extensions.* -import io.ktor.utils.io.errors.* -import kotlinx.io.IOException +import kotlinx.io.* /** * TLS secret key exchange type. diff --git a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/OID.kt b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/OID.kt similarity index 95% rename from ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/OID.kt rename to ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/OID.kt index 7f63bd72713..78b3fc88d5c 100644 --- a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/OID.kt +++ b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/OID.kt @@ -1,11 +1,9 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.tls -import io.ktor.util.* - public data class OID(public val identifier: String) { public val asArray: IntArray = identifier.split(".", " ").map { it.trim().toInt() }.toIntArray() diff --git a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSClientSession.kt b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSClientSession.kt similarity index 71% rename from ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSClientSession.kt rename to ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSClientSession.kt index 766791b048b..aceb68b683a 100644 --- a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSClientSession.kt +++ b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSClientSession.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.tls diff --git a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSCommon.kt b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSCommon.kt similarity index 92% rename from ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSCommon.kt rename to ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSCommon.kt index 1f3e1aa18a2..358f3e0bf91 100644 --- a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSCommon.kt +++ b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSCommon.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.tls diff --git a/ktor-network/windows/test/io/ktor/network/sockets/tests/TestUtilsWindows.kt b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSConfig.kt similarity index 54% rename from ktor-network/windows/test/io/ktor/network/sockets/tests/TestUtilsWindows.kt rename to ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSConfig.kt index 5006b7bb955..10a3f75b4e1 100644 --- a/ktor-network/windows/test/io/ktor/network/sockets/tests/TestUtilsWindows.kt +++ b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSConfig.kt @@ -2,6 +2,6 @@ * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -package io.ktor.network.sockets.tests +package io.ktor.network.tls -internal actual fun Any.supportsUnixDomainSockets(): Boolean = false +public expect class TLSConfig diff --git a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt similarity index 79% rename from ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt rename to ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt index a9e1e369789..1054ce0b90f 100644 --- a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt +++ b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.tls diff --git a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/NamedCurves.kt b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/NamedCurves.kt similarity index 91% rename from ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/NamedCurves.kt rename to ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/NamedCurves.kt index ad79f272939..ad1059b53b4 100644 --- a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/NamedCurves.kt +++ b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/NamedCurves.kt @@ -1,11 +1,9 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.tls.extensions -import kotlin.native.concurrent.* - /** * Named curves for Elliptic Curves. * @property code curve numeric code diff --git a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/PointFormat.kt b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/PointFormat.kt similarity index 81% rename from ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/PointFormat.kt rename to ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/PointFormat.kt index 609b6c03e6b..9b3e4c54ffd 100644 --- a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/PointFormat.kt +++ b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/PointFormat.kt @@ -1,11 +1,9 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.tls.extensions -import kotlin.native.concurrent.* - /** * Elliptic curve point format * @property code numeric point format code diff --git a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/SignatureAlgorithm.kt b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/SignatureAlgorithm.kt similarity index 96% rename from ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/SignatureAlgorithm.kt rename to ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/SignatureAlgorithm.kt index d1a700860fe..f62b86a09c1 100644 --- a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/SignatureAlgorithm.kt +++ b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/SignatureAlgorithm.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.tls.extensions diff --git a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/TLSExtension.kt b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/TLSExtension.kt similarity index 86% rename from ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/TLSExtension.kt rename to ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/TLSExtension.kt index b009c4748ec..d5b5f4e8a6b 100644 --- a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/extensions/TLSExtension.kt +++ b/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/extensions/TLSExtension.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.tls.extensions diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSClientHandshake.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSClientHandshake.kt index a10fe5caf7b..e47b551ca9b 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSClientHandshake.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSClientHandshake.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.tls @@ -21,6 +21,7 @@ import javax.crypto.* import javax.crypto.spec.* import javax.security.auth.x500.* import kotlin.coroutines.* +import kotlin.use internal class TLSClientHandshake( rawInput: ByteReadChannel, diff --git a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSConfig.kt b/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSConfig.kt deleted file mode 100644 index ca84d859e16..00000000000 --- a/ktor-network/ktor-network-tls/jvmAndPosix/src/io/ktor/network/tls/TLSConfig.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ - -package io.ktor.network.tls - -public expect class TLSConfig diff --git a/ktor-network/ktor-network-tls/ktor-network-tls-certificates/jvm/src/io/ktor/network/tls/certificates/Certificates.kt b/ktor-network/ktor-network-tls/ktor-network-tls-certificates/jvm/src/io/ktor/network/tls/certificates/Certificates.kt index 08d306311be..5c7c295d778 100644 --- a/ktor-network/ktor-network-tls/ktor-network-tls-certificates/jvm/src/io/ktor/network/tls/certificates/Certificates.kt +++ b/ktor-network/ktor-network-tls/ktor-network-tls-certificates/jvm/src/io/ktor/network/tls/certificates/Certificates.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.network.tls.certificates @@ -23,6 +23,7 @@ import javax.security.auth.x500.* import kotlin.time.* import kotlin.time.Duration import kotlin.time.Duration.Companion.days +import kotlin.use internal val DEFAULT_PRINCIPAL = X500Principal("CN=localhost, OU=Kotlin, O=JetBrains, C=RU") private val DEFAULT_CA_PRINCIPAL = X500Principal("CN=localhostCA, OU=Kotlin, O=JetBrains, C=RU") diff --git a/ktor-network/ktor-network-tls/ktor-network-tls-certificates/jvm/src/io/ktor/network/tls/certificates/builders.kt b/ktor-network/ktor-network-tls/ktor-network-tls-certificates/jvm/src/io/ktor/network/tls/certificates/builders.kt index b8895edcb4c..ebd9ddf7694 100644 --- a/ktor-network/ktor-network-tls/ktor-network-tls-certificates/jvm/src/io/ktor/network/tls/certificates/builders.kt +++ b/ktor-network/ktor-network-tls/ktor-network-tls-certificates/jvm/src/io/ktor/network/tls/certificates/builders.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.network.tls.certificates @@ -7,7 +7,6 @@ package io.ktor.network.tls.certificates import io.ktor.network.tls.* import io.ktor.network.tls.extensions.* import io.ktor.util.* -import io.ktor.utils.io.core.* import java.io.* import java.net.* import java.security.* diff --git a/ktor-network/ktor-network-tls/posix/src/io/ktor/network/tls/CipherSuitesNative.kt b/ktor-network/ktor-network-tls/nonJvm/src/io/ktor/network/tls/CipherSuites.nonJvm.kt similarity index 100% rename from ktor-network/ktor-network-tls/posix/src/io/ktor/network/tls/CipherSuitesNative.kt rename to ktor-network/ktor-network-tls/nonJvm/src/io/ktor/network/tls/CipherSuites.nonJvm.kt diff --git a/ktor-network/ktor-network-tls/posix/src/io/ktor/network/tls/TLSNative.kt b/ktor-network/ktor-network-tls/nonJvm/src/io/ktor/network/tls/TLS.nonJvm.kt similarity index 100% rename from ktor-network/ktor-network-tls/posix/src/io/ktor/network/tls/TLSNative.kt rename to ktor-network/ktor-network-tls/nonJvm/src/io/ktor/network/tls/TLS.nonJvm.kt diff --git a/ktor-network/ktor-network-tls/posix/src/io/ktor/network/tls/TLSClientSessionNative.kt b/ktor-network/ktor-network-tls/nonJvm/src/io/ktor/network/tls/TLSClientSession.nonJvm.kt similarity index 100% rename from ktor-network/ktor-network-tls/posix/src/io/ktor/network/tls/TLSClientSessionNative.kt rename to ktor-network/ktor-network-tls/nonJvm/src/io/ktor/network/tls/TLSClientSession.nonJvm.kt diff --git a/ktor-network/ktor-network-tls/posix/src/io/ktor/network/tls/TLSConfigNative.kt b/ktor-network/ktor-network-tls/nonJvm/src/io/ktor/network/tls/TLSConfig.nonJvm.kt similarity index 100% rename from ktor-network/ktor-network-tls/posix/src/io/ktor/network/tls/TLSConfigNative.kt rename to ktor-network/ktor-network-tls/nonJvm/src/io/ktor/network/tls/TLSConfig.nonJvm.kt diff --git a/ktor-network/ktor-network-tls/posix/src/io/ktor/network/tls/TLSConfigBuilderNative.kt b/ktor-network/ktor-network-tls/nonJvm/src/io/ktor/network/tls/TLSConfigBuilder.nonJvm.kt similarity index 100% rename from ktor-network/ktor-network-tls/posix/src/io/ktor/network/tls/TLSConfigBuilderNative.kt rename to ktor-network/ktor-network-tls/nonJvm/src/io/ktor/network/tls/TLSConfigBuilder.nonJvm.kt diff --git a/ktor-network/macos/test/io/ktor/network/sockets/tests/TestUtilsMacos.kt b/ktor-network/macos/test/io/ktor/network/sockets/tests/TestUtilsMacos.kt deleted file mode 100644 index a283858bbb3..00000000000 --- a/ktor-network/macos/test/io/ktor/network/sockets/tests/TestUtilsMacos.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.network.sockets.tests - -internal actual fun Any.supportsUnixDomainSockets(): Boolean = true diff --git a/ktor-network/posix/src/io/ktor/network/sockets/SocketAddressNative.kt b/ktor-network/nonJvm/src/io/ktor/network/sockets/SocketAddress.nonJvm.kt similarity index 100% rename from ktor-network/posix/src/io/ktor/network/sockets/SocketAddressNative.kt rename to ktor-network/nonJvm/src/io/ktor/network/sockets/SocketAddress.nonJvm.kt diff --git a/ktor-network/posix/src/io/ktor/network/sockets/SocketTimeoutException.kt b/ktor-network/nonJvm/src/io/ktor/network/sockets/SocketTimeoutException.kt similarity index 51% rename from ktor-network/posix/src/io/ktor/network/sockets/SocketTimeoutException.kt rename to ktor-network/nonJvm/src/io/ktor/network/sockets/SocketTimeoutException.kt index 7bbab1cc4cc..ca54b0fb353 100644 --- a/ktor-network/posix/src/io/ktor/network/sockets/SocketTimeoutException.kt +++ b/ktor-network/nonJvm/src/io/ktor/network/sockets/SocketTimeoutException.kt @@ -1,13 +1,11 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.sockets -import io.ktor.utils.io.errors.* -import kotlinx.io.IOException +import kotlinx.io.* -@Suppress("EXPECT_WITHOUT_ACTUAL") public actual class SocketTimeoutException( message: String, cause: Throwable? diff --git a/ktor-network/posix/src/io/ktor/network/selector/SelectorManager.kt b/ktor-network/posix/src/io/ktor/network/selector/SelectorManager.kt index 55746637530..fbc2cb85f52 100644 --- a/ktor-network/posix/src/io/ktor/network/selector/SelectorManager.kt +++ b/ktor-network/posix/src/io/ktor/network/selector/SelectorManager.kt @@ -38,7 +38,6 @@ public actual interface SelectorManager : CoroutineScope, Closeable { /** * Select interest kind */ -@Suppress("KDocMissingDocumentation", "NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING") public actual enum class SelectInterest { READ, WRITE, ACCEPT, CONNECT; diff --git a/ktor-network/posix/src/io/ktor/network/sockets/ConnectUtilsNative.kt b/ktor-network/posix/src/io/ktor/network/sockets/ConnectUtilsNative.kt index ec9c0f4e3bb..c54d7cd90aa 100644 --- a/ktor-network/posix/src/io/ktor/network/sockets/ConnectUtilsNative.kt +++ b/ktor-network/posix/src/io/ktor/network/sockets/ConnectUtilsNative.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.network.sockets @@ -14,7 +14,7 @@ import platform.posix.* private const val DEFAULT_BACKLOG_SIZE = 50 @OptIn(UnsafeNumber::class, ExperimentalForeignApi::class) -internal actual suspend fun connect( +internal actual suspend fun tcpConnect( selector: SelectorManager, remoteAddress: SocketAddress, socketOptions: SocketOptions.TCPClientSocketOptions @@ -79,7 +79,7 @@ internal actual suspend fun connect( } @OptIn(UnsafeNumber::class, ExperimentalForeignApi::class) -internal actual fun bind( +internal actual suspend fun tcpBind( selector: SelectorManager, localAddress: SocketAddress?, socketOptions: SocketOptions.AcceptorOptions diff --git a/ktor-network/posix/src/io/ktor/network/sockets/UDPSocketBuilderNative.kt b/ktor-network/posix/src/io/ktor/network/sockets/UDPSocketBuilderNative.kt index eb4fd26ec59..cfb45545d9d 100644 --- a/ktor-network/posix/src/io/ktor/network/sockets/UDPSocketBuilderNative.kt +++ b/ktor-network/posix/src/io/ktor/network/sockets/UDPSocketBuilderNative.kt @@ -10,7 +10,7 @@ import kotlinx.cinterop.* import platform.posix.* @OptIn(UnsafeNumber::class, ExperimentalForeignApi::class) -internal actual fun connectUDP( +internal actual suspend fun udpConnect( selector: SelectorManager, remoteAddress: SocketAddress, localAddress: SocketAddress?, @@ -51,7 +51,7 @@ internal actual fun connectUDP( } @OptIn(UnsafeNumber::class, ExperimentalForeignApi::class) -internal actual fun bindUDP( +internal actual suspend fun udpBind( selector: SelectorManager, localAddress: SocketAddress?, options: SocketOptions.UDPSocketOptions diff --git a/ktor-network/posix/test/io/ktor/network/sockets/tests/TestUtils.posix.kt b/ktor-network/posix/test/io/ktor/network/sockets/tests/TestUtils.posix.kt new file mode 100644 index 00000000000..df03ca53d52 --- /dev/null +++ b/ktor-network/posix/test/io/ktor/network/sockets/tests/TestUtils.posix.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.sockets.tests + +import kotlin.experimental.* + +@OptIn(ExperimentalNativeApi::class) +internal actual fun Any.supportsUnixDomainSockets(): Boolean = when (Platform.osFamily) { + OsFamily.MACOSX, OsFamily.LINUX -> true + else -> false +} diff --git a/ktor-network/posix/test/io/ktor/network/sockets/tests/TestUtilsNix.kt b/ktor-network/posix/test/io/ktor/network/sockets/tests/TestUtilsNix.kt deleted file mode 100644 index d1d3d99310b..00000000000 --- a/ktor-network/posix/test/io/ktor/network/sockets/tests/TestUtilsNix.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.network.sockets.tests - -import platform.posix.* - -internal actual fun createTempFilePath(basename: String): String = "/tmp/$basename" - -internal actual fun removeFile(path: String) { - if (remove(path) != 0) error("Failed to delete socket node") -} diff --git a/ktor-network/tvos/test/io/ktor/network/sockets/tests/TestUtilsTvos.kt b/ktor-network/tvos/test/io/ktor/network/sockets/tests/TestUtilsTvos.kt deleted file mode 100644 index 5964f2a095f..00000000000 --- a/ktor-network/tvos/test/io/ktor/network/sockets/tests/TestUtilsTvos.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.network.sockets.tests - -internal actual fun Any.supportsUnixDomainSockets(): Boolean = false diff --git a/ktor-network/wasmJs/src/io/ktor/network/sockets/nodejs/node.net.wasmJs.kt b/ktor-network/wasmJs/src/io/ktor/network/sockets/nodejs/node.net.wasmJs.kt new file mode 100644 index 00000000000..0b889f54ed0 --- /dev/null +++ b/ktor-network/wasmJs/src/io/ktor/network/sockets/nodejs/node.net.wasmJs.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.sockets.nodejs + +import io.ktor.network.sockets.* +import org.khronos.webgl.* + +internal actual fun nodeNet(): NodeNet? = js("eval('require')('node:net')") + +internal actual fun TcpCreateConnectionOptions( + block: TcpCreateConnectionOptions.() -> Unit +): TcpCreateConnectionOptions = createObject(block) + +internal actual fun IpcCreateConnectionOptions( + block: IpcCreateConnectionOptions.() -> Unit +): IpcCreateConnectionOptions = createObject(block) + +internal actual fun CreateServerOptions( + block: CreateServerOptions.() -> Unit +): CreateServerOptions = createObject(block) + +internal actual fun ServerListenOptions( + block: ServerListenOptions.() -> Unit +): ServerListenOptions = createObject(block) + +internal actual fun JsError.toThrowable(): Throwable = Error(message) +internal actual fun Throwable.toJsError(): JsError? = jsError(message) + +private fun jsError(message: String?): JsError = js("(new Error(message))") + +internal actual fun ByteArray.toJsBuffer(fromIndex: Int, toIndex: Int): JsBuffer { + val array = Int8Array(toIndex - fromIndex) + repeat(array.length) { index -> + array[index] = this[fromIndex + index] + } + return justCast(array) +} + +internal actual fun JsBuffer.toByteArray(): ByteArray { + val array = Int8Array(justCast(this)) + val bytes = ByteArray(array.length) + + repeat(array.length) { index -> + bytes[index] = array[index] + } + return bytes +} + +internal actual fun ServerLocalAddressInfo.toSocketAddress(): SocketAddress { + if (jsTypeOf(justCast(this)) == "string") return UnixSocketAddress(justCast(this).toString()) + val info = justCast(this) + return InetSocketAddress(info.address, info.port) +} + +private fun jsTypeOf(obj: JsAny): String = js("(typeof obj)") + +private fun createJsObject(): JsAny = js("({})") + +private fun createObject(block: T.() -> Unit): T = createJsObject().unsafeCast().apply(block) + +// overcomes the issue that expect declarations are not extending `JsAny` +@Suppress("UNCHECKED_CAST") +private fun justCast(obj: Any): T = obj as T diff --git a/ktor-network/watchos/test/io/ktor/network/sockets/tests/TestUtilsWatchos.kt b/ktor-network/watchos/test/io/ktor/network/sockets/tests/TestUtilsWatchos.kt deleted file mode 100644 index 5964f2a095f..00000000000 --- a/ktor-network/watchos/test/io/ktor/network/sockets/tests/TestUtilsWatchos.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.network.sockets.tests - -internal actual fun Any.supportsUnixDomainSockets(): Boolean = false diff --git a/ktor-server/ktor-server-cio/build.gradle.kts b/ktor-server/ktor-server-cio/build.gradle.kts index 61445613e95..1629871d5d4 100644 --- a/ktor-server/ktor-server-cio/build.gradle.kts +++ b/ktor-server/ktor-server-cio/build.gradle.kts @@ -1,3 +1,7 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + description = "" kotlin.sourceSets { @@ -14,11 +18,11 @@ kotlin.sourceSets { api(project(":ktor-server:ktor-server-core")) api(project(":ktor-client:ktor-client-cio")) api(project(":ktor-server:ktor-server-test-suites")) + api(project(":ktor-server:ktor-server-test-base")) } } jvmTest { dependencies { - api(project(":ktor-server:ktor-server-test-base")) api(project(":ktor-server:ktor-server-core", configuration = "testOutput")) api(libs.logback.classic) } diff --git a/ktor-server/ktor-server-cio/jvmAndPosix/src/io/ktor/server/cio/backend/HttpServer.kt b/ktor-server/ktor-server-cio/jvmAndPosix/src/io/ktor/server/cio/backend/HttpServer.kt index f24df8a15fd..3c811d9a72d 100644 --- a/ktor-server/ktor-server-cio/jvmAndPosix/src/io/ktor/server/cio/backend/HttpServer.kt +++ b/ktor-server/ktor-server-cio/jvmAndPosix/src/io/ktor/server/cio/backend/HttpServer.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.server.cio.backend @@ -11,8 +11,6 @@ import io.ktor.server.engine.* import io.ktor.server.engine.internal.* import io.ktor.util.logging.* import io.ktor.utils.io.* -import io.ktor.utils.io.core.* -import io.ktor.utils.io.errors.* import kotlinx.coroutines.* import kotlinx.io.IOException import kotlin.time.Duration.Companion.seconds diff --git a/ktor-server/ktor-server-cio/jvmAndPosix/test/io/ktor/tests/server/cio/CIOEngineTest.kt b/ktor-server/ktor-server-cio/jvmAndPosix/test/io/ktor/tests/server/cio/CIOEngineTest.kt index 32ee17291db..da08b330b05 100644 --- a/ktor-server/ktor-server-cio/jvmAndPosix/test/io/ktor/tests/server/cio/CIOEngineTest.kt +++ b/ktor-server/ktor-server-cio/jvmAndPosix/test/io/ktor/tests/server/cio/CIOEngineTest.kt @@ -1,8 +1,8 @@ -// ktlint-disable filename /* - * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ +// ktlint-disable filename package io.ktor.tests.server.cio import io.ktor.client.statement.* @@ -16,7 +16,6 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.testing.suites.* import io.ktor.utils.io.* -import io.ktor.utils.io.core.* import kotlin.test.* class CIOHttpServerTest : HttpServerCommonTestSuite(CIO) { diff --git a/ktor-server/ktor-server-cio/posix/src/io/ktor/server/cio/internal/CoroutineUtilsNix.kt b/ktor-server/ktor-server-cio/posix/src/io/ktor/server/cio/internal/CoroutineUtilsNix.kt index 800fcc118fa..a41e8ddb60f 100644 --- a/ktor-server/ktor-server-cio/posix/src/io/ktor/server/cio/internal/CoroutineUtilsNix.kt +++ b/ktor-server/ktor-server-cio/posix/src/io/ktor/server/cio/internal/CoroutineUtilsNix.kt @@ -7,4 +7,4 @@ package io.ktor.server.cio.internal import kotlinx.coroutines.* internal actual val Dispatchers.IOBridge: CoroutineDispatcher - get() = Default + get() = IO diff --git a/ktor-server/ktor-server-core/api/ktor-server-core.api b/ktor-server/ktor-server-core/api/ktor-server-core.api index 59586e5d9c4..e3c682dd8e2 100644 --- a/ktor-server/ktor-server-core/api/ktor-server-core.api +++ b/ktor-server/ktor-server-core/api/ktor-server-core.api @@ -895,7 +895,9 @@ public final class io/ktor/server/http/content/StaticContentResolutionKt { public final class io/ktor/server/http/content/SuppressionAttributeKt { public static final fun getSuppressionAttribute ()Lio/ktor/util/AttributeKey; public static final fun isCompressionSuppressed (Lio/ktor/server/application/ApplicationCall;)Z + public static final fun isDecompressionSuppressed (Lio/ktor/server/application/ApplicationCall;)Z public static final fun suppressCompression (Lio/ktor/server/application/ApplicationCall;)V + public static final fun suppressDecompression (Lio/ktor/server/application/ApplicationCall;)V } public final class io/ktor/server/logging/LoggingKt { diff --git a/ktor-server/ktor-server-core/api/ktor-server-core.klib.api b/ktor-server/ktor-server-core/api/ktor-server-core.klib.api index 75e902b0407..53959feab8c 100644 --- a/ktor-server/ktor-server-core/api/ktor-server-core.klib.api +++ b/ktor-server/ktor-server-core/api/ktor-server-core.klib.api @@ -1637,6 +1637,8 @@ final val io.ktor.server.http.content/SuppressionAttribute // io.ktor.server.htt final fun (): io.ktor.util/AttributeKey // io.ktor.server.http.content/SuppressionAttribute.|(){}[0] final val io.ktor.server.http.content/isCompressionSuppressed // io.ktor.server.http.content/isCompressionSuppressed|@io.ktor.server.application.ApplicationCall{}isCompressionSuppressed[0] final fun (io.ktor.server.application/ApplicationCall).(): kotlin/Boolean // io.ktor.server.http.content/isCompressionSuppressed.|@io.ktor.server.application.ApplicationCall(){}[0] +final val io.ktor.server.http.content/isDecompressionSuppressed // io.ktor.server.http.content/isDecompressionSuppressed|@io.ktor.server.application.ApplicationCall{}isDecompressionSuppressed[0] + final fun (io.ktor.server.application/ApplicationCall).(): kotlin/Boolean // io.ktor.server.http.content/isDecompressionSuppressed.|@io.ktor.server.application.ApplicationCall(){}[0] final val io.ktor.server.logging/mdcProvider // io.ktor.server.logging/mdcProvider|@io.ktor.server.application.Application{}mdcProvider[0] final fun (io.ktor.server.application/Application).(): io.ktor.server.logging/MDCProvider // io.ktor.server.logging/mdcProvider.|@io.ktor.server.application.Application(){}[0] final val io.ktor.server.plugins/MutableOriginConnectionPointKey // io.ktor.server.plugins/MutableOriginConnectionPointKey|{}MutableOriginConnectionPointKey[0] @@ -1672,6 +1674,7 @@ final fun (io.ktor.http/HeadersBuilder).io.ktor.server.response/contentRange(kot final fun (io.ktor.http/URLBuilder.Companion).io.ktor.server.util/createFromCall(io.ktor.server.application/ApplicationCall): io.ktor.http/URLBuilder // io.ktor.server.util/createFromCall|createFromCall@io.ktor.http.URLBuilder.Companion(io.ktor.server.application.ApplicationCall){}[0] final fun (io.ktor.server.application/Application).io.ktor.server.routing/routing(kotlin/Function1): io.ktor.server.routing/RoutingRoot // io.ktor.server.routing/routing|routing@io.ktor.server.application.Application(kotlin.Function1){}[0] final fun (io.ktor.server.application/ApplicationCall).io.ktor.server.http.content/suppressCompression() // io.ktor.server.http.content/suppressCompression|suppressCompression@io.ktor.server.application.ApplicationCall(){}[0] +final fun (io.ktor.server.application/ApplicationCall).io.ktor.server.http.content/suppressDecompression() // io.ktor.server.http.content/suppressDecompression|suppressDecompression@io.ktor.server.application.ApplicationCall(){}[0] final fun (io.ktor.server.application/ApplicationCall).io.ktor.server.http/push(kotlin/Function1) // io.ktor.server.http/push|push@io.ktor.server.application.ApplicationCall(kotlin.Function1){}[0] final fun (io.ktor.server.application/ApplicationCall).io.ktor.server.http/push(kotlin/String) // io.ktor.server.http/push|push@io.ktor.server.application.ApplicationCall(kotlin.String){}[0] final fun (io.ktor.server.application/ApplicationCall).io.ktor.server.http/push(kotlin/String, io.ktor.http/Parameters) // io.ktor.server.http/push|push@io.ktor.server.application.ApplicationCall(kotlin.String;io.ktor.http.Parameters){}[0] diff --git a/ktor-server/ktor-server-core/common/src/io/ktor/server/application/ApplicationPlugin.kt b/ktor-server/ktor-server-core/common/src/io/ktor/server/application/ApplicationPlugin.kt index f38ce7ea430..6f8012d4664 100644 --- a/ktor-server/ktor-server-core/common/src/io/ktor/server/application/ApplicationPlugin.kt +++ b/ktor-server/ktor-server-core/common/src/io/ktor/server/application/ApplicationPlugin.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.server.application @@ -8,7 +8,6 @@ import io.ktor.server.routing.* import io.ktor.util.* import io.ktor.util.internal.* import io.ktor.util.pipeline.* -import io.ktor.utils.io.core.* import kotlinx.coroutines.* /** @@ -230,7 +229,7 @@ public fun , B : Any, F : Any> A.uninstall( public fun , F : Any> A.uninstallPlugin(key: AttributeKey) { val registry = attributes.getOrNull(pluginRegistryKey) ?: return val instance = registry.getOrNull(key) ?: return - if (instance is Closeable) { + if (instance is AutoCloseable) { instance.close() } registry.remove(key) diff --git a/ktor-server/ktor-server-core/common/src/io/ktor/server/application/KtorCallContexts.kt b/ktor-server/ktor-server-core/common/src/io/ktor/server/application/KtorCallContexts.kt index 606b1c85d63..75dbd7241cd 100644 --- a/ktor-server/ktor-server-core/common/src/io/ktor/server/application/KtorCallContexts.kt +++ b/ktor-server/ktor-server-core/common/src/io/ktor/server/application/KtorCallContexts.kt @@ -58,7 +58,6 @@ public class OnCallReceiveContext internal constructor( public suspend fun transformBody(transform: suspend TransformBodyContext.(body: ByteReadChannel) -> Any) { val receiveBody = context.subject as? ByteReadChannel ?: return val typeInfo = context.call.receiveType - if (typeInfo == typeInfo()) return val transformContext = TransformBodyContext(typeInfo) context.subject = transformContext.transform(receiveBody) diff --git a/ktor-server/ktor-server-core/common/src/io/ktor/server/http/content/SuppressionAttribute.kt b/ktor-server/ktor-server-core/common/src/io/ktor/server/http/content/SuppressionAttribute.kt index dc93bd309a1..53712f45b8a 100644 --- a/ktor-server/ktor-server-core/common/src/io/ktor/server/http/content/SuppressionAttribute.kt +++ b/ktor-server/ktor-server-core/common/src/io/ktor/server/http/content/SuppressionAttribute.kt @@ -15,6 +15,7 @@ import io.ktor.util.* level = DeprecationLevel.ERROR ) public val SuppressionAttribute: AttributeKey = AttributeKey("preventCompression") +internal val DecompressionSuppressionAttribute: AttributeKey = AttributeKey("preventDecompression") /** * Suppress response body compression plugin for this [ApplicationCall]. @@ -24,8 +25,23 @@ public fun ApplicationCall.suppressCompression() { attributes.put(SuppressionAttribute, true) } +/** + * Suppresses the decompression for the current application call. + */ +public fun ApplicationCall.suppressDecompression() { + attributes.put(DecompressionSuppressionAttribute, true) +} + /** * Checks if response body compression is suppressed for this [ApplicationCall]. */ @Suppress("DEPRECATION_ERROR") public val ApplicationCall.isCompressionSuppressed: Boolean get() = SuppressionAttribute in attributes + +/** + * Indicates whether decompression is suppressed for the current application call. + * If decompression is suppressed, the plugin will not decompress the request body. + * + * To suppress decompression, use [suppressDecompression]. + */ +public val ApplicationCall.isDecompressionSuppressed: Boolean get() = DecompressionSuppressionAttribute in attributes diff --git a/ktor-server/ktor-server-core/posix/src/io/ktor/server/engine/internal/ApplicationUtilsNix.kt b/ktor-server/ktor-server-core/posix/src/io/ktor/server/engine/internal/ApplicationUtilsNix.kt index ed2e6cc8e12..a54655c1f8b 100644 --- a/ktor-server/ktor-server-core/posix/src/io/ktor/server/engine/internal/ApplicationUtilsNix.kt +++ b/ktor-server/ktor-server-core/posix/src/io/ktor/server/engine/internal/ApplicationUtilsNix.kt @@ -9,10 +9,12 @@ import io.ktor.server.engine.* import kotlinx.cinterop.* import kotlinx.coroutines.* import platform.posix.* +import kotlin.experimental.* -internal actual fun availableProcessorsBridge(): Int = 1 +@OptIn(ExperimentalNativeApi::class) +internal actual fun availableProcessorsBridge(): Int = Platform.getAvailableProcessors() -internal actual val Dispatchers.IOBridge: CoroutineDispatcher get() = Default +internal actual val Dispatchers.IOBridge: CoroutineDispatcher get() = IO @OptIn(ExperimentalForeignApi::class) internal actual fun printError(message: Any?) { diff --git a/ktor-server/ktor-server-plugins/ktor-server-auth/common/test/io/ktor/tests/auth/SessionAuthTest.kt b/ktor-server/ktor-server-plugins/ktor-server-auth/common/test/io/ktor/tests/auth/SessionAuthTest.kt index 0fcdefca394..c565add44a4 100644 --- a/ktor-server/ktor-server-plugins/ktor-server-auth/common/test/io/ktor/tests/auth/SessionAuthTest.kt +++ b/ktor-server/ktor-server-plugins/ktor-server-auth/common/test/io/ktor/tests/auth/SessionAuthTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.tests.auth @@ -14,7 +14,6 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.sessions.* import io.ktor.server.testing.* -import io.ktor.utils.io.core.* import kotlinx.serialization.* import kotlin.test.* diff --git a/ktor-server/ktor-server-plugins/ktor-server-compression/jvm/src/io/ktor/server/plugins/compression/Compression.kt b/ktor-server/ktor-server-plugins/ktor-server-compression/jvm/src/io/ktor/server/plugins/compression/Compression.kt index ed1c18cd526..434d83fb7d8 100644 --- a/ktor-server/ktor-server-plugins/ktor-server-compression/jvm/src/io/ktor/server/plugins/compression/Compression.kt +++ b/ktor-server/ktor-server-plugins/ktor-server-compression/jvm/src/io/ktor/server/plugins/compression/Compression.kt @@ -5,7 +5,6 @@ package io.ktor.server.plugins.compression import io.ktor.http.* -import io.ktor.http.cio.* import io.ktor.http.content.* import io.ktor.server.application.* import io.ktor.server.http.content.* @@ -13,9 +12,15 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.util.* import io.ktor.util.logging.* -import io.ktor.util.pipeline.* import io.ktor.utils.io.* -import kotlinx.coroutines.* + +/** + * List of [ContentEncoder] names that were used to decode request body. + */ +public val ApplicationRequest.appliedDecoders: List + get() = call.attributes.getOrNull(DecompressionListAttribute) ?: emptyList() + +internal val DecompressionListAttribute: AttributeKey> = AttributeKey("DecompressionListAttribute") internal val LOGGER = KtorSimpleLogger("io.ktor.server.plugins.compression.Compression") @@ -24,27 +29,6 @@ internal val LOGGER = KtorSimpleLogger("io.ktor.server.plugins.compression.Compr */ internal const val DEFAULT_MINIMAL_COMPRESSION_SIZE: Long = 200L -private object ContentEncoding : Hook Unit> { - - class Context(private val pipelineContext: PipelineContext) { - fun transformBody(block: (OutgoingContent) -> OutgoingContent?) { - val transformedContent = block(pipelineContext.subject as OutgoingContent) - if (transformedContent != null) { - pipelineContext.subject = transformedContent - } - } - } - - override fun install( - pipeline: ApplicationCallPipeline, - handler: suspend Context.(PipelineCall) -> Unit - ) { - pipeline.sendPipeline.intercept(ApplicationSendPipeline.ContentEncoding) { - handler(Context(this), call) - } - } -} - /** * A plugin that provides the capability to compress a response and decompress request bodies. * You can use different compression algorithms, including `gzip` and `deflate`, @@ -82,15 +66,21 @@ public val Compression: RouteScopedPlugin = createRouteScoped if (!mode.response) return@on encode(call, options) } - onCall { call -> - if (!mode.request) return@onCall + + onCallReceive { call -> + if (!mode.request) return@onCallReceive decode(call, options) } + } @OptIn(InternalAPI::class) -private fun decode(call: PipelineCall, options: CompressionOptions) { +private suspend fun OnCallReceiveContext.decode(call: PipelineCall, options: CompressionOptions) { val encodingRaw = call.request.headers[HttpHeaders.ContentEncoding] + if (call.isDecompressionSuppressed) { + LOGGER.trace("Skip decompression for ${call.request.uri} because it is suppressed.") + return + } if (encodingRaw == null) { LOGGER.trace("Skip decompression for ${call.request.uri} because no content encoding provided.") return @@ -112,10 +102,12 @@ private fun decode(call: PipelineCall, options: CompressionOptions) { } else { call.request.setHeader(HttpHeaders.ContentEncoding, null) } - val originalChannel = call.request.receiveChannel() - val decoded = encoders.fold(originalChannel) { content, encoder -> encoder.encoder.decode(content) } - call.request.setReceiveChannel(decoded) + call.attributes.put(DecompressionListAttribute, encoderNames) + + transformBody { body -> + encoders.fold(body) { content, encoder -> encoder.encoder.decode(content) } + } } private fun ContentEncoding.Context.encode(call: PipelineCall, options: CompressionOptions) { @@ -180,14 +172,6 @@ private fun ContentEncoding.Context.encode(call: PipelineCall, options: Compress } } -internal val DecompressionListAttribute: AttributeKey> = AttributeKey("DecompressionListAttribute") - -/** - * List of [ContentEncoder] names that were used to decode request body. - */ -public val ApplicationRequest.appliedDecoders: List - get() = call.attributes.getOrNull(DecompressionListAttribute) ?: emptyList() - private fun PipelineResponse.isSSEResponse(): Boolean { val contentType = headers[HttpHeaders.ContentType]?.let { ContentType.parse(it) } return contentType?.withoutParameters() == ContentType.Text.EventStream diff --git a/ktor-server/ktor-server-plugins/ktor-server-compression/jvm/src/io/ktor/server/plugins/compression/ContentEncoding.kt b/ktor-server/ktor-server-plugins/ktor-server-compression/jvm/src/io/ktor/server/plugins/compression/ContentEncoding.kt new file mode 100644 index 00000000000..5a67799e188 --- /dev/null +++ b/ktor-server/ktor-server-plugins/ktor-server-compression/jvm/src/io/ktor/server/plugins/compression/ContentEncoding.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.server.plugins.compression + +import io.ktor.http.content.* +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.util.pipeline.* + +internal object ContentEncoding : Hook Unit> { + + class Context(private val pipelineContext: PipelineContext) { + fun transformBody(block: (OutgoingContent) -> OutgoingContent?) { + val transformedContent = block(pipelineContext.subject as OutgoingContent) + if (transformedContent != null) { + pipelineContext.subject = transformedContent + } + } + } + + override fun install( + pipeline: ApplicationCallPipeline, + handler: suspend Context.(PipelineCall) -> Unit + ) { + pipeline.sendPipeline.intercept(ApplicationSendPipeline.ContentEncoding) { + handler(Context(this), call) + } + } +} diff --git a/ktor-server/ktor-server-test-base/jsAndWasmShared/src/io/ktor/server/test/base/EngineTestBase.jsAndWasmShared.kt b/ktor-server/ktor-server-test-base/jsAndWasmShared/src/io/ktor/server/test/base/EngineTestBase.jsAndWasmShared.kt index fe9b632b017..7f176a56623 100644 --- a/ktor-server/ktor-server-test-base/jsAndWasmShared/src/io/ktor/server/test/base/EngineTestBase.jsAndWasmShared.kt +++ b/ktor-server/ktor-server-test-base/jsAndWasmShared/src/io/ktor/server/test/base/EngineTestBase.jsAndWasmShared.kt @@ -14,7 +14,6 @@ import io.ktor.server.engine.* import io.ktor.server.routing.* import io.ktor.server.testing.* import io.ktor.util.logging.* -import io.ktor.utils.io.core.* import kotlinx.coroutines.* import kotlin.coroutines.* import kotlin.time.Duration.Companion.seconds diff --git a/ktor-server/ktor-server-test-base/posix/src/io/ktor/server/test/base/EngineTestBaseNix.kt b/ktor-server/ktor-server-test-base/posix/src/io/ktor/server/test/base/EngineTestBaseNix.kt index b575aa151b8..a870cc2cb9e 100644 --- a/ktor-server/ktor-server-test-base/posix/src/io/ktor/server/test/base/EngineTestBaseNix.kt +++ b/ktor-server/ktor-server-test-base/posix/src/io/ktor/server/test/base/EngineTestBaseNix.kt @@ -17,7 +17,6 @@ import io.ktor.server.engine.* import io.ktor.server.routing.* import io.ktor.server.testing.* import io.ktor.util.logging.* -import io.ktor.utils.io.core.* import kotlinx.coroutines.* import kotlin.coroutines.* import kotlin.time.Duration.Companion.seconds diff --git a/ktor-server/ktor-server-test-host/posix/src/io/ktor/server/testing/internal/CoroutineUtilsNix.kt b/ktor-server/ktor-server-test-host/posix/src/io/ktor/server/testing/internal/CoroutineUtilsNix.kt index ba4902b93c4..acecc1a3da2 100644 --- a/ktor-server/ktor-server-test-host/posix/src/io/ktor/server/testing/internal/CoroutineUtilsNix.kt +++ b/ktor-server/ktor-server-test-host/posix/src/io/ktor/server/testing/internal/CoroutineUtilsNix.kt @@ -6,6 +6,6 @@ package io.ktor.server.testing.internal import kotlinx.coroutines.* -internal actual val Dispatchers.IOBridge: CoroutineDispatcher get() = Default +internal actual val Dispatchers.IOBridge: CoroutineDispatcher get() = IO internal actual fun maybeRunBlocking(block: suspend CoroutineScope.() -> T): T = runBlocking(block = block) diff --git a/ktor-server/ktor-server-test-suites/common/src/io/ktor/server/testing/suites/HttpServerCommonTestSuite.kt b/ktor-server/ktor-server-test-suites/common/src/io/ktor/server/testing/suites/HttpServerCommonTestSuite.kt index 5b2a6c7a924..cf2ad35dacc 100644 --- a/ktor-server/ktor-server-test-suites/common/src/io/ktor/server/testing/suites/HttpServerCommonTestSuite.kt +++ b/ktor-server/ktor-server-test-suites/common/src/io/ktor/server/testing/suites/HttpServerCommonTestSuite.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.server.testing.suites @@ -31,6 +31,7 @@ import kotlinx.coroutines.* import kotlinx.io.* import kotlin.coroutines.* import kotlin.test.* +import kotlin.use abstract class HttpServerCommonTestSuite( hostFactory: ApplicationEngineFactory diff --git a/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/ContentTestSuite.kt b/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/ContentTestSuite.kt index 7be8a940ab6..960ea9817a2 100644 --- a/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/ContentTestSuite.kt +++ b/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/ContentTestSuite.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.server.testing.suites @@ -18,7 +18,6 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.test.base.* import io.ktor.utils.io.* -import io.ktor.utils.io.core.* import kotlinx.coroutines.* import org.junit.jupiter.api.extension.* import java.io.* diff --git a/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/HttpServerJvmTestSuite.kt b/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/HttpServerJvmTestSuite.kt index d7e1469c61d..5c78a07a6d8 100644 --- a/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/HttpServerJvmTestSuite.kt +++ b/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/HttpServerJvmTestSuite.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.server.testing.suites @@ -25,6 +25,7 @@ import kotlin.coroutines.* import kotlin.test.* import kotlin.text.toByteArray import kotlin.time.Duration.Companion.seconds +import kotlin.use abstract class HttpServerJvmTestSuite( hostFactory: ApplicationEngineFactory diff --git a/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/SustainabilityTestSuite.kt b/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/SustainabilityTestSuite.kt index f50ebb82ed9..a361683dca1 100644 --- a/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/SustainabilityTestSuite.kt +++ b/ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/SustainabilityTestSuite.kt @@ -1,6 +1,6 @@ /* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ package io.ktor.server.testing.suites @@ -37,6 +37,7 @@ import java.util.* import java.util.concurrent.* import java.util.concurrent.atomic.* import kotlin.test.* +import kotlin.use @ExtendWith(RetrySupport::class) abstract class SustainabilityTestSuite( diff --git a/ktor-server/ktor-server-test-suites/jvmAndPosix/src/io/ktor/server/testing/suites/WebSocketEngineSuite.kt b/ktor-server/ktor-server-test-suites/jvmAndPosix/src/io/ktor/server/testing/suites/WebSocketEngineSuite.kt index 920a0f382a7..f4e86cb77b3 100644 --- a/ktor-server/ktor-server-test-suites/jvmAndPosix/src/io/ktor/server/testing/suites/WebSocketEngineSuite.kt +++ b/ktor-server/ktor-server-test-suites/jvmAndPosix/src/io/ktor/server/testing/suites/WebSocketEngineSuite.kt @@ -26,6 +26,7 @@ import kotlin.test.* import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds +import kotlin.use @OptIn(InternalAPI::class) abstract class WebSocketEngineSuite( diff --git a/ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/CompressionTest.kt b/ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/CompressionTest.kt index 9c979b8e31f..2d0d0c07834 100644 --- a/ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/CompressionTest.kt +++ b/ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/CompressionTest.kt @@ -642,24 +642,31 @@ class CompressionTest { install(Compression) routing { post("/identity") { + val message = call.receiveText() assertNull(call.request.headers[HttpHeaders.ContentEncoding]) assertEquals(listOf("identity"), call.request.appliedDecoders) - call.respond(call.receiveText()) + + call.respond(message) } post("/gzip") { + val message = call.receiveText() + assertNull(call.request.headers[HttpHeaders.ContentEncoding]) assertEquals(listOf("gzip"), call.request.appliedDecoders) - call.respond(call.receiveText()) + + call.respond(message) } post("/deflate") { + val message = call.receiveText() assertNull(call.request.headers[HttpHeaders.ContentEncoding]) assertEquals(listOf("deflate"), call.request.appliedDecoders) - call.respond(call.receiveText()) + call.respond(message) } post("/multiple") { + val message = call.receiveText() assertNull(call.request.headers[HttpHeaders.ContentEncoding]) assertEquals(listOf("identity", "deflate", "gzip"), call.request.appliedDecoders) - call.respond(call.receiveText()) + call.respond(message) } post("/unknown") { assertEquals("unknown", call.request.headers[HttpHeaders.ContentEncoding]) @@ -754,9 +761,38 @@ class CompressionTest { } routing { post("/gzip") { + val body = call.receive() + assertNull(call.request.headers[HttpHeaders.ContentEncoding]) + assertContentEquals(compressed, body) + + call.respond(textToCompressAsBytes) + } + } + + val response = client.post("/gzip") { + setBody(compressed) + header(HttpHeaders.ContentEncoding, "gzip") + header(HttpHeaders.AcceptEncoding, "gzip") + } + assertContentEquals(textToCompressAsBytes, response.body()) + } + + @Test + fun testDisableCallEncoding() = testApplication { + val compressed = GZip.encode(ByteReadChannel(textToCompressAsBytes)).readRemaining().readByteArray() + install(Compression) + + routing { + post("/gzip") { + call.suppressCompression() + val body = call.receive() - assertContentEquals(textToCompressAsBytes, body) + + assertEquals("gzip", call.request.appliedDecoders.first()) + assertNull(call.request.headers[HttpHeaders.ContentEncoding]) + assertContentEquals(compressed, body) + call.respond(textToCompressAsBytes) } } @@ -769,6 +805,29 @@ class CompressionTest { assertContentEquals(textToCompressAsBytes, response.body()) } + @Test + fun testDisableCallDecoding() = testApplication { + val compressed = GZip.encode(ByteReadChannel(textToCompressAsBytes)).readRemaining().readByteArray() + + install(Compression) + routing { + post("/gzip") { + call.suppressDecompression() + assertEquals("gzip", call.request.headers[HttpHeaders.ContentEncoding]) + val body = call.receive() + assertContentEquals(compressed, body) + call.respond(textToCompress) + } + } + + val response = client.post("/gzip") { + setBody(compressed) + header(HttpHeaders.ContentEncoding, "gzip") + header(HttpHeaders.AcceptEncoding, "gzip") + } + assertContentEquals(compressed, response.body()) + } + private suspend fun ApplicationTestBuilder.handleAndAssert( url: String, acceptHeader: String?, diff --git a/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/CORSTest.kt b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/CORSTest.kt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/ktor-shared/ktor-junit/jvm/src/io/ktor/junit/Assertions.kt b/ktor-shared/ktor-junit/jvm/src/io/ktor/junit/Assertions.kt index f5e0d07b1e8..9e1545f0fb4 100644 --- a/ktor-shared/ktor-junit/jvm/src/io/ktor/junit/Assertions.kt +++ b/ktor-shared/ktor-junit/jvm/src/io/ktor/junit/Assertions.kt @@ -4,6 +4,9 @@ package io.ktor.junit +import java.io.* +import kotlin.test.* + /** * Convenience function for asserting on all elements of a collection. */ @@ -29,3 +32,14 @@ fun assertAll(collection: Iterable, assertion: (T) -> Unit) { } ) } + +inline fun assertSerializable(obj: T, checkEquality: Boolean = true): T { + val encoded = ByteArrayOutputStream().also { + ObjectOutputStream(it).writeObject(obj) + }.toByteArray() + val decoded = ObjectInputStream(encoded.inputStream()).readObject() as T + if (checkEquality) { + assertEquals(obj, decoded, "deserialized object must be equal to original object") + } + return decoded +} diff --git a/ktor-utils/jvm/src/io/ktor/util/cio/FileChannels.kt b/ktor-utils/jvm/src/io/ktor/util/cio/FileChannels.kt index e673f52f114..c6b72d89991 100644 --- a/ktor-utils/jvm/src/io/ktor/util/cio/FileChannels.kt +++ b/ktor-utils/jvm/src/io/ktor/util/cio/FileChannels.kt @@ -1,12 +1,10 @@ /* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.util.cio -import io.ktor.util.* import io.ktor.utils.io.* -import io.ktor.utils.io.core.* import io.ktor.utils.io.jvm.nio.* import kotlinx.coroutines.* import java.io.*