WHOIS & RDAP Lookup — Ruby 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 Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.
# whois — RDAP input classification + URL building (WHOIS tool, Ruby port of src/lib/whois.ts; fetch omitted).
ASN_RE = /^(?:as)?(\d{1,10})$/i.freeze
LABEL_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.freeze
module WhoisRdap
module_function
# Strip scheme, userinfo, path, and optional port down to the bare host.
def strip_to_host(raw)
s = raw.strip.sub(%r{^[a-z][a-z0-9+.\-]*://}i, '').sub(/^(?:whois|rdap):/i, '')
s = s[(s.rindex('@') + 1)..] if s.include?('@')
s = s.split(%r{[/?#]}).first.to_s
return s[/^\[([0-9a-fA-F:.]+)\](?::\d+)?$/, 1] if s.start_with?('[') # [ipv6](:port)?
s.sub(/^([^:\[\]]*\.[^:\[\]]*):\d+$/, '\1') # dotted host with port
end
def ipv4?(host) # four decimal octets <= 255, no leading zeros
parts = host.split('.')
parts.size == 4 && parts.all? do |p|
p.match?(/\A\d{1,3}\z/) && p.to_i <= 255 && (p == '0' || !p.start_with?('0'))
end
end
def ipv6?(host) # hex groups of 1-4, at most one "::" collapse
return false unless host.include?(':') && host.match?(/\A[0-9a-fA-F:]+\z/)
halves = host.split('::', -1)
return false if halves.size > 2
groups = halves.reject(&:empty?).flat_map { |h| h.split(':') }
groups.size <= 8 && groups.all? { |g| g.match?(/\A[0-9a-fA-F]{1,4}\z/) }
end
# Classify a lookup target: ASN (as?\d+) | IPv4 | IPv6 | domain. Never raises.
def classify_input(raw)
host = strip_to_host(raw.to_s)
return { kind: 'invalid', value: '' } if host.empty?
if (m = ASN_RE.match(host)) && m[1].to_i.positive?
return { kind: 'asn', value: m[1].to_i.to_s }
end
return { kind: 'ipv4', value: host } if ipv4?(host)
return { kind: 'ipv6', value: host.downcase } if ipv6?(host)
norm = host.downcase.sub(/\.+$/, '') # trailing root dot(s)
labels = norm.split('.')
unless norm.include?('.') && labels.all? { |l| l.match?(LABEL_RE) }
return { kind: 'invalid', value: '' }
end
{ kind: 'domain', value: norm, tld: labels.last }
end
# The RDAP path segment type for a classified input.
def object_path(kind)
{ 'domain' => 'domain', 'ipv4' => 'ip', 'ipv6' => 'ip', 'asn' => 'autnum' }[kind]
end
# Exactly one slash separates the base and the object path.
def build_rdap_url(base_url, classified)
path = object_path(classified[:kind])
path ? "#{base_url.sub(%r{/+$}, '')}/#{path}/#{classified[:value]}" : ''
end
# Bootstrap (dns.json / asn.json) keys: "lo-hi" ranges or bare numbers.
def asn_in_range(key, asn)
lo, hi = key.split('-')
hi ? asn.between?(lo.to_i, hi.to_i) : asn == lo.to_i
end
def resolve_server(entries, tld: nil, asn: nil)
entries.each do |keys, url|
hit = keys.any? { |k| (tld && k.casecmp?(tld)) || (asn && asn_in_range(k, asn)) }
return url if hit
end
nil
end
end
if $PROGRAM_NAME == __FILE__
dns = [[%w[org com], 'https://rdap.org']]
asns = [[['64496-64511'], 'https://rdap.arin.net/registry']]
['https://CosmoLabs.org/tools#whois', 'AS3356', '192.0.2.1', 'not_a_domain'].each do |q|
c = WhoisRdap.classify_input(q)
base = if c[:kind] == 'domain'
WhoisRdap.resolve_server(dns, tld: c[:tld])
elsif c[:kind] == 'asn'
WhoisRdap.resolve_server(asns, asn: c[:value].to_i)
end
path = WhoisRdap.object_path(c[:kind]) || 'invalid'
puts format('%-34s %-8s %s', q, path, base ? WhoisRdap.build_rdap_url(base, c) : '')
end
end
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 →