@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.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +178 -0
- package/dist/port43.d.ts +3 -0
- package/dist/port43.js +121 -0
- package/dist/port43servers.d.ts +3 -0
- package/dist/port43servers.js +800 -0
- package/dist/utils/findInObject.d.ts +1 -0
- package/dist/utils/findInObject.js +17 -0
- package/dist/utils/fixArrays.d.ts +1 -0
- package/dist/utils/fixArrays.js +15 -0
- package/dist/utils/ianaIdToRegistrar.d.ts +11 -0
- package/dist/utils/ianaIdToRegistrar.js +16 -0
- package/dist/utils/tldToRdap.d.ts +1 -0
- package/dist/utils/tldToRdap.js +61 -0
- package/dist/whoisStatus.d.ts +1 -0
- package/dist/whoisStatus.js +36 -0
- package/package.json +27 -0
- package/src/index.ts +246 -0
- package/src/port43.ts +164 -0
- package/src/port43servers.ts +827 -0
- package/src/utils/findInObject.ts +23 -0
- package/src/utils/fixArrays.ts +17 -0
- package/src/utils/ianaIdToRegistrar.ts +34 -0
- package/src/utils/tldToRdap.ts +73 -0
- package/src/whoisStatus.ts +43 -0
- package/tsconfig.json +109 -0
- package/whois.d.ts +17 -0
package/dist/index.d.ts
ADDED
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
|
+
// );
|
package/dist/port43.d.ts
ADDED
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
|
+
}
|