WHOIS & RDAP Lookup — PHP 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 PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.
<?php
/**
* RDAP lookup — WHOIS registration data over HTTPS (RFCs 7480-7485).
*
* Language: PHP (8.3+, cURL extension)
* CosmoDev polyglot showcase port of the `whois` tool.
* Ported from src/lib/whois.ts — display source, part of CosmoDev's
* polyglot tool pages.
*
* RDAP is the JSON successor to port-43 WHOIS. The client first asks
* IANA's bootstrap registry which server is authoritative for the TLD
* (dns.json) or the ASN block (asn.json), then fetches the record
* straight from that server — two HTTPS GETs, no WHOIS port, no backend.
*
* Uses cURL, PHP's canonical HTTP client. `array_any()` requires 8.3+.
*/
declare(strict_types=1);
const BOOTSTRAP_URLS = [
'domain' => 'https://data.iana.org/RDAP/dns.json',
'autnum' => 'https://data.iana.org/RDAP/asn.json',
];
/**
* GET a URL over HTTPS and decode the JSON body.
*
* @throws RuntimeException On network failure or an HTTP 4xx/5xx response.
*/
function fetch_json(string $url): array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 15,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($body === false || $error !== '') {
throw new RuntimeException("request failed: {$error}");
}
if ($status >= 400) {
throw new RuntimeException("HTTP {$status} for {$url}");
}
return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}
/** Classify a query: an AS number ("as3356" / "3356") or a domain name. */
function classify(string $query): array
{
if (preg_match('/^(?:as)?(\d{1,10})$/i', trim($query), $m) === 1 && (int) $m[1] > 0) {
return ['kind' => 'autnum', 'value' => (string) (int) $m[1]];
}
$value = rtrim(strtolower(trim($query)), '.');
$tld = substr($value, strrpos($value, '.') + 1);
return ['kind' => 'domain', 'value' => $value, 'tld' => $tld];
}
/** True when $asn falls inside an asn.json key ("lo-hi" or a bare number). */
function asn_in_range(string $key, int $asn): bool
{
if (str_contains($key, '-')) {
[$lo, $hi] = array_map('intval', explode('-', $key, 2));
return $asn >= $lo && $asn <= $hi;
}
return (int) $key === $asn;
}
/** Ask IANA's bootstrap which RDAP server is authoritative for the query. */
function resolve_rdap_base(array $q): string
{
$doc = fetch_json(BOOTSTRAP_URLS[$q['kind']]);
foreach ($doc['services'] as [$keys, $urls]) {
$hit = $q['kind'] === 'autnum'
? array_any($keys, fn ($k) => asn_in_range($k, (int) $q['value']))
: array_any($keys, fn ($k) => strcasecmp($k, $q['tld']) === 0);
if ($hit) {
return $urls[0];
}
}
throw new RuntimeException("no RDAP server for {$q['value']}");
}
/**
* Look up a domain or ASN over RDAP and pull the essentials: registrar,
* registration/expiration dates, status flags, and nameservers.
*
* @return array{registrar: ?string, registrarEmail: ?string,
* registered: ?string, expires: ?string,
* status: list<string>, nameservers: list<string>}
* @throws RuntimeException On network failure or unknown TLD/ASN.
*/
function whois_lookup(string $query): array
{
$q = classify($query);
$base = resolve_rdap_base($q);
$rdap = fetch_json(rtrim($base, '/') . "/{$q['kind']}/{$q['value']}");
// Dates are event objects: [{"eventAction": "registration", ...}, ...]
$dates = [];
foreach ($rdap['events'] ?? [] as $e) {
$dates[strtolower($e['eventAction'] ?? '')] = $e['eventDate'] ?? null;
}
// The registrar is an entity with role "registrar"; its name and email
// live inside the jCard array as ["fn", {}, "text", "..."] properties.
$registrar = null;
$email = null;
foreach ($rdap['entities'] ?? [] as $en) {
if (!in_array('registrar', $en['roles'] ?? [], true)) {
continue;
}
foreach ($en['vcardArray'][1] ?? [] as $prop) {
if ($prop[0] === 'fn' && $registrar === null) {
$registrar = $prop[3];
}
if ($prop[0] === 'email' && $email === null) {
$email = $prop[3];
}
}
break;
}
$nameservers = [];
foreach ($rdap['nameservers'] ?? [] as $ns) {
if (isset($ns['ldhName'])) {
$nameservers[] = $ns['ldhName'];
}
}
return [
'registrar' => $registrar,
'registrarEmail' => $email,
'registered' => $dates['registration'] ?? null,
'expires' => $dates['expiration'] ?? null,
'status' => $rdap['status'] ?? [],
'nameservers' => $nameservers,
];
}
// CLI demo: `php whois.php cosmolabs.org` (or `php whois.php as3356`).
print_r(whois_lookup($argv[1] ?? 'cosmolabs.org'));
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 →