@cleandns/whois-rdap 1.0.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.
@@ -0,0 +1,2 @@
1
+ import { WhoisOptions, WhoisResponse } from "../whois.js";
2
+ export declare function whois(domain: string, options?: WhoisOptions): Promise<WhoisResponse>;
package/dist/index.js ADDED
@@ -0,0 +1,178 @@
1
+ import { determinePort43Domain, port43 } from "./port43.js";
2
+ import { findInObject } from "./utils/findInObject.js";
3
+ import { fixArrays } from "./utils/fixArrays.js";
4
+ import { ianaIdToRegistrar } from "./utils/ianaIdToRegistrar.js";
5
+ import { tldToRdap } from "./utils/tldToRdap.js";
6
+ import { normalizeWhoisStatus } from "./whoisStatus.js";
7
+ const eventMap = new Map([
8
+ ["registration", "created"],
9
+ ["last changed", "updated"],
10
+ ["expiration", "expires"],
11
+ ]);
12
+ export async function whois(domain, options = { fetch: fetch }) {
13
+ var _a, _b, _c, _d, _e, _f;
14
+ const fetch = options.fetch;
15
+ let url = null;
16
+ [domain, url] = await tldToRdap(domain);
17
+ const response = {
18
+ found: false,
19
+ registrar: { id: 0, name: null },
20
+ reseller: null,
21
+ status: [],
22
+ nameservers: [],
23
+ ts: { created: null, updated: null, expires: null },
24
+ };
25
+ if (url === null) {
26
+ if (determinePort43Domain(domain)[2]) {
27
+ return port43(domain);
28
+ }
29
+ url = "https://rdap.org";
30
+ }
31
+ const thinRdap = `${url}/domain/${domain}`;
32
+ // console.log(`fetching thin RDAP: ${thinRdap}`);
33
+ let thinResponse = await fetch(thinRdap)
34
+ .then((r) => r.json())
35
+ .catch(() => null);
36
+ if (thinResponse && !thinResponse.errorCode) {
37
+ }
38
+ else {
39
+ return response;
40
+ }
41
+ if ((_a = thinResponse === null || thinResponse === void 0 ? void 0 : thinResponse.rdapConformance) === null || _a === void 0 ? void 0 : _a["0"]) {
42
+ thinResponse = fixArrays(thinResponse);
43
+ }
44
+ const selfRdap = (_b = thinResponse.links) === null || _b === void 0 ? void 0 : _b.find((link) => link.rel === "self");
45
+ const thickRdap = (_d = (_c = thinResponse.links) === null || _c === void 0 ? void 0 : _c.find((link) => link.href !== (selfRdap === null || selfRdap === void 0 ? void 0 : selfRdap.href) &&
46
+ link.rel === "related" &&
47
+ link.type === "application/rdap+json")) === null || _d === void 0 ? void 0 : _d.href.replace("/domain/domain/", "/domain/");
48
+ let thickResponse = null;
49
+ if (thickRdap) {
50
+ // console.log(`fetching thick RDAP: ${thickRdap}`);
51
+ thickResponse = await fetch(thickRdap)
52
+ .then((r) => r.json())
53
+ .catch(() => null);
54
+ if (thickResponse && !thickResponse.errorCode && !thickResponse.error) {
55
+ }
56
+ else {
57
+ thickResponse = null;
58
+ // console.warn(`thick RDAP failed for ${domain}`);
59
+ }
60
+ }
61
+ if ((_e = thickResponse === null || thickResponse === void 0 ? void 0 : thickResponse.rdapConformance) === null || _e === void 0 ? void 0 : _e["0"]) {
62
+ thickResponse = fixArrays(thickResponse);
63
+ }
64
+ const registrars = [];
65
+ const resellers = [];
66
+ async function extractRegistrarsAndResellers(response, url) {
67
+ var _a, _b, _c, _d, _e, _f, _g;
68
+ for (const ent of [
69
+ ...(response.entities || []),
70
+ response.entity ? { events: response.events, ...response.entity } : null,
71
+ ].filter(Boolean)) {
72
+ if (((_a = ent.roles) === null || _a === void 0 ? void 0 : _a.includes("registrar")) || ent.role === "registrar") {
73
+ const pubIds = [];
74
+ if (ent.publicIds) {
75
+ pubIds.push(...(Array.isArray(ent.publicIds)
76
+ ? ent.publicIds
77
+ : [[ent.publicIds]]));
78
+ }
79
+ if (ent.publicIDs) {
80
+ pubIds.push(...(Array.isArray(ent.publicIDs)
81
+ ? ent.publicIDs
82
+ : [[ent.publicIDs]]));
83
+ }
84
+ const reg = ((_b = pubIds.find((id) => id.type === "PANDI Registrar ID")) === null || _b === void 0 ? void 0 : _b.Identifier) ||
85
+ ((_c = pubIds.find((id) => id.type === "PANDI Registrar ID")) === null || _c === void 0 ? void 0 : _c.identifier) ||
86
+ ((_d = pubIds.find((id) => id.type === "IANA Registrar ID")) === null || _d === void 0 ? void 0 : _d.Identifier) ||
87
+ ((_e = pubIds.find((id) => id.type === "IANA Registrar ID")) === null || _e === void 0 ? void 0 : _e.identifier);
88
+ if (reg) {
89
+ const id = reg;
90
+ const name = parseInt(id) == id
91
+ ? (_f = (await ianaIdToRegistrar(parseInt(id)))) === null || _f === void 0 ? void 0 : _f.name
92
+ : findInObject(ent.vcardArray, (el) => Array.isArray(el) && (el[0] === "fn" || el[0] === "org"), (el) => el[3], reg);
93
+ const events = ent.events || response.events || ent.enents || response.enents;
94
+ registrars.push({ id, name, events });
95
+ }
96
+ }
97
+ if ((((_g = ent.roles) === null || _g === void 0 ? void 0 : _g.includes("reseller")) || ent.role === "reseller") &&
98
+ ent.vcardArray) {
99
+ // vcard objects can be unexpectedly and arbitrarily nested
100
+ const name = findInObject(ent.vcardArray, (el) => Array.isArray(el) && (el[0] === "fn" || el[0] === "org"), (el) => el[3], "");
101
+ resellers.push({ name });
102
+ }
103
+ }
104
+ }
105
+ if (thickResponse && !thickResponse.errorCode) {
106
+ await extractRegistrarsAndResellers(thickResponse, thickRdap);
107
+ }
108
+ if (thinResponse && !thinResponse.errorCode) {
109
+ await extractRegistrarsAndResellers(thinResponse, thinRdap);
110
+ }
111
+ response.found = true;
112
+ // registrar
113
+ const { events, ...registrar } = registrars.sort((a, b) => {
114
+ var _a, _b;
115
+ // console.warn({ a, b });
116
+ const aDate = ((_a = a.events.find((ev) => ev.eventAction === "registration")) === null || _a === void 0 ? void 0 : _a.eventDate) || 0;
117
+ const bDate = ((_b = b.events.find((ev) => ev.eventAction === "registration")) === null || _b === void 0 ? void 0 : _b.eventDate) || 0;
118
+ return new Date(bDate).valueOf() - new Date(aDate).valueOf();
119
+ })[0] || { id: 0, name: "" };
120
+ response.registrar = registrar;
121
+ // reseller
122
+ const reseller = ((_f = resellers[0]) === null || _f === void 0 ? void 0 : _f.name) || "";
123
+ response.reseller = reseller;
124
+ // status
125
+ response.status = findStatus((thickResponse === null || thickResponse === void 0 ? void 0 : thickResponse.status) || (thinResponse === null || thinResponse === void 0 ? void 0 : thinResponse.status) || [], domain);
126
+ // nameservers
127
+ response.nameservers = findNameservers((thickResponse === null || thickResponse === void 0 ? void 0 : thickResponse.nameservers) || (thinResponse === null || thinResponse === void 0 ? void 0 : thinResponse.nameservers) || []);
128
+ // ts
129
+ response.ts = findTimestamps((thickResponse === null || thickResponse === void 0 ? void 0 : thickResponse.events) || (thinResponse === null || thinResponse === void 0 ? void 0 : thinResponse.events) || []);
130
+ return response;
131
+ }
132
+ function findStatus(statuses, domain) {
133
+ // console.warn({ domain, statuses });
134
+ return (Array.isArray(statuses)
135
+ ? statuses
136
+ : statuses && typeof statuses === "object"
137
+ ? Object.keys(statuses)
138
+ : (statuses || "").trim().split(/\s*,\s*/)).map((status) => normalizeWhoisStatus(status));
139
+ }
140
+ function findNameservers(values) {
141
+ let nameservers = [];
142
+ if (Array.isArray(values)) {
143
+ nameservers = values;
144
+ }
145
+ else if (typeof values === "object") {
146
+ nameservers = Object.values(values);
147
+ }
148
+ return nameservers
149
+ .map((ns) => { var _a; return ns.ldhName || ns.ldnName || ((_a = ns.ipAddresses) === null || _a === void 0 ? void 0 : _a.v4); })
150
+ .flat()
151
+ .filter((ns) => ns)
152
+ .map((ns) => (ns.stringValue || ns).toLocaleLowerCase())
153
+ .sort();
154
+ }
155
+ function findTimestamps(values) {
156
+ const ts = {
157
+ created: null,
158
+ updated: null,
159
+ expires: null,
160
+ };
161
+ let events = [];
162
+ if (Array.isArray(values)) {
163
+ events = values;
164
+ }
165
+ else if (typeof values === "object") {
166
+ events = Object.values(values);
167
+ }
168
+ for (const [event, field] of eventMap) {
169
+ const date = events.find((ev) => ev.eventAction === event);
170
+ if (date === null || date === void 0 ? void 0 : date.eventDate) {
171
+ ts[field] = new Date(date.eventDate);
172
+ }
173
+ }
174
+ return ts;
175
+ }
176
+ // await whois(process.argv[2]).then((r) =>
177
+ // console.log(JSON.stringify(r, null, 2))
178
+ // );
@@ -0,0 +1,3 @@
1
+ import { WhoisResponse } from "../whois.js";
2
+ export declare function determinePort43Domain(actor: string): any[];
3
+ export declare function port43(actor: string): Promise<WhoisResponse>;
package/dist/port43.js ADDED
@@ -0,0 +1,121 @@
1
+ import { ParseResultType, parseDomain } from "parse-domain";
2
+ import { PromiseSocket } from "promise-socket";
3
+ import { Socket } from "net";
4
+ import { port43servers, port43parsers } from "./port43servers.js";
5
+ import { ianaToRegistrarCache } from "./utils/ianaIdToRegistrar.js";
6
+ import { normalizeWhoisStatus } from "./whoisStatus.js";
7
+ export function determinePort43Domain(actor) {
8
+ const parsed = parseDomain(actor);
9
+ if (parsed.type === ParseResultType.Listed) {
10
+ let tld = parsed.topLevelDomains.join(".");
11
+ if (port43servers[tld]) {
12
+ const domain = parsed.domain + "." + tld;
13
+ return [domain, tld, port43servers[tld]];
14
+ }
15
+ tld = parsed.icann.topLevelDomains.join(".");
16
+ if (port43servers[tld]) {
17
+ const domain = parsed.icann.domain + "." + tld;
18
+ return [domain, tld, port43servers[tld]];
19
+ }
20
+ }
21
+ return [actor, "", null];
22
+ }
23
+ export async function port43(actor) {
24
+ const [domain, tld, whoisServer] = determinePort43Domain(actor);
25
+ const opts = whoisServer;
26
+ const server = (opts === null || opts === void 0 ? void 0 : opts.host) || opts || null;
27
+ const query = (opts === null || opts === void 0 ? void 0 : opts.query)
28
+ ? opts.query.replace("$addr", domain)
29
+ : `${domain}\r\n`;
30
+ const port = (opts === null || opts === void 0 ? void 0 : opts.port) || 43;
31
+ // console.log(`looking up ${domain} on ${server}`);
32
+ const response = {
33
+ found: true,
34
+ registrar: { id: 0, name: null },
35
+ reseller: null,
36
+ status: [],
37
+ nameservers: [],
38
+ ts: { created: null, updated: null, expires: null },
39
+ };
40
+ if (!server) {
41
+ return response;
42
+ }
43
+ let port43response = "";
44
+ try {
45
+ const promiseSocket = new PromiseSocket(new Socket());
46
+ promiseSocket.setTimeout(5 * 1000);
47
+ await promiseSocket.connect(port, server);
48
+ await promiseSocket.write(query);
49
+ port43response = (await promiseSocket.readAll())
50
+ .toString()
51
+ .replace(/^[ \t]+/gm, "");
52
+ await promiseSocket.end();
53
+ }
54
+ catch (error) {
55
+ response.found = false;
56
+ }
57
+ if (!response.found) {
58
+ return response;
59
+ }
60
+ if (port43response.match(/^%*\s+(NOT FOUND|No match|NO OBJECT FOUND|No entries found|No Data Found|No information available|Status: free)\b/im)) {
61
+ response.found = false;
62
+ return response;
63
+ }
64
+ let m;
65
+ if (port43parsers[tld]) {
66
+ port43parsers[tld](port43response, response);
67
+ }
68
+ !response.registrar.name &&
69
+ (m = port43response.match(/^(?:Registrar(?: Name)?|registrar_name|registrar):[ \t]*(\S.+)/m)) &&
70
+ (response.registrar.name = m[1]);
71
+ !response.reseller &&
72
+ (m = port43response.match(/^(?:Reseller(?: Name)?|reseller_name|reseller):[ \t]*(\S.+)/m)) &&
73
+ (response.reseller = m[1]);
74
+ !response.registrar.id &&
75
+ (m = port43response.match(/^Registrar IANA ID:[ \t]*(\d+)/m)) &&
76
+ (response.registrar.id = parseInt(m[1] || "0"));
77
+ !response.ts.updated &&
78
+ (m = port43response.match(/^(?:Last Modified|Updated Date|domain_datelastmodified|last-update):[ \t]*(\S.+)/m)) &&
79
+ (response.ts.updated = new Date(m[1]) || null);
80
+ !response.ts.created &&
81
+ (m = port43response.match(/^(?:Creation Date|domain_dateregistered|created):[ \t]*(\S.+)/m)) &&
82
+ (response.ts.created = new Date(m[1]) || null);
83
+ !response.ts.expires &&
84
+ (m = port43response.match(/^(?:(?:Registry )?Expiry Date):[ \t]*(\S.+)/m)) &&
85
+ (response.ts.expires = new Date(m[1]) || null);
86
+ (m = port43response.match(/^(?:Status|Domain Status|status):.*/gm)) &&
87
+ m.forEach((s) => {
88
+ let m;
89
+ (m = s.match(/^(?:Status|Domain Status|status):[ \t]*(?:<a[^>]*>)?(\S+)/m)) && response.status.push(normalizeWhoisStatus(m[1]));
90
+ });
91
+ (m = port43response.match(/^(?:Name Server|ns_name_\d+|namserver|nserver):.*/gm)) &&
92
+ m.forEach((s) => {
93
+ let m;
94
+ (m = s.match(/^(?:Name Server|ns_name_\d+|namserver|nserver):[ \t]*(.*)/m)) && response.nameservers.push(m[1].toLowerCase());
95
+ });
96
+ if (response.registrar.id === 0 && response.registrar.name !== "") {
97
+ for (const [id, { name }] of ianaToRegistrarCache.entries()) {
98
+ if (name === response.registrar.name) {
99
+ response.registrar.id = id;
100
+ break;
101
+ }
102
+ }
103
+ }
104
+ if (response.registrar.id === 0 && response.registrar.name !== "") {
105
+ for (const [id, { name }] of ianaToRegistrarCache.entries()) {
106
+ if (name.match(new RegExp(`\\b${response.registrar.name}\\b`, "i"))) {
107
+ response.registrar.id = id;
108
+ break;
109
+ }
110
+ }
111
+ }
112
+ if (response.registrar.id === 0 && response.registrar.name) {
113
+ for (const [id, { name }] of ianaToRegistrarCache.entries()) {
114
+ if (name.match(new RegExp(`\\b${response.registrar.name.replace(/,.*/, "")}\\b`, "i"))) {
115
+ response.registrar.id = id;
116
+ break;
117
+ }
118
+ }
119
+ }
120
+ return response;
121
+ }
@@ -0,0 +1,3 @@
1
+ import { WhoisResponse } from "../whois.js";
2
+ export declare const port43parsers: Record<string, (response: string, record: WhoisResponse) => void>;
3
+ export declare const port43servers: Record<string, any>;