-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
28 changed files
with
4,764 additions
and
2 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
...runtime-api/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/api/address/Address.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* | ||
* Copyright (c) 2023 Matthew Nelson | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
**/ | ||
package io.matthewnelson.kmp.tor.runtime.api.address | ||
|
||
import kotlin.jvm.JvmField | ||
|
||
/** | ||
* Base abstraction for all address types | ||
* | ||
* @see [IPAddress] | ||
* @see [OnionAddress] | ||
* @see [ProxyAddress] | ||
* */ | ||
public sealed class Address( | ||
@JvmField | ||
public val value: String, | ||
): Comparable<Address> { | ||
|
||
/** | ||
* Returns the [value] in it's canonicalized hostname form | ||
* | ||
* e.g. | ||
* | ||
* println("127.0.0.1" | ||
* .toIPAddressV4() | ||
* .canonicalHostname() | ||
* ) | ||
* // 127.0.0.1 | ||
* | ||
* println("::1" | ||
* .toIPAddressV6() | ||
* .canonicalHostname() | ||
* ) | ||
* // [::1] | ||
* | ||
* println("http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion" | ||
* .toOnionAddressV3() | ||
* .canonicalHostname() | ||
* ) | ||
* // 2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion | ||
* | ||
* println("http://127.0.0.1:8081/path" | ||
* .toProxyAddress() | ||
* .canonicalHostName() | ||
* ) | ||
* // 127.0.0.1 | ||
* */ | ||
public abstract fun canonicalHostname(): String | ||
|
||
public final override fun compareTo(other: Address): Int = value.compareTo(other.value) | ||
|
||
public final override fun equals(other: Any?): Boolean { | ||
if (other === this) return true | ||
if (other !is Address) return false | ||
if (other::class != this::class) return false | ||
return other.value == value | ||
} | ||
|
||
public final override fun hashCode(): Int { | ||
var result = 17 | ||
result = result * 31 + this::class.hashCode() | ||
result = result * 31 + value.hashCode() | ||
return result | ||
} | ||
|
||
public final override fun toString(): String = value | ||
} |
199 changes: 199 additions & 0 deletions
199
...ntime-api/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/api/address/IPAddress.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
/* | ||
* Copyright (c) 2023 Matthew Nelson | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
**/ | ||
package io.matthewnelson.kmp.tor.runtime.api.address | ||
|
||
import io.matthewnelson.kmp.tor.runtime.api.address.IPAddress.V4.Companion.toIPAddressV4OrNull | ||
import io.matthewnelson.kmp.tor.runtime.api.address.IPAddress.V6.Companion.toIPAddressV6OrNull | ||
import io.matthewnelson.kmp.tor.runtime.api.internal.findHostnameAndPortFromURL | ||
import kotlin.jvm.JvmName | ||
import kotlin.jvm.JvmStatic | ||
|
||
/** | ||
* Base abstraction for denoting a String value as an ip address | ||
* */ | ||
public sealed class IPAddress private constructor(value: String): Address(value) { | ||
|
||
public companion object { | ||
|
||
/** | ||
* Parses a String for its IPv4 or IPv6 address. | ||
* | ||
* String can be either a URL containing the IP address, or the | ||
* IPv4/IPv6 address itself. | ||
* | ||
* @return [IPAddress] | ||
* @throws [IllegalArgumentException] if no IP address is found | ||
* */ | ||
@JvmStatic | ||
@JvmName("get") | ||
@Throws(IllegalArgumentException::class) | ||
public fun String.toIPAddress(): IPAddress { | ||
return toIPAddressOrNull() | ||
?: throw IllegalArgumentException("$this does not contain an IP address") | ||
} | ||
|
||
/** | ||
* Parses a String for its IPv4 or IPv6 address. | ||
* | ||
* String can be either a URL containing the IP address, or the | ||
* IPv4/IPv6 address itself. | ||
* | ||
* @return [IPAddress] or null | ||
* */ | ||
@JvmStatic | ||
@JvmName("getOrNull") | ||
public fun String.toIPAddressOrNull(): IPAddress? { | ||
return toIPAddressV4OrNull() | ||
?: toIPAddressV6OrNull() | ||
} | ||
} | ||
|
||
/** | ||
* Holder for an IPv4 address | ||
* */ | ||
public class V4 private constructor(value: String): IPAddress(value) { | ||
|
||
public override fun canonicalHostname(): String = value | ||
|
||
public companion object { | ||
|
||
/** | ||
* Parses a String for its IPv4 address. | ||
* | ||
* String can be either a URL containing the IPv4 address, or the | ||
* IPv4 address itself. | ||
* | ||
* @return [IPAddress.V4] | ||
* @throws [IllegalArgumentException] if no IPv4 address is found | ||
* */ | ||
@JvmStatic | ||
@JvmName("get") | ||
@Throws(IllegalArgumentException::class) | ||
public fun String.toIPAddressV4(): V4 { | ||
return toIPAddressV4OrNull() | ||
?: throw IllegalArgumentException("$this does not contain an IPv4 address") | ||
} | ||
|
||
/** | ||
* Parses a String for its IPv4 address. | ||
* | ||
* String can be either a URL containing the IPv4 address, or the | ||
* IPv4 address itself. | ||
* | ||
* @return [IPAddress.V4] or null | ||
* */ | ||
@JvmStatic | ||
@JvmName("getOrNull") | ||
public fun String.toIPAddressV4OrNull(): V4? { | ||
val stripped = findHostnameAndPortFromURL() | ||
.substringBeforeLast(':') | ||
|
||
if (!stripped.matches(REGEX)) return null | ||
return V4(stripped) | ||
} | ||
|
||
// https://ihateregex.io/expr/ip/ | ||
@Suppress("RegExpSimplifiable") | ||
private val REGEX: Regex = Regex(pattern = | ||
"(" + | ||
"\\b25[0-5]|" + | ||
"\\b2[0-4][0-9]|" + | ||
"\\b[01]?[0-9][0-9]?)(\\.(25[0-5]|" + | ||
"2[0-4][0-9]|" + | ||
"[01]?[0-9][0-9]?)" + | ||
"){3}" | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* Holder for an IPv6 address | ||
* */ | ||
public class V6 private constructor(value: String): IPAddress(value) { | ||
|
||
public override fun canonicalHostname(): String = "[$value]" | ||
|
||
public companion object { | ||
|
||
/** | ||
* Parses a String for its IPv6 address. | ||
* | ||
* String can be either a URL containing the IPv6 address, or the | ||
* IPv6 address itself. | ||
* | ||
* @return [IPAddress.V6] | ||
* @throws [IllegalArgumentException] if no IPv6 address is found | ||
* */ | ||
@JvmStatic | ||
@JvmName("get") | ||
@Throws(IllegalArgumentException::class) | ||
public fun String.toIPAddressV6(): V6 { | ||
return toIPAddressV6OrNull() | ||
?: throw IllegalArgumentException("$this does not contain an IPv6 address") | ||
} | ||
|
||
/** | ||
* Parses a String for its IPv6 address. | ||
* | ||
* String can be either a URL containing the IPv6 address, or the | ||
* IPv6 address itself. | ||
* | ||
* @return [IPAddress.V6] or null | ||
* */ | ||
@JvmStatic | ||
@JvmName("getOrNull") | ||
public fun String.toIPAddressV6OrNull(): V6? { | ||
var stripped = findHostnameAndPortFromURL() | ||
|
||
// is canonical with port >> [::1]:8080 | ||
if (stripped.startsWith('[') && stripped.contains("]:")) { | ||
stripped = stripped.substringBeforeLast(':') | ||
} | ||
|
||
if (stripped.startsWith('[') && stripped.endsWith(']')) { | ||
stripped = stripped.drop(1).dropLast(1) | ||
} | ||
|
||
if (!stripped.matches(REGEX)) return null | ||
return V6(stripped) | ||
} | ||
|
||
// https://ihateregex.io/expr/ipv6/ | ||
@Suppress("RegExpSimplifiable") | ||
private val REGEX: Regex = Regex(pattern = | ||
"(" + | ||
"([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + | ||
"([0-9a-fA-F]{1,4}:){1,7}:|" + | ||
"([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + | ||
"([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + | ||
"([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + | ||
"([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + | ||
"([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + | ||
"[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + | ||
":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + | ||
"fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + | ||
"::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|" + | ||
"(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + | ||
"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|" + | ||
"([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|" + | ||
"(2[0-4]|" + | ||
"1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|" + | ||
"(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" + | ||
")" | ||
) | ||
} | ||
} | ||
} |
Oops, something went wrong.