Skip to content

Commit 6fdba95

Browse files
authored
KTOR-8490 Do not parse ipString (#4912)
1 parent 9a045e8 commit 6fdba95

File tree

15 files changed

+91
-157
lines changed

15 files changed

+91
-157
lines changed

ktor-io/posix/src/io/ktor/utils/io/errors/PosixErrors.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
package io.ktor.utils.io.errors
66

7-
import kotlinx.cinterop.*
8-
import kotlinx.io.*
7+
import kotlinx.cinterop.ExperimentalForeignApi
8+
import kotlinx.cinterop.memScoped
9+
import kotlinx.cinterop.toKString
910
import platform.posix.*
10-
import kotlin.native.concurrent.*
1111

1212
private val KnownPosixErrors = mapOf(
1313
EBADF to "EBADF",
@@ -78,7 +78,7 @@ public sealed class PosixException(public val errno: Int, message: String) : Exc
7878
*
7979
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.utils.io.errors.PosixException.Companion.forErrno)
8080
*
81-
* @param errno error code returned by [posix.platform.errno]
81+
* @param errno error code returned by [platform.posix.errno]
8282
* @param posixFunctionName optional function name to be included to the exception message
8383
* @return an instance of [PosixException] or it's subtype
8484
*/

ktor-network/common/src/io/ktor/network/sockets/SocketAddress.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ public expect class InetSocketAddress(
5151
/**
5252
* Returns the raw IP address bytes of this socket address.
5353
*
54-
* The returned array is 4 bytes for IPv4 addresses and 16 bytes for IPv6 addresses.
55-
* Returns null if the address cannot be resolved or is not a valid IP address.
54+
* The returned array is 4-bytes for IPv4 addresses and 16-bytes for IPv6 addresses.
55+
* Returns `null` if the address cannot be resolved or is not a valid IP address.
5656
*
57-
* Always returns null for wasm/js targets.
57+
* Always returns `null` for Kotlin/JS and Kotlin/Wasm targets.
5858
*
59-
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.network.sockets.InetSocketAddress.address)
59+
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.network.sockets.InetSocketAddress.resolveAddress)
6060
*/
6161
public fun resolveAddress(): ByteArray?
6262

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package io.ktor.network.sockets.tests
6+
7+
import io.ktor.network.sockets.*
8+
import io.ktor.util.*
9+
import kotlin.test.BeforeTest
10+
import kotlin.test.Test
11+
import kotlin.test.assertContentEquals
12+
13+
class InetSocketAddressTest {
14+
15+
@BeforeTest
16+
fun setUp() {
17+
initSocketsIfNeeded()
18+
}
19+
20+
@Test
21+
fun testResolveAddress() {
22+
// Address resolving is not supported on JS and WASM platforms
23+
if (PlatformUtils.IS_JS || PlatformUtils.IS_WASM_JS) return
24+
25+
val testCases = listOf(
26+
"127.0.0.1" to byteArrayOf(127, 0, 0, 1),
27+
"::1" to byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
28+
)
29+
30+
for ((hostname, expectedBytes) in testCases) {
31+
val address = InetSocketAddress(hostname, 8080)
32+
val resolved = address.resolveAddress()
33+
assertContentEquals(expectedBytes, resolved, "Unexpected bytes for the address '$hostname'")
34+
}
35+
}
36+
}

ktor-network/common/test/io/ktor/network/sockets/tests/TestUtils.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ internal fun createTempFilePath(basename: String): String {
3535
internal fun removeFile(path: String) {
3636
SystemFileSystem.delete(Path(path), mustExist = false)
3737
}
38+
39+
internal expect fun initSocketsIfNeeded()

ktor-network/jsAndWasmShared/test/io/ktor/network/sockets/tests/TestUtils.jsAndWasmShared.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
package io.ktor.network.sockets.tests
66

77
internal actual fun Throwable.isPosixException(): Boolean = false
8+
9+
actual fun initSocketsIfNeeded() {}

ktor-network/jvm/test/io/ktor/network/sockets/tests/TestUtilsJvm.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
package io.ktor.network.sockets.tests
66

77
internal actual fun Throwable.isPosixException(): Boolean = false
8+
9+
actual fun initSocketsIfNeeded() {}

ktor-network/nix/src/io/ktor/network/util/NativeSocketAddressNix.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ internal actual class NativeIPv4SocketAddress(
2828
}
2929
}
3030

31+
@OptIn(ExperimentalForeignApi::class)
32+
actual override val rawAddressBytes: ByteArray
33+
get() = cValue<in_addr> { s_addr = address }.getBytes()
34+
3135
@OptIn(ExperimentalForeignApi::class)
3236
actual override val ipString: String
3337
get() = memScoped {
@@ -45,18 +49,17 @@ internal actual class NativeIPv4SocketAddress(
4549
}
4650
}
4751

48-
@OptIn(UnsafeNumber::class)
52+
@OptIn(UnsafeNumber::class, ExperimentalForeignApi::class)
4953
internal actual class NativeIPv6SocketAddress(
5054
family: UByte,
51-
private val rawAddress: in6_addr,
55+
private val rawAddress: CValue<in6_addr>,
5256
port: Int,
5357
private val flowInfo: uint32_t,
5458
private val scopeId: uint32_t
5559
) : NativeInetSocketAddress(family, port) {
5660

5761
override fun toString(): String = "NativeIPv6SocketAddress[$ipString:$port]"
5862

59-
@OptIn(ExperimentalForeignApi::class)
6063
actual override fun nativeAddress(block: (address: CPointer<sockaddr>, size: UInt) -> Unit) {
6164
cValue<sockaddr_in6> {
6265
sin6_family = family.convert()
@@ -68,7 +71,9 @@ internal actual class NativeIPv6SocketAddress(
6871
}
6972
}
7073

71-
@OptIn(ExperimentalForeignApi::class)
74+
actual override val rawAddressBytes: ByteArray
75+
get() = rawAddress.getBytes()
76+
7277
actual override val ipString: String
7378
get() = memScoped {
7479
val string = allocArray<ByteVar>(INET6_ADDRSTRLEN)

ktor-network/nix/src/io/ktor/network/util/SocketUtils.nix.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ internal actual fun sockaddr.toNativeSocketAddress(): NativeSocketAddress = when
5959
val address = ptr.reinterpret<sockaddr_in6>().pointed
6060
NativeIPv6SocketAddress(
6161
address.sin6_family.convert(),
62-
address.sin6_addr,
62+
address.sin6_addr.readValue(),
6363
networkToHostOrder(address.sin6_port).toInt(),
6464
address.sin6_flowinfo,
6565
address.sin6_scope_id

ktor-network/posix/src/io/ktor/network/sockets/SocketAddress.nonJvm.posix.kt

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,12 @@
44

55
package io.ktor.network.sockets
66

7-
import io.ktor.network.util.NativeIPv4SocketAddress
8-
import io.ktor.network.util.NativeIPv6SocketAddress
9-
import io.ktor.network.util.parseIPv4String
10-
import io.ktor.network.util.parseIPv6String
11-
import io.ktor.network.util.resolve
7+
import io.ktor.network.util.*
128

139
internal actual fun InetSocketAddress.platformResolveAddress(): ByteArray? {
1410
return this.resolve().firstOrNull()?.let {
1511
when (it) {
16-
is NativeIPv4SocketAddress -> parseIPv4String(it.ipString)
17-
is NativeIPv6SocketAddress -> parseIPv6String(it.ipString)
12+
is NativeInetSocketAddress -> it.rawAddressBytes
1813
else -> null
1914
}
2015
}

ktor-network/posix/src/io/ktor/network/util/NativeSocketAddress.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
package io.ktor.network.util
66

77
import io.ktor.network.sockets.*
8-
import kotlinx.cinterop.*
9-
import platform.posix.*
8+
import kotlinx.cinterop.CPointer
9+
import kotlinx.cinterop.ExperimentalForeignApi
10+
import kotlinx.cinterop.convert
11+
import platform.posix.sockaddr
1012

1113
/**
1214
* Represents a native socket address.
@@ -23,18 +25,21 @@ internal abstract class NativeInetSocketAddress(
2325
family: UByte,
2426
val port: Int
2527
) : NativeSocketAddress(family) {
26-
internal abstract val ipString: String
28+
abstract val rawAddressBytes: ByteArray
29+
abstract val ipString: String
2730
}
2831

2932
internal expect class NativeIPv4SocketAddress : NativeInetSocketAddress {
3033
@OptIn(ExperimentalForeignApi::class)
3134
override fun nativeAddress(block: (address: CPointer<sockaddr>, size: UInt) -> Unit)
35+
override val rawAddressBytes: ByteArray
3236
override val ipString: String
3337
}
3438

3539
internal expect class NativeIPv6SocketAddress : NativeInetSocketAddress {
3640
@OptIn(ExperimentalForeignApi::class)
3741
override fun nativeAddress(block: (address: CPointer<sockaddr>, size: UInt) -> Unit)
42+
override val rawAddressBytes: ByteArray
3843
override val ipString: String
3944
}
4045

ktor-network/posix/src/io/ktor/network/util/SocketAddressUtils.kt

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,48 +19,3 @@ internal fun SocketAddress.resolve(): List<NativeSocketAddress> = when (this) {
1919
is InetSocketAddress -> getAddressInfo(hostname, port)
2020
is UnixSocketAddress -> listOf(NativeUnixSocketAddress(AF_UNIX.convert(), path))
2121
}
22-
23-
internal fun parseIPv4String(ipString: String): ByteArray? {
24-
return try {
25-
val octets = ipString.split('.').also {
26-
require(it.size == 4) { "Invalid IPv4 string: $ipString" }
27-
}
28-
29-
octets.map { it.toUByte().toByte() }.toByteArray()
30-
} catch (_: Throwable) {
31-
null
32-
}
33-
}
34-
35-
internal fun parseIPv6String(ipString: String): ByteArray? {
36-
return try {
37-
val groups = if ("::" in ipString) {
38-
val parts = ipString.split("::", limit = 2)
39-
40-
val groups = Pair(
41-
if (parts[0].isNotEmpty()) parts[0].split(':') else emptyList(),
42-
if (parts.size > 1 && parts[1].isNotEmpty()) parts[1].split(':') else emptyList()
43-
)
44-
45-
val totalGroups = groups.first.size + groups.second.size
46-
val emptyGroups = 8 - totalGroups
47-
48-
groups.first + List(emptyGroups) { "0" } + groups.second
49-
} else {
50-
ipString.split(':').also {
51-
require(it.size == 8) { "Invalid IPv6 string: $ipString" }
52-
}
53-
}
54-
55-
groups.flatMap {
56-
val int = if (it.matches(Regex("^[0-9a-fA-f]+$"))) {
57-
it.toUShort(16)
58-
} else {
59-
return null
60-
}.toInt()
61-
listOf((int shr 8).toByte(), int.toByte())
62-
}.toByteArray()
63-
} catch (_: Throwable) {
64-
null
65-
}
66-
}

ktor-network/posix/test/io/ktor/network/sockets/tests/IPStringParseTest.kt

Lines changed: 0 additions & 84 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package io.ktor.network.sockets.tests
6+
7+
actual fun initSocketsIfNeeded() = io.ktor.network.util.initSocketsIfNeeded()

ktor-network/windows/src/io/ktor/network/util/NativeSocketAddressWindows.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ package io.ktor.network.util
66

77
import kotlinx.cinterop.*
88
import platform.posix.*
9-
import platform.windows.*
9+
import platform.windows.INET6_ADDRSTRLEN
10+
import platform.windows.INET_ADDRSTRLEN
11+
import platform.windows.in6_addr
12+
import platform.windows.sockaddr_in6
1013

1114
internal actual class NativeIPv4SocketAddress(
1215
family: UByte,
@@ -28,6 +31,10 @@ internal actual class NativeIPv4SocketAddress(
2831
}
2932
}
3033

34+
@OptIn(ExperimentalForeignApi::class)
35+
actual override val rawAddressBytes: ByteArray
36+
get() = cValue<in_addr> { S_un.S_addr = address }.getBytes()
37+
3138
@OptIn(ExperimentalForeignApi::class)
3239
actual override val ipString: String
3340
get() = memScoped {
@@ -45,17 +52,17 @@ internal actual class NativeIPv4SocketAddress(
4552
}
4653
}
4754

55+
@OptIn(ExperimentalForeignApi::class)
4856
internal actual class NativeIPv6SocketAddress(
4957
family: UByte,
50-
private val rawAddress: in6_addr,
58+
private val rawAddress: CValue<in6_addr>,
5159
port: Int,
5260
private val flowInfo: uint32_t,
5361
private val scopeId: uint32_t
5462
) : NativeInetSocketAddress(family, port) {
5563

5664
override fun toString(): String = "NativeIPv6SocketAddress[$ipString:$port]"
5765

58-
@OptIn(ExperimentalForeignApi::class)
5966
actual override fun nativeAddress(block: (address: CPointer<sockaddr>, size: UInt) -> Unit) {
6067
cValue<sockaddr_in6> {
6168
sin6_family = family.convert()
@@ -67,7 +74,9 @@ internal actual class NativeIPv6SocketAddress(
6774
}
6875
}
6976

70-
@OptIn(ExperimentalForeignApi::class)
77+
actual override val rawAddressBytes: ByteArray
78+
get() = rawAddress.getBytes()
79+
7180
actual override val ipString: String
7281
get() = memScoped {
7382
val string = allocArray<ByteVar>(INET6_ADDRSTRLEN)

0 commit comments

Comments
 (0)