Stacks WHOIS
Domain WHOIS lookup with SOCKS proxy support, batch processing, TLD server discovery, and response parsing. Includes a native SOCKS4/SOCKS5 client implementation.
Key Paths
- Core package:
storage/framework/core/whois/src/
Source Files
whois/src/
├── index.ts # whois(), lookup(), batchWhois(), tcpWhois(), WhoIsParser, exports
├── types.ts # WhoIsResponse, WhoIsOptions, ProxyData, ProxyType
├── constants.ts # IANA_CHK_URL, PARAMETERS, SERVERS
├── utils.ts # shallowCopy helper
└── socks.ts # Native SocksClient implementation (SOCKS4/SOCKS5)
Main Functions
whois() -- Primary Lookup
async function whois(
domain: string,
parse?: boolean, // default: false (but still parses with default fields)
options?: WhoIsOptions | null,
): Promise<WhoIsResponse>
When parse is false (default), it still calls WhoIsParser.parseData(rawData, null) which parses with the default field set. When parse is true and options.parseData is provided, it uses a shallow copy of that object as the parse template.
Flow:
- Determine TLD from domain (or use
options.tld) - Find WHOIS server: check
SERVERSmap first, then query IANA viafindWhoIsServer() - Get query parameters via
getParameters()for the server - Execute TCP query via
tcpWhois() - Parse response via
WhoIsParser.parseData() - Returns
{ _raw: string, parsedData: any | null }
lookup() -- Parsed Lookup Shortcut
function lookup(domain: string, options?: WhoIsOptions | null): Promise<WhoIsResponse>
Calls whois(domain, true, options) -- always parses the response.
batchWhois() -- Batch Lookups
async function batchWhois(
domains: string[],
parallel?: boolean, // default: false
threads?: number, // default: 1 (batch size for parallel mode)
parse?: boolean, // default: false
options?: WhoIsOptions | null,
): Promise<WhoIsResponse[]>
- Sequential mode (
parallel=false): processes domains one at a time - Parallel mode (
parallel=true): processes in batches ofthreadssize usingPromise.all - If
threads > domains.length, clamps todomains.length - Note: In parallel mode, only the last batch's results are returned (
responseis reassigned, not concatenated)
tcpWhois() -- Raw TCP Query
async function tcpWhois(
domain: string,
queryOptions: string, // server-specific prefix (e.g., '-T dn,ace')
server: string,
port: number, // default 43
encoding: string, // default 'utf-8'
proxy: ProxyData | null,
): Promise<string>
- Without proxy: creates
Net.Socket, connects directly to WHOIS server on specified port - With proxy: uses
SocksClient.createConnection()to tunnel through SOCKS proxy - Sends query as
"<queryOptions> <domain>\r\n"or"<domain>\r\n"if no queryOptions - Uses
TextDecoderwith specified encoding for response
Server Discovery
findWhoIsServer() -- Remote IANA Lookup
async function findWhoIsServer(tld: string): Promise<string>
Fetches https://www.iana.org/whois?q=<tld> and extracts server from whois:\s+(\S+) regex match. Returns empty string on failure.
getWhoIsServer() -- Local Lookup
function getWhoIsServer(tld: keyof typeof SERVERS): string | undefined
Looks up TLD in the built-in SERVERS map. Special case: 'com' always returns 'whois.verisign-grs.com'.
getTLD() -- Extract TLD
function getTLD(domain: string): keyof typeof SERVERS
Splits domain by . and checks from longest suffix first against the SERVERS map. For example, sub.example.co.uk checks co.uk first (which exists in SERVERS), not just uk. Falls back to the last part if no match found.
getParameters() -- Server Query Parameters
function getParameters(server: string): string | undefined
Returns server-specific query format from the PARAMETERS map.
WhoIsParser
class WhoIsParser {
// Parse raw WHOIS text with custom or default field template
static parseData(rawData: string, outputData: any | null): any
// Internal iterative parser (private)
private static iterParse(rawData: string, outputData: any): any
}
When outputData is null, parseData() uses this default template:
{
'Domain Name': '',
'Creation Date': '',
'Updated Date': '',
'Registry Expiry Date': '',
'Domain Status': [], // array collects multiple values
'Registrar': '',
}
The parser iterates character by character through raw WHOIS text:
- Keys are identified when a line matches a key in
outputData - Values follow after
": "separator - Array-typed fields (like
Domain Status) accumulate multiple values - Parsing stops at
"Record maintained by"or">>>"markers
Custom parse templates: pass an object with the field names you want extracted. Use empty string '' for single values, use [] for fields with multiple values.
const result = await whois('example.com', true, {
parseData: {
'Domain Name': '',
'Domain Status': [], // collects ALL status values
'Registrar': '',
'Name Server': [], // collects ALL name servers
}
})
Types
WhoIsResponse
interface WhoIsResponse {
_raw: string // raw text from WHOIS server
parsedData: any | null // parsed key-value pairs (null on error)
}
WhoIsOptions
interface WhoIsOptions {
tld?: string | null // override TLD detection
encoding?: string | null // character encoding (default: 'utf-8')
proxy?: ProxyData | null // SOCKS proxy configuration
server?: string | null // override WHOIS server
serverPort?: number | null // override port (default: 43)
parseData?: object | null // custom parse template (keys to extract)
}
ProxyData
interface ProxyData {
ip: string
port: number
username?: string | null
password?: string | null
type: ProxyType
}
ProxyType
enum ProxyType {
SOCKS4 = 0,
SOCKS5 = 1,
}
Note: ProxyType.SOCKS4 = 0 and ProxyType.SOCKS5 = 1 -- these are mapped to SOCKS versions 4 and 5 internally when creating SocksClient options.
SOCKS Proxy Support
const result = await whois('example.com', false, {
proxy: {
ip: '127.0.0.1',
port: 1080,
type: ProxyType.SOCKS5,
username: 'user', // optional
password: 'pass', // optional
}
})
Built-in SocksClient (socks.ts)
Native SOCKS client implementation using node:net. Replaces the socks npm package.
class SocksClient {
static createConnection(
options: SocksClientOptions,
callback: (err: Error | null, info?: SocksClientEstablishedEvent) => void,
): void
}
interface SocksClientOptions {
proxy: SocksProxy
command: 'connect' | 'bind' | 'associate'
destination: SocksDestination
}
interface SocksProxy {
host: string
port: number
type: 4 | 5 // SOCKS version
userId?: string
password?: string
}
interface SocksDestination {
host: string
port: number
}
interface SocksClientEstablishedEvent {
socket: Net.Socket
remoteHost?: string
remotePort?: number
}
SOCKS4 Implementation
- Supports SOCKS4 and SOCKS4a (domain name support via special IP marker
0.0.0.1) - Request format: VN(1) + CD(1) + DSTPORT(2) + DSTIP(4) + USERID(var) + NULL(1)
- Success response: status byte
0x5A(90)
SOCKS5 Implementation
- Three-step handshake: greeting -> authentication (optional) -> connect request
- Supports no-auth (
0x00) and username/password auth (0x02) - Supports IPv4 (
0x01), IPv6 (0x04), and domain name (0x03) address types - Detailed error messages for all SOCKS5 status codes (general failure, not allowed, network unreachable, host unreachable, connection refused, TTL expired, command not supported, address type not supported)
Constants
IANA_CHK_URL
const IANA_CHK_URL = 'https://www.iana.org/whois?q='
PARAMETERS -- Server-Specific Query Formats
const PARAMETERS: Record<string, string> = {
'whois.denic.de': '-T dn,ace',
'whois.nic.fr': '-V Md5.2',
}
SERVERS -- TLD to WHOIS Server Mapping
Comprehensive mapping of hundreds of TLDs to their WHOIS servers. Examples:
'com'->'whois.verisign-grs.com''net'->'whois.verisign-grs.com''org'->'whois.pir.org''io'->'whois.nic.io''co.uk'-> compound TLD support viagetTLD()longest-match logic- Country code TLDs, new gTLDs, compound TLDs (e.g.,
br.com,co.ca)
Utility: shallowCopy (utils.ts)
function shallowCopy<T>(obj: T): T
Recursively copies objects and arrays. Used to clone parseData templates before filling them with parsed values, so the original template is not mutated.
Exports from index.ts
Functions: whois, lookup, batchWhois, tcpWhois, findWhoIsServer, getWhoIsServer, getTLD, getParameters
Classes: WhoIsParser, SocksClient
Constants: IANA_CHK_URL, PARAMETERS, SERVERS
Types: WhoIsResponse, WhoIsOptions, ProxyData, ProxyType, SocksClientOptions
Dependencies
node:net-- TCP socket connections (direct WHOIS queries and SOCKS client)@stacksjs/logging--logfor debug logging during lookups
Gotchas
whois()withparse=falsestill parses with default fields -- theparseflag controls whether customparseDatais usedbatchWhois()in parallel mode has a bug: it reassignsresponseeach batch instead of concatenating, so only the last batch's results are returnedProxyTypeenum values are0and1, not4and5-- they are mapped to SOCKS versions internallygetWhoIsServer()returnsundefinedfor unknown TLDs, not an empty stringfindWhoIsServer()returns empty string''on failure (notundefined)getTLD()checks compound TLDs (likeco.uk) before simple ones via longest-suffix matching- The parser stops at
"Record maintained by"or">>>"markers in the raw response - WHOIS servers are rate-limited -- batch queries should use small thread counts
shallowCopy()is a recursive deep copy despite its name -- it clones nested objects and arrays- TCP connections use
node:netSocket, notfetch-- WHOIS is a raw TCP protocol on port 43 - The SOCKS client uses callback-style API, not promises
- Query options are prepended to the domain in the TCP query (e.g.,
"-T dn,ace example.de\r\n")