WHOIS & RDAP Lookup — C# 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 C# implementation — the same logic the interactive tool runs, in a shareable, citable form.
// whois — RDAP input classification + URL building (WHOIS tool, C# port of src/lib/whois.ts; fetch omitted).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
record Classified(string Kind, string Value, string? Tld = null); // Kind: domain|ipv4|ipv6|asn|invalid
static class WhoisRdap
{
static readonly Regex AsnRe = new(@"^(?:as)?(\d{1,10})$", RegexOptions.IgnoreCase);
static readonly Regex Ipv4Re = new(@"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$");
static readonly Regex LabelRe = new(@"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$", RegexOptions.IgnoreCase);
// Strip scheme, userinfo, path, and optional port down to the bare host.
static string StripToHost(string raw)
{
string s = raw.Trim();
s = Regex.Replace(s, @"^[a-z][a-z0-9+.-]*://", "", RegexOptions.IgnoreCase);
s = Regex.Replace(s, @"^(?:whois|rdap):", "", RegexOptions.IgnoreCase);
if (s.Contains('@')) s = s[(s.LastIndexOf('@') + 1)..];
s = Regex.Replace(s, @"[/?#].*$", "");
var bracketed = Regex.Match(s, @"^\[([0-9a-fA-F:.]+)\](?::\d+)?$"); // [ipv6](:port)?
if (bracketed.Success) return bracketed.Groups[1].Value;
return Regex.Replace(s, @"^([^:\[\]]*\.[^:\[\]]*):\d+$", "$1"); // host:port -> host
}
static bool IsValidIpv4(string host) => // four decimal octets <= 255, no leading zeros
Ipv4Re.Match(host) is { Success: true } m
&& m.Groups.Cast<Group>().Skip(1).All(g => int.Parse(g.Value) <= 255
&& (g.Value == "0" || !g.Value.StartsWith('0')));
static bool IsValidIpv6(string host) { // hex groups of 1-4, at most one "::" collapse
if (!host.Contains(':') || !Regex.IsMatch(host, @"^[0-9a-fA-F:]+$")) return false;
string[] halves = host.Split("::", StringSplitOptions.None);
if (halves.Length > 2) return false;
var groups = halves.Where(p => p.Length > 0).SelectMany(p => p.Split(':')).ToList();
return groups.Count <= 8 && groups.All(g => Regex.IsMatch(g, @"^[0-9a-fA-F]{1,4}$"));
}
public static Classified ClassifyInput(string raw) { // ASN (as?\d+) | IPv4 | IPv6 | domain
string host = StripToHost(raw ?? "");
if (host == "") return new("invalid", "");
var asn = AsnRe.Match(host);
if (asn.Success && long.Parse(asn.Groups[1].Value) > 0)
return new("asn", long.Parse(asn.Groups[1].Value).ToString());
if (IsValidIpv4(host)) return new("ipv4", host);
if (IsValidIpv6(host)) return new("ipv6", host.ToLowerInvariant());
string norm = host.ToLowerInvariant().TrimEnd('.'); // trailing root dot(s)
string[] labels = norm.Split('.');
if (!norm.Contains('.') || labels.Any(l => !LabelRe.IsMatch(l))) return new("invalid", "");
return new("domain", norm, labels[^1]);
}
// The RDAP path segment type for a classified input.
public static string? ObjectPath(string kind) =>
kind switch { "domain" => "domain", "ipv4" or "ipv6" => "ip", "asn" => "autnum", _ => null };
// Exactly one slash separates the base and the object path.
public static string BuildRdapUrl(string baseUrl, Classified c) =>
ObjectPath(c.Kind) is { } p ? $"{baseUrl.TrimEnd('/')}/{p}/{c.Value}" : "";
}
static class Bootstrap // dns.json / asn.json: exact TLD match, or "lo-hi" ASN ranges
{
public static string? Resolve(List<(string[] Keys, string Url)> entries, string? tld, long? asn) =>
entries.FirstOrDefault(e => tld != null && e.Keys.Any(k => k.Equals(tld, StringComparison.OrdinalIgnoreCase))
|| asn != null && e.Keys.Any(k => AsnInRange(k, asn.Value)))?.Url;
static bool AsnInRange(string key, long asn) =>
key.Split('-') is { Length: 2 } r ? asn >= long.Parse(r[0]) && asn <= long.Parse(r[1])
: long.Parse(key) == asn;
}
class Demo
{
static void Main()
{
var dns = new List<(string[] Keys, string Url)> { (new[] { "org", "com" }, "https://rdap.org") };
var asns = new List<(string[] Keys, string Url)> { (new[] { "64496-64511" }, "https://rdap.arin.net/registry") };
foreach (var q in new[] { "https://CosmoLabs.org/tools#whois", "AS3356", "192.0.2.1", "not_a_domain" })
{
var c = WhoisRdap.ClassifyInput(q);
var baseUrl = c.Kind == "domain" ? Bootstrap.Resolve(dns, c.Tld, null)
: c.Kind == "asn" ? Bootstrap.Resolve(asns, null, long.Parse(c.Value)) : null;
string path = WhoisRdap.ObjectPath(c.Kind) ?? "invalid";
Console.WriteLine($"{q,-34} {path,-8} {baseUrl != null ? WhoisRdap.BuildRdapUrl(baseUrl, 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 →