UNPKG

whoiser

Version:

Whois info for domains, TLDs, IPs, and ASN

289 lines (288 loc) 10.4 kB
import net from 'node:net'; import { toASCII, toUnicode } from 'punycode-esm'; import { parseSimpleWhois, parseDomainWhois, whoisDataToGroups } from "./parsers.js"; import { validatedTld } from "./utils.js"; // Cache WHOIS servers // Basic list of servers, more will be auto-discovered let cacheTldWhoisServer = { com: 'whois.verisign-grs.com', net: 'whois.verisign-grs.com', org: 'whois.pir.org', // ccTLDs ai: 'whois.nic.ai', au: 'whois.auda.org.au', bz: 'whois.identity.digital', co: 'whois.registry.co', ca: 'whois.cira.ca', do: 'whois.nic.do', eu: 'whois.eu', gi: 'whois.identity.digital', gl: 'whois.nic.gl', in: 'whois.registry.in', io: 'whois.nic.io', it: 'whois.nic.it', lc: 'whois.identity.digital', me: 'whois.nic.me', ro: 'whois.rotld.ro', rs: 'whois.rnids.rs', so: 'whois.nic.so', tr: 'whois.nic.tr', us: 'whois.nic.us', vc: 'whois.identity.digital', ws: 'whois.website.ws', agency: 'whois.nic.agency', app: 'whois.nic.google', biz: 'whois.nic.biz', country: 'whois.uniregistry.net', // hardcoded because `whois.iana.org` sometimes returns 'whois.uniregistry.net' or 'whois.nic.country' dev: 'whois.nic.google', house: 'whois.nic.house', health: 'whois.nic.health', info: 'whois.nic.info', link: 'whois.uniregistry.net', live: 'whois.nic.live', nyc: 'whois.nic.nyc', one: 'whois.nic.one', online: 'whois.nic.online', shop: 'whois.nic.shop', site: 'whois.nic.site', xyz: 'whois.nic.xyz', }; // misspelled whois servers.. const misspelledWhoisServer = { //'whois.google.com': 'whois.nic.google', // Why was this added?? 'www.gandi.net/whois': 'whois.gandi.net', 'who.godaddy.com/': 'whois.godaddy.com', 'whois.godaddy.com/': 'whois.godaddy.com', 'www.nic.ru/whois/en/': 'whois.nic.ru', 'www.whois.corporatedomains.com': 'whois.corporatedomains.com', 'www.safenames.net/DomainNames/WhoisSearch.aspx': 'whois.safenames.net', 'WWW.GNAME.COM/WHOIS': 'whois.gname.com', }; /** * Query a WHOIS server and return the result. * @param host WHOIS server hostname * @param query Query string * @param timeout Timeout duration in milliseconds * @returns Result of the WHOIS query */ export function whoisQuery(host, query, timeout = 5000) { return new Promise((resolve, reject) => { let data = ''; const socket = net.connect({ host, port: 43, family: 4 }, () => socket.write(query + '\r\n')); socket.setTimeout(timeout); socket.on('data', (chunk) => (data += chunk)); socket.on('close', () => resolve(data)); socket.on('timeout', () => socket.destroy(new Error('Timeout'))); socket.on('error', reject); }); } /** * TLD WHOIS data, from the [IANA WHOIS](https://www.iana.org/whois) server. * * @param tld TLD/SLD to query. Example: 'com', '.co.uk' * @param timeout Timeout for WHOIS query in milliseconds * @returns Normalized WHOIS data * @throws Error if TLD is invalid or not found */ export async function whoisTld(tld, timeout = 1000) { tld = validatedTld(tld); const whoisData = await whoisQuery('whois.iana.org', tld, timeout); const { comments, groups } = whoisDataToGroups(whoisData); const groupWithDomain = groups.find((group) => Object.keys(group).includes('domain')); if (!groupWithDomain) { throw new Error(`TLD "${tld}" not found`); } const tldResponse = { tld: String(groupWithDomain['domain']), organisation: undefined, contacts: [], nserver: [], 'ds-rdata': undefined, whois: undefined, status: 'ACTIVE', remarks: '', created: '', changed: '', source: '', __comments: comments, __raw: whoisData, }; groups.forEach((group) => { if (Object.keys(group).at(0) === 'organisation') { tldResponse.organisation = group; } else if (Object.keys(group).at(0) === 'contact') { tldResponse.contacts.push(group); } else { for (const key in group) { const value = group[key]; if (value) { if (key === 'status') { tldResponse.status = value; } else if (key === 'remarks') { tldResponse.remarks = value; } else if (key === 'created') { tldResponse.created = value; } else if (key === 'changed') { tldResponse.changed = value; } else if (key === 'source') { tldResponse.source = value; } else if (key === 'whois') { tldResponse.whois = value; } else if (key === 'nserver') { tldResponse.nserver = value.split('\n').map((ns) => ns.trim()); } else if (key === 'ds-rdata') { tldResponse['ds-rdata'] = value; } } } } }); return tldResponse; } /** * Get WHOIS data for a domain name. * @param domain Domain name to query. Example: 'example.com' * @param options Options for querying WHOIS * @returns Object containing WHOIS results */ export async function whoisDomain(domain, options) { domain = toASCII(domain); const domainTld = domain.split('.').at(-1); let results = {}; // set WHOIS server for TLD let host = options?.host || cacheTldWhoisServer[domainTld]; // find WHOIS server for TLD if (!host) { const tld = await whoisTld(domainTld); if (!tld.whois) { throw new Error(`TLD for "${domain}" not supported`); } host = tld.whois; cacheTldWhoisServer[domainTld] = tld.whois; } let follow = options?.follow || 1; const queryFn = options?.whoisQuery || whoisQuery; // query WHOIS servers for data while (host && follow) { let query = domain; let result; let resultRaw; // hardcoded WHOIS queries.. if (host === 'whois.denic.de') { query = `-T dn ${toUnicode(domain)}`; } else if (host === 'whois.jprs.jp') { query = `${query}/e`; } try { resultRaw = await queryFn(host, query, options?.timeout); result = parseDomainWhois(domain, resultRaw, options?.ignorePrivacy ?? true); } catch (err) { result = { error: err.message }; } if (options?.raw) { result.__raw = resultRaw; } results[host] = result; follow--; // check for next WHOIS server let nextWhoisServer = result['Registrar WHOIS Server'] || result['Registry WHOIS Server'] || result['ReferralServer'] || result['Registrar Whois'] || result['Whois Server'] || result['WHOIS Server'] || false; // fill in WHOIS servers when missing if (!nextWhoisServer && result['Registrar URL'] && result['Registrar URL'].includes('domains.google')) { nextWhoisServer = 'whois.google.com'; } if (nextWhoisServer) { // if found, remove protocol and path if (nextWhoisServer.includes('://')) { let parsedUrl = new URL(nextWhoisServer); //todo use parsedUrl.port, if defined nextWhoisServer = parsedUrl.hostname; } // check if found server is in misspelled list nextWhoisServer = misspelledWhoisServer[nextWhoisServer] || nextWhoisServer; // check if found server was queried already nextWhoisServer = !results[nextWhoisServer] ? nextWhoisServer : false; } host = nextWhoisServer; } return results; } async function findWhoisServerInIana(query) { let whoisResult = await whoisQuery('whois.iana.org', query, 1000); const { groups } = whoisDataToGroups(whoisResult); const groupWithWhois = groups.find((group) => Object.keys(group).includes('whois')); return groupWithWhois['whois']; } /** * IP WHOIS data, from the [IANA WHOIS](https://www.iana.org/whois) server. * * @param ip IP address to query. Example: '192.0.2.1' * @param options Options for WHOIS query * @returns Normalized WHOIS data * @throws Error if IP is invalid or not found */ export async function whoisIp(ip, options = {}) { if (!net.isIP(ip)) { throw new Error(`Invalid IP address "${ip}"`); } const host = options.host || (await findWhoisServerInIana(ip)); if (!host) { throw new Error(`No WHOIS server for "${ip}"`); } let modifiedQuery = ip; // hardcoded custom queries.. if (host === 'whois.arin.net') { modifiedQuery = `+ n ${ip}`; } const ipWhoisResult = await whoisQuery(host, modifiedQuery, options.timeout || 1000); return parseSimpleWhois(ipWhoisResult); } /** * ASN WHOIS data, from the [IANA WHOIS](https://www.iana.org/whois) server. * @param asn ASN number to query. Example: 12345 * @param options Options for WHOIS query * @returns Normalized WHOIS data * @throws Error if ASN is invalid or not found */ export async function whoisAsn(asn, options = {}) { if (asn < 0 || asn > 4294967295) { throw new Error(`Invalid ASN number "${asn}"`); } // find WHOIS server for ASN const host = options.host || (await findWhoisServerInIana(String(asn))); if (!host) { throw new Error(`No WHOIS server for "${asn}"`); } let modifiedQuery = String(asn); // hardcoded custom queries.. if (host === 'whois.arin.net') { modifiedQuery = `+ a ${asn}`; } const asnWhoisResult = await whoisQuery(host, modifiedQuery, options.timeout || 1000); return parseSimpleWhois(asnWhoisResult); } /** * Return the first WHOIS result from a list of WHOIS results returned by `whoisDomain()` * @param whoisResults * @returns First (single) WHOIS result */ export function firstResult(whoisResults) { const whoisServers = Object.keys(whoisResults); return whoisResults[whoisServers[0]]; }