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++20 port of src/lib/whois.ts; fetch omitted).
#include <algorithm>
#include <cctype>
#include <iostream>
#include <optional>
#include <string>
#include <vector>
enum class Kind { invalid, domain, ipv4, ipv6, asn };
struct Classified { Kind kind; std::string value, tld; };
static std::string lc(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
return s;
}
static bool label_ok(const std::string& s) { // a-z0-9 + inner '-', no edge '-'
if (s.empty() || s.size() > 63 || !std::isalnum((unsigned char)s.front()) || !std::isalnum((unsigned char)s.back())) return false;
for (size_t i = 1; i + 1 < s.size(); i++) if (!std::isalnum((unsigned char)s[i]) && s[i] != '-') return false;
return true;
}
static bool v4(const std::string& h) { // four decimal octets <= 255, no leading zeros
size_t i = 0;
for (int o = 0; o < 4; o++) {
if (!std::isdigit(h[i]) || (h[i] == '0' && std::isdigit(h[i + 1]))) return false;
long v = 0;
while (std::isdigit(h[i])) v = v * 10 + (h[i++] - '0');
if (v > 255 || (o < 3 && h[i++] != '.')) return false;
}
return i == h.size();
}
static bool v6(const std::string& h) { // hex groups of 1-4, at most one "::" collapse
if (h.find(':') == std::string::npos) return false;
auto dd = h.find("::");
if (dd != std::string::npos && h.find("::", dd + 1) != std::string::npos) return false;
int g = 0;
for (size_t i = 0; i < h.size();) {
if (h[i] == ':') { i++; continue; }
size_t len = 0;
while (std::isxdigit((unsigned char)h[i + len])) len++;
if (len < 1 || len > 4) return false;
g++, i += len;
}
return g <= 8 && (dd != std::string::npos || g == 8);
}
static std::string host_of(std::string raw) { // strip scheme/userinfo/path/port -> bare host
auto sep = raw.find("://");
std::string s = sep == std::string::npos ? raw : raw.substr(sep + 3);
if (s.starts_with("whois:") || s.starts_with("rdap:")) s = s.substr(s.find(':') + 1);
if (auto at = s.rfind('@'); at != std::string::npos) s = s.substr(at + 1);
for (char c : {'/', '?', '#'}) if (auto cut = s.find(c); cut != std::string::npos) s.resize(cut);
if (s.starts_with('[')) s = s.substr(1, s.find(']') - 1); // [ipv6](:port)?
else if (auto colon = s.rfind(':'); colon != std::string::npos && s.find(':') == colon) s.resize(colon); // host:port
return lc(s);
}
static Classified classify(const std::string& raw) { // ASN (as?\d+) | IPv4 | IPv6 | domain
std::string h = host_of(raw);
if (h.empty()) return { Kind::invalid, "", "" };
std::string asn = h.starts_with("as") ? h.substr(2) : h; // AS3356 / 3356
if (!asn.empty() && asn.size() <= 10 && asn.find_first_not_of("0123456789") == std::string::npos && std::stol(asn) > 0)
return { Kind::asn, std::to_string(std::stol(asn)), "" };
if (v4(h)) return { Kind::ipv4, h, "" };
if (v6(h)) return { Kind::ipv6, h, "" };
while (!h.empty() && h.back() == '.') h.pop_back(); // trailing root dot(s)
if (h.find('.') == std::string::npos || h.find("..") != std::string::npos) return { Kind::invalid, "", "" };
std::string val, tld;
for (size_t start = 0; start <= h.size();) { // split on '.', every label must be valid
size_t end = h.find('.', start);
std::string label = h.substr(start, end == std::string::npos ? end : end - start);
if (!label_ok(label)) return { Kind::invalid, "", "" };
val += label + ".", tld = label;
if (end == std::string::npos) break;
start = end + 1;
}
return { Kind::domain, val.substr(0, val.size() - 1), tld };
}
static std::optional<std::string> path_of(Kind k) { // the RDAP object path segment
switch (k) {
case Kind::domain: return "domain";
case Kind::ipv4: case Kind::ipv6: return "ip";
case Kind::asn: return "autnum";
default: return std::nullopt;
}
}
static std::string url(std::string base, const Classified& c) { // one slash before the object path
while (!base.empty() && base.back() == '/') base.pop_back();
return path_of(c.kind) ? base + "/" + *path_of(c.kind) + "/" + c.value : "";
}
static bool asn_range(const std::string& key, long a) { // asn.json keys: "lo-hi" or bare
auto dash = key.find('-');
return dash == std::string::npos ? std::stol(key) == a
: a >= std::stol(key) && a <= std::stol(key.substr(dash + 1));
}
// Bootstrap (dns.json / asn.json): exact TLD match, or "lo-hi" ASN ranges.
static std::string resolve(const std::vector<std::pair<std::vector<std::string>, std::string>>& bs,
const std::string& tld, long asn) {
for (auto& [keys, u] : bs) for (auto& k : keys) {
if (!tld.empty() && lc(k) == tld) return u;
if (tld.empty() && asn_range(k, asn)) return u;
}
return "";
}
int main() {
std::vector<std::pair<std::vector<std::string>, std::string>> dns = { { { "org", "com" }, "https://rdap.org" } },
asns = { { { "64496-64511" }, "https://rdap.arin.net/registry" } };
for (std::string q : { "https://CosmoLabs.org/tools#whois", "AS3356", "192.0.2.1", "not_a_domain" }) {
Classified c = classify(q);
std::string base = c.kind == Kind::domain ? resolve(dns, c.tld, 0)
: c.kind == Kind::asn ? resolve(asns, "", std::stol(c.value)) : "";
auto p = path_of(c.kind);
std::cout << q << " -> " << (p ? *p : "invalid") << ' ' << (base.empty() ? "" : url(base, c)) << '\n';
}
}
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 →