Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KTOR-6182 Support throwing UnknownServiceException if there is no cleartext traffic permitted #3725

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
@@ -0,0 +1,4 @@
public final class io/ktor/client/plugins/platform/PlatformingKt {
public static final fun getPlatforming ()Lio/ktor/client/plugins/api/ClientPlugin;
}

@@ -0,0 +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 = "Ktor client platform support"

useJdkVersionForJvmTests(11)
@@ -0,0 +1,10 @@
/*
* 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.platform

internal expect object Platform {

fun isCleartextTrafficPermitted(hostname: String): Boolean
}
@@ -0,0 +1,21 @@
/*
* 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.platform

import io.ktor.client.plugins.api.ClientPlugin
import io.ktor.client.plugins.api.createClientPlugin
import io.ktor.client.request.host
import io.ktor.utils.io.errors.UnknownServiceException

public val Platforming: ClientPlugin<Any> = createClientPlugin("Platforming", ::Any) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can I install this plugin for CIO by default?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's make this in the separate PR, it looks like it require service loader or some similar mechanism

onRequest { request, _ ->
val host = request.host
if (!Platform.isCleartextTrafficPermitted(host)) {
throw UnknownServiceException(
"CLEARTEXT communication to $host not permitted by network security policy"
)
}
}
}
@@ -0,0 +1,61 @@
/*
* 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.platform

import java.lang.reflect.InvocationTargetException

internal actual object Platform {
/**
* This explicit check avoids activating in Android Studio with Android specific classes available when running plugins inside the IDE.
*/
private val isAndroid: Boolean = System.getProperty("java.vm.name") == "Dalvik"

actual fun isCleartextTrafficPermitted(hostname: String): Boolean {
if (!isAndroid) return true
return try {
val networkPolicyClass = Class.forName("android.security.NetworkSecurityPolicy")
val getInstanceMethod = networkPolicyClass.getMethod("getInstance").apply { isAccessible = true }
val networkSecurityPolicy = getInstanceMethod.invoke(null)
api24IsCleartextTrafficPermitted(hostname, networkPolicyClass, networkSecurityPolicy)
} catch (_: ClassNotFoundException) {
true
} catch (_: NoSuchMethodException) {
true
} catch (e: IllegalAccessException) {
throw AssertionError("unable to determine cleartext support", e)
} catch (e: IllegalArgumentException) {
throw AssertionError("unable to determine cleartext support", e)
} catch (e: InvocationTargetException) {
throw AssertionError("unable to determine cleartext support", e)
}
}

@Throws(InvocationTargetException::class, IllegalAccessException::class)
private fun api24IsCleartextTrafficPermitted(
hostname: String,
networkPolicyClass: Class<*>,
networkSecurityPolicy: Any
): Boolean = try {
val isCleartextTrafficPermittedMethod = networkPolicyClass
.getMethod("isCleartextTrafficPermitted", String::class.java)
.apply { isAccessible = true }
isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy, hostname) as Boolean
} catch (_: NoSuchMethodException) {
api23IsCleartextTrafficPermitted(networkPolicyClass, networkSecurityPolicy)
}

@Throws(InvocationTargetException::class, IllegalAccessException::class)
private fun api23IsCleartextTrafficPermitted(
networkPolicyClass: Class<*>,
networkSecurityPolicy: Any
): Boolean = try {
val isCleartextTrafficPermittedMethod = networkPolicyClass
.getMethod("isCleartextTrafficPermitted")
.apply { isAccessible = true }
isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy) as Boolean
} catch (_: NoSuchMethodException) {
true
}
}
@@ -0,0 +1,10 @@
/*
* 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.platform

internal actual object Platform {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems nonJvm source set hasn't been recognized, how can I implement this actual object for non-jvm platforms?

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Goooler, it will need to be implemented for js and posix source sets.

To check the implementation in ide, you can invert the flag ktor.ide.jvmAndCommonOnly in gradle.properties https://github.com/ktorio/ktor/blob/92ece71d433f6d96754ef7d18ab2691e1f36160a/gradle.properties#L7C1-L7C1 and import project again

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


fun isCleartextTrafficPermitted(hostname: String): Boolean = false
}
2 changes: 2 additions & 0 deletions ktor-io/common/src/io/ktor/utils/io/errors/Errors.kt
Expand Up @@ -5,3 +5,5 @@ public expect open class IOException(message: String, cause: Throwable?) : Excep
}

public expect open class EOFException(message: String) : IOException

public expect open class UnknownServiceException(message: String) : IOException
2 changes: 2 additions & 0 deletions ktor-io/jvm/src/io/ktor/utils/io/errors/IOException.kt
Expand Up @@ -3,3 +3,5 @@ package io.ktor.utils.io.errors
public actual typealias IOException = java.io.IOException

public actual typealias EOFException = java.io.EOFException

public actual typealias UnknownServiceException = java.net.UnknownServiceException
1 change: 1 addition & 0 deletions settings.gradle.kts
Expand Up @@ -92,6 +92,7 @@ include(":ktor-client:ktor-client-plugins:ktor-client-json:ktor-client-serializa
include(":ktor-client:ktor-client-plugins:ktor-client-auth")
include(":ktor-client:ktor-client-plugins:ktor-client-call-id")
include(":ktor-client:ktor-client-plugins:ktor-client-logging")
include(":ktor-client:ktor-client-plugins:ktor-client-platform")
include(":ktor-client:ktor-client-plugins:ktor-client-encoding")
include(":ktor-client:ktor-client-plugins:ktor-client-websockets")
include(":ktor-client:ktor-client-plugins:ktor-client-content-negotiation")
Expand Down