rdapper 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.d.ts DELETED
@@ -1,83 +0,0 @@
1
- export type LookupSource = "rdap" | "whois";
2
- export interface RegistrarInfo {
3
- name?: string;
4
- ianaId?: string;
5
- url?: string;
6
- email?: string;
7
- phone?: string;
8
- }
9
- export interface Contact {
10
- type: "registrant" | "admin" | "tech" | "billing" | "abuse" | "registrar" | "reseller" | "unknown";
11
- name?: string;
12
- organization?: string;
13
- email?: string | string[];
14
- phone?: string | string[];
15
- fax?: string | string[];
16
- street?: string[];
17
- city?: string;
18
- state?: string;
19
- postalCode?: string;
20
- country?: string;
21
- countryCode?: string;
22
- }
23
- export interface Nameserver {
24
- host: string;
25
- ipv4?: string[];
26
- ipv6?: string[];
27
- }
28
- export interface StatusEvent {
29
- status: string;
30
- description?: string;
31
- raw?: string;
32
- }
33
- export interface DomainRecord {
34
- domain: string;
35
- tld: string;
36
- isRegistered: boolean;
37
- isIDN?: boolean;
38
- unicodeName?: string;
39
- punycodeName?: string;
40
- registry?: string;
41
- registrar?: RegistrarInfo;
42
- reseller?: string;
43
- statuses?: StatusEvent[];
44
- creationDate?: string;
45
- updatedDate?: string;
46
- expirationDate?: string;
47
- deletionDate?: string;
48
- transferLock?: boolean;
49
- dnssec?: {
50
- enabled: boolean;
51
- dsRecords?: Array<{
52
- keyTag?: number;
53
- algorithm?: number;
54
- digestType?: number;
55
- digest?: string;
56
- }>;
57
- };
58
- nameservers?: Nameserver[];
59
- contacts?: Contact[];
60
- whoisServer?: string;
61
- rdapServers?: string[];
62
- rawRdap?: unknown;
63
- rawWhois?: string;
64
- source: LookupSource;
65
- fetchedAt: string;
66
- warnings?: string[];
67
- }
68
- export interface LookupOptions {
69
- timeoutMs?: number;
70
- rdapOnly?: boolean;
71
- whoisOnly?: boolean;
72
- followWhoisReferral?: boolean;
73
- customBootstrapUrl?: string;
74
- whoisHints?: Record<string, string>;
75
- includeRaw?: boolean;
76
- signal?: AbortSignal;
77
- }
78
- export interface LookupResult {
79
- ok: boolean;
80
- record?: DomainRecord;
81
- error?: string;
82
- }
83
- export type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
package/dist/types.js DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,10 +0,0 @@
1
- import type { LookupOptions } from "../types.js";
2
- export interface WhoisQueryResult {
3
- serverQueried: string;
4
- text: string;
5
- }
6
- /**
7
- * Perform a WHOIS query against an RFC 3912 server over TCP 43.
8
- * Returns the raw text and the server used.
9
- */
10
- export declare function whoisQuery(server: string, query: string, options?: LookupOptions): Promise<WhoisQueryResult>;
@@ -1,46 +0,0 @@
1
- import { createConnection } from "node:net";
2
- import { withTimeout } from "../lib/async.js";
3
- import { DEFAULT_TIMEOUT_MS } from "../lib/constants.js";
4
- /**
5
- * Perform a WHOIS query against an RFC 3912 server over TCP 43.
6
- * Returns the raw text and the server used.
7
- */
8
- export async function whoisQuery(server, query, options) {
9
- const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
10
- const port = 43;
11
- const host = server.replace(/^whois:\/\//i, "");
12
- const text = await withTimeout(queryTcp(host, port, query, options), timeoutMs, "WHOIS timeout");
13
- return { serverQueried: server, text };
14
- }
15
- // Low-level WHOIS TCP client. Some registries require CRLF after the domain query.
16
- function queryTcp(host, port, query, options) {
17
- return new Promise((resolve, reject) => {
18
- const socket = createConnection({ host, port });
19
- let data = "";
20
- let done = false;
21
- const cleanup = () => {
22
- if (done)
23
- return;
24
- done = true;
25
- socket.destroy();
26
- };
27
- socket.setTimeout((options?.timeoutMs ?? DEFAULT_TIMEOUT_MS) - 1000, () => {
28
- cleanup();
29
- reject(new Error("WHOIS socket timeout"));
30
- });
31
- socket.on("error", (err) => {
32
- cleanup();
33
- reject(err);
34
- });
35
- socket.on("data", (chunk) => {
36
- data += chunk.toString("utf8");
37
- });
38
- socket.on("end", () => {
39
- cleanup();
40
- resolve(data);
41
- });
42
- socket.on("connect", () => {
43
- socket.write(`${query}\r\n`);
44
- });
45
- });
46
- }
@@ -1,24 +0,0 @@
1
- import type { LookupOptions } from "../types.js";
2
- /**
3
- * Parse the IANA WHOIS response for a TLD and extract the WHOIS server
4
- * without crossing line boundaries. Some TLDs (e.g. .np) leave the field
5
- * blank, in which case this returns undefined.
6
- */
7
- export declare function parseIanaWhoisServer(text: string): string | undefined;
8
- /**
9
- * Parse a likely registration information URL from an IANA WHOIS response.
10
- * Looks at lines like:
11
- * remarks: Registration information: http://example.tld
12
- * url: https://registry.example
13
- */
14
- export declare function parseIanaRegistrationInfoUrl(text: string): string | undefined;
15
- /** Fetch raw IANA WHOIS text for a TLD (best-effort). */
16
- export declare function getIanaWhoisTextForTld(tld: string, options?: LookupOptions): Promise<string | undefined>;
17
- /**
18
- * Best-effort discovery of the authoritative WHOIS server for a TLD via IANA root DB.
19
- */
20
- export declare function ianaWhoisServerForTld(tld: string, options?: LookupOptions): Promise<string | undefined>;
21
- /**
22
- * Extract registrar referral WHOIS server from a WHOIS response, if present.
23
- */
24
- export declare function extractWhoisReferral(text: string): string | undefined;
@@ -1,99 +0,0 @@
1
- import { whoisQuery } from "./client.js";
2
- import { WHOIS_TLD_EXCEPTIONS } from "./servers.js";
3
- /**
4
- * Parse the IANA WHOIS response for a TLD and extract the WHOIS server
5
- * without crossing line boundaries. Some TLDs (e.g. .np) leave the field
6
- * blank, in which case this returns undefined.
7
- */
8
- export function parseIanaWhoisServer(text) {
9
- // Search lines in priority order: whois, refer, whois server
10
- const fields = ["whois", "refer", "whois server"];
11
- const lines = String(text).split(/\r?\n/);
12
- for (const field of fields) {
13
- for (const raw of lines) {
14
- const line = raw.trimEnd();
15
- // Match beginning of line, allowing leading spaces, case-insensitive
16
- const re = new RegExp(`^\\s*${field}\\s*:\\s*(.*?)$`, "i");
17
- const m = line.match(re);
18
- if (m) {
19
- const value = (m[1] || "").trim();
20
- if (value)
21
- return value;
22
- }
23
- }
24
- }
25
- return undefined;
26
- }
27
- /**
28
- * Parse a likely registration information URL from an IANA WHOIS response.
29
- * Looks at lines like:
30
- * remarks: Registration information: http://example.tld
31
- * url: https://registry.example
32
- */
33
- export function parseIanaRegistrationInfoUrl(text) {
34
- const lines = String(text).split(/\r?\n/);
35
- for (const raw of lines) {
36
- const line = raw.trim();
37
- if (!/^\s*(remarks|url|website)\s*:/i.test(line))
38
- continue;
39
- const urlMatch = line.match(/https?:\/\/\S+/i);
40
- if (urlMatch?.[0])
41
- return urlMatch[0];
42
- }
43
- return undefined;
44
- }
45
- /** Fetch raw IANA WHOIS text for a TLD (best-effort). */
46
- export async function getIanaWhoisTextForTld(tld, options) {
47
- try {
48
- const res = await whoisQuery("whois.iana.org", tld.toLowerCase(), options);
49
- return res.text;
50
- }
51
- catch {
52
- return undefined;
53
- }
54
- }
55
- /**
56
- * Best-effort discovery of the authoritative WHOIS server for a TLD via IANA root DB.
57
- */
58
- export async function ianaWhoisServerForTld(tld, options) {
59
- const key = tld.toLowerCase();
60
- // 1) Explicit hint override
61
- const hint = options?.whoisHints?.[key];
62
- if (hint)
63
- return normalizeServer(hint);
64
- // 2) IANA WHOIS authoritative discovery over TCP 43
65
- try {
66
- const res = await whoisQuery("whois.iana.org", key, options);
67
- const txt = res.text;
68
- const server = parseIanaWhoisServer(txt);
69
- if (server)
70
- return normalizeServer(server);
71
- }
72
- catch {
73
- // fallthrough to exceptions/guess
74
- }
75
- // 3) Curated exceptions
76
- const exception = WHOIS_TLD_EXCEPTIONS[key];
77
- if (exception)
78
- return normalizeServer(exception);
79
- return undefined;
80
- }
81
- /**
82
- * Extract registrar referral WHOIS server from a WHOIS response, if present.
83
- */
84
- export function extractWhoisReferral(text) {
85
- const patterns = [
86
- /^Registrar WHOIS Server:\s*(.+)$/im,
87
- /^Whois Server:\s*(.+)$/im,
88
- /^ReferralServer:\s*whois:\/\/(.+)$/im,
89
- ];
90
- for (const re of patterns) {
91
- const m = text.match(re);
92
- if (m?.[1])
93
- return m[1].trim();
94
- }
95
- return undefined;
96
- }
97
- function normalizeServer(server) {
98
- return server.replace(/^whois:\/\//i, "").replace(/\/$/, "");
99
- }
@@ -1,6 +0,0 @@
1
- import type { DomainRecord } from "../types.js";
2
- /**
3
- * Convert raw WHOIS text into our normalized DomainRecord.
4
- * Heuristics cover many gTLD and ccTLD formats; exact fields vary per registry.
5
- */
6
- export declare function normalizeWhois(domain: string, tld: string, whoisText: string, whoisServer: string | undefined, fetchedAtISO: string, includeRaw?: boolean): DomainRecord;
@@ -1,214 +0,0 @@
1
- import { toISO } from "../lib/dates.js";
2
- import { isWhoisAvailable } from "../lib/domain.js";
3
- import { parseKeyValueLines, uniq } from "../lib/text.js";
4
- /**
5
- * Convert raw WHOIS text into our normalized DomainRecord.
6
- * Heuristics cover many gTLD and ccTLD formats; exact fields vary per registry.
7
- */
8
- export function normalizeWhois(domain, tld, whoisText, whoisServer, fetchedAtISO, includeRaw = false) {
9
- const map = parseKeyValueLines(whoisText);
10
- // Date extraction across common synonyms
11
- const creationDate = anyValue(map, [
12
- "creation date",
13
- "created on",
14
- "registered on",
15
- "domain registration date",
16
- "domain create date",
17
- "created",
18
- "registered",
19
- ]);
20
- const updatedDate = anyValue(map, [
21
- "updated date",
22
- "last updated",
23
- "last modified",
24
- "modified",
25
- ]);
26
- const expirationDate = anyValue(map, [
27
- "registry expiry date",
28
- "expiry date",
29
- "expiration date",
30
- "paid-till",
31
- "expires on",
32
- "renewal date",
33
- ]);
34
- // Registrar info (thin registries like .com/.net require referral follow for full data)
35
- const registrar = (() => {
36
- const name = anyValue(map, [
37
- "registrar",
38
- "sponsoring registrar",
39
- "registrar name",
40
- ]);
41
- const ianaId = anyValue(map, ["registrar iana id", "iana id"]);
42
- const url = anyValue(map, [
43
- "registrar url",
44
- "url of the registrar",
45
- "referrer",
46
- ]);
47
- const abuseEmail = anyValue(map, [
48
- "registrar abuse contact email",
49
- "abuse contact email",
50
- ]);
51
- const abusePhone = anyValue(map, [
52
- "registrar abuse contact phone",
53
- "abuse contact phone",
54
- ]);
55
- if (!name && !ianaId && !url && !abuseEmail && !abusePhone)
56
- return undefined;
57
- return {
58
- name: name || undefined,
59
- ianaId: ianaId || undefined,
60
- url: url || undefined,
61
- email: abuseEmail || undefined,
62
- phone: abusePhone || undefined,
63
- };
64
- })();
65
- // Statuses: multiple entries are expected; keep raw
66
- const statusLines = map["domain status"] || map.status || [];
67
- const statuses = statusLines.length
68
- ? statusLines.map((line) => ({ status: line.split(/\s+/)[0], raw: line }))
69
- : undefined;
70
- // Nameservers: also appear as "nserver" on some ccTLDs (.de, .ru) and as "name server"
71
- const nsLines = [
72
- ...(map["name server"] || []),
73
- ...(map.nameserver || []),
74
- ...(map["name servers"] || []),
75
- ...(map.nserver || []),
76
- ];
77
- const nameservers = nsLines.length
78
- ? uniq(nsLines
79
- .map((line) => line.trim())
80
- .filter(Boolean)
81
- .map((line) => {
82
- // Common formats: "ns1.example.com" or "ns1.example.com 192.0.2.1" or "ns1.example.com 2001:db8::1"
83
- const parts = line.split(/\s+/);
84
- const host = parts.shift()?.toLowerCase() || "";
85
- const ipv4 = [];
86
- const ipv6 = [];
87
- for (const p of parts) {
88
- if (/^\d+\.\d+\.\d+\.\d+$/.test(p))
89
- ipv4.push(p);
90
- else if (/^[0-9a-f:]+$/i.test(p))
91
- ipv6.push(p);
92
- }
93
- if (!host)
94
- return undefined;
95
- const ns = { host };
96
- if (ipv4.length)
97
- ns.ipv4 = ipv4;
98
- if (ipv6.length)
99
- ns.ipv6 = ipv6;
100
- return ns;
101
- })
102
- .filter((x) => !!x))
103
- : undefined;
104
- // Contacts: best-effort parse common keys
105
- const contacts = collectContacts(map);
106
- const dnssecRaw = (map.dnssec?.[0] || "").toLowerCase();
107
- const dnssec = dnssecRaw
108
- ? { enabled: /signed|yes|true/.test(dnssecRaw) }
109
- : undefined;
110
- // Simple lock derivation from statuses
111
- const transferLock = !!statuses?.some((s) => /transferprohibited/i.test(s.status));
112
- const record = {
113
- domain,
114
- tld,
115
- isRegistered: !isWhoisAvailable(whoisText),
116
- isIDN: /(^|\.)xn--/i.test(domain),
117
- unicodeName: undefined,
118
- punycodeName: undefined,
119
- registry: undefined,
120
- registrar,
121
- reseller: anyValue(map, ["reseller"]) || undefined,
122
- statuses,
123
- creationDate: toISO(creationDate || undefined),
124
- updatedDate: toISO(updatedDate || undefined),
125
- expirationDate: toISO(expirationDate || undefined),
126
- deletionDate: undefined,
127
- transferLock,
128
- dnssec,
129
- nameservers,
130
- contacts,
131
- whoisServer,
132
- rdapServers: undefined,
133
- rawRdap: undefined,
134
- rawWhois: includeRaw ? whoisText : undefined,
135
- source: "whois",
136
- fetchedAt: fetchedAtISO,
137
- warnings: undefined,
138
- };
139
- return record;
140
- }
141
- function anyValue(map, keys) {
142
- for (const k of keys) {
143
- const v = map[k];
144
- if (v?.length)
145
- return v[0];
146
- }
147
- return undefined;
148
- }
149
- function collectContacts(map) {
150
- const roles = [
151
- { role: "registrant", prefix: "registrant" },
152
- { role: "admin", prefix: "admin" },
153
- { role: "tech", prefix: "tech" },
154
- { role: "billing", prefix: "billing" },
155
- { role: "abuse", prefix: "abuse" },
156
- ];
157
- const contacts = [];
158
- for (const r of roles) {
159
- const name = anyValue(map, [
160
- `${r.prefix} name`,
161
- `${r.prefix} contact name`,
162
- `${r.prefix}`,
163
- ]);
164
- const org = anyValue(map, [`${r.prefix} organization`, `${r.prefix} org`]);
165
- const email = anyValue(map, [
166
- `${r.prefix} email`,
167
- `${r.prefix} contact email`,
168
- `${r.prefix} e-mail`,
169
- ]);
170
- const phone = anyValue(map, [
171
- `${r.prefix} phone`,
172
- `${r.prefix} contact phone`,
173
- `${r.prefix} telephone`,
174
- ]);
175
- const fax = anyValue(map, [`${r.prefix} fax`, `${r.prefix} facsimile`]);
176
- const street = multi(map, [`${r.prefix} street`, `${r.prefix} address`]);
177
- const city = anyValue(map, [`${r.prefix} city`]);
178
- const state = anyValue(map, [
179
- `${r.prefix} state`,
180
- `${r.prefix} province`,
181
- `${r.prefix} state/province`,
182
- ]);
183
- const postalCode = anyValue(map, [
184
- `${r.prefix} postal code`,
185
- `${r.prefix} postcode`,
186
- `${r.prefix} zip`,
187
- ]);
188
- const country = anyValue(map, [`${r.prefix} country`]);
189
- if (name || org || email || phone || street?.length) {
190
- contacts.push({
191
- type: r.role,
192
- name: name || undefined,
193
- organization: org || undefined,
194
- email: email || undefined,
195
- phone: phone || undefined,
196
- fax: fax || undefined,
197
- street: street,
198
- city: city || undefined,
199
- state: state || undefined,
200
- postalCode: postalCode || undefined,
201
- country: country || undefined,
202
- });
203
- }
204
- }
205
- return contacts.length ? contacts : undefined;
206
- }
207
- function multi(map, keys) {
208
- for (const k of keys) {
209
- const v = map[k];
210
- if (v?.length)
211
- return v;
212
- }
213
- return undefined;
214
- }
@@ -1 +0,0 @@
1
- export declare const WHOIS_TLD_EXCEPTIONS: Record<string, string>;
@@ -1,64 +0,0 @@
1
- // Curated authoritative WHOIS servers by TLD (exceptions to default/referral logic)
2
- // Source of truth checked against IANA delegation records; prefer RDAP first.
3
- export const WHOIS_TLD_EXCEPTIONS = {
4
- // gTLDs (port-43 still available at registry)
5
- com: "whois.verisign-grs.com",
6
- net: "whois.verisign-grs.com",
7
- org: "whois.publicinterestregistry.org", // PIR
8
- biz: "whois.nic.biz",
9
- name: "whois.nic.name",
10
- edu: "whois.educause.edu",
11
- gov: "whois.nic.gov", // was whois.dotgov.gov
12
- // ccTLDs & other TLDs with working port-43 WHOIS
13
- de: "whois.denic.de",
14
- jp: "whois.jprs.jp",
15
- fr: "whois.nic.fr",
16
- it: "whois.nic.it",
17
- pl: "whois.dns.pl",
18
- nl: "whois.domain-registry.nl",
19
- be: "whois.dns.be",
20
- se: "whois.iis.se",
21
- no: "whois.norid.no",
22
- fi: "whois.fi",
23
- cz: "whois.nic.cz",
24
- es: "whois.nic.es",
25
- br: "whois.registro.br",
26
- ca: "whois.cira.ca",
27
- dk: "whois.punktum.dk", // was whois.dk-hostmaster.dk
28
- hk: "whois.hkirc.hk",
29
- sg: "whois.sgnic.sg",
30
- in: "whois.nixiregistry.in", // was whois.registry.in
31
- nz: "whois.irs.net.nz", // was whois.srs.net.nz
32
- ch: "whois.nic.ch",
33
- li: "whois.nic.li",
34
- io: "whois.nic.io",
35
- ai: "whois.nic.ai",
36
- ru: "whois.tcinet.ru",
37
- su: "whois.tcinet.ru",
38
- us: "whois.nic.us",
39
- co: "whois.nic.co",
40
- me: "whois.nic.me",
41
- tv: "whois.nic.tv",
42
- cc: "ccwhois.verisign-grs.com",
43
- eu: "whois.eu",
44
- au: "whois.auda.org.au",
45
- kr: "whois.kr",
46
- tw: "whois.twnic.net.tw",
47
- uk: "whois.nic.uk",
48
- nu: "whois.iis.nu",
49
- "xn--p1ai": "whois.tcinet.ru", // .рф
50
- // CentralNic-operated public SLD zones (still WHOIS @ centralnic)
51
- "uk.com": "whois.centralnic.com",
52
- "uk.net": "whois.centralnic.com",
53
- "gb.com": "whois.centralnic.com",
54
- "gb.net": "whois.centralnic.com",
55
- "eu.com": "whois.centralnic.com",
56
- "us.com": "whois.centralnic.com",
57
- "se.com": "whois.centralnic.com",
58
- "de.com": "whois.centralnic.com",
59
- "br.com": "whois.centralnic.com",
60
- "ru.com": "whois.centralnic.com",
61
- "cn.com": "whois.centralnic.com",
62
- "sa.com": "whois.centralnic.com",
63
- "co.com": "whois.centralnic.com",
64
- };