WHOIS & RDAP Lookup — Kotlin source
Look up registration data for any domain, IP address, or ASN — straight from your browser to the authoritative RDAP registry over HTTPS. No backend, no database of lookups, fully private.
This is the Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.
// whois — RDAP input classification + URL building (WHOIS tool, Kotlin port of src/lib/whois.ts; fetch omitted).
data class Classified(val kind: String, val value: String, val tld: String? = null) // kind: domain|ipv4|ipv6|asn|invalid
private val ASN_RE = Regex("""^(?:as)?(\d{1,10})$""", RegexOption.IGNORE_CASE)
private val LABEL_RE = Regex("""^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$""", RegexOption.IGNORE_CASE)
// Strip scheme, userinfo, path, and optional port down to the bare host.
fun stripToHost(raw: String): String {
var s = raw.trim().replace(Regex("""^[a-z][a-z0-9+.-]*://""", RegexOption.IGNORE_CASE), "")
s = s.replace(Regex("""^(?:whois|rdap):""", RegexOption.IGNORE_CASE), "")
if ('@' in s) s = s.substringAfterLast('@')
s = s.split("/", "?", "#").first()
val bracketed = Regex("""^\[([0-9a-fA-F:.]+)](?::\d+)?$""").find(s) // [ipv6](:port)?
if (bracketed != null) return bracketed.groupValues[1]
return s.replace(Regex("""^([^:\[\]]*\.[^:\[\]]*):\d+$"""), "$1") // dotted host with port
}
fun isValidIpv4(host: String): Boolean { // four decimal octets <= 255, no leading zeros
val parts = host.split('.')
return parts.size == 4 && parts.all { p ->
p.length in 1..3 && p.all { it.isDigit() } && p.toInt() <= 255 && (p == "0" || !p.startsWith("0"))
}
}
fun isValidIpv6(host: String): Boolean { // hex groups of 1-4, at most one "::" collapse
if (':' !in host || !host.matches(Regex("[0-9a-fA-F:]+"))) return false
val halves = host.split("::")
if (halves.size > 2) return false
val groups = halves.filter { it.isNotEmpty() }.flatMap { it.split(':') }
return groups.size <= 8 && groups.all { it.length in 1..4 && it.all { c -> c.isDigit() || c in 'a'..'f' || c in 'A'..'F' } }
}
fun classifyInput(raw: String?): Classified { // ASN (as?\d+) | IPv4 | IPv6 | domain; never throws
val host = stripToHost(raw ?: "")
if (host.isEmpty()) return Classified("invalid", "")
ASN_RE.find(host)?.takeIf { it.groupValues[1].toLong() > 0 }
?.let { return Classified("asn", it.groupValues[1].toLong().toString()) }
if (isValidIpv4(host)) return Classified("ipv4", host)
if (isValidIpv6(host)) return Classified("ipv6", host.lowercase())
val norm = host.lowercase().trimEnd('.') // trailing root dot(s)
val labels = norm.split('.')
if ('.' !in norm || labels.any { !LABEL_RE.matches(it) }) return Classified("invalid", "")
return Classified("domain", norm, labels.last())
}
// The RDAP path segment type for a classified input.
fun objectPath(kind: String): String? = when (kind) {
"domain" -> "domain"; "ipv4", "ipv6" -> "ip"; "asn" -> "autnum"; else -> null
}
// Exactly one slash separates the base and the object path.
fun buildRdapUrl(baseUrl: String, c: Classified): String =
objectPath(c.kind)?.let { "${baseUrl.trimEnd('/')}/$it/${c.value}" } ?: ""
// Bootstrap (dns.json / asn.json) keys: "lo-hi" ranges or bare numbers.
fun asnInRange(key: String, asn: Long): Boolean {
val r = key.split('-')
return if (r.size == 2) asn in r[0].toLong()..r[1].toLong() else asn == r[0].toLong()
}
fun resolveServer(entries: List<Pair<List<String>, String>>, tld: String? = null, asn: Long? = null): String? =
entries.firstNotNullOfOrNull { (keys, url) ->
keys.firstOrNull { (tld != null && it.equals(tld, ignoreCase = true)) || (asn != null && asnInRange(it, asn)) }
?.let { url }
}
fun main() {
val dns = listOf(listOf("org", "com") to "https://rdap.org")
val asns = listOf(listOf("64496-64511") to "https://rdap.arin.net/registry")
for (q in listOf("https://CosmoLabs.org/tools#whois", "AS3356", "192.0.2.1", "not_a_domain")) {
val c = classifyInput(q)
val base = when (c.kind) {
"domain" -> resolveServer(dns, tld = c.tld)
"asn" -> resolveServer(asns, asn = c.value.toLong())
else -> null
}
val path = objectPath(c.kind) ?: "invalid"
println("%-34s %-8s %s".format(q, path, base?.let { buildRdapUrl(it, c) } ?: ""))
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →