@mitrajit/simple-whois 0.0.1
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 +4 -0
- package/dist/index.js +20 -0
- package/dist/parser.d.ts +13 -0
- package/dist/parser.js +663 -0
- package/dist/servers.json +783 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +56 -0
- package/dist/whois.d.ts +784 -0
- package/dist/whois.js +236 -0
- package/package.json +40 -0
package/dist/parser.js
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.whoisDataToGroups = exports.parseDomainWhois = exports.parseSimpleWhois = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
function parseSimpleWhois(whois) {
|
|
6
|
+
const renameLabels = {
|
|
7
|
+
NetRange: "range",
|
|
8
|
+
inetnum: "range",
|
|
9
|
+
CIDR: "route",
|
|
10
|
+
origin: "asn",
|
|
11
|
+
OriginAS: "asn",
|
|
12
|
+
};
|
|
13
|
+
// labels of WHOIS data that are actually groups
|
|
14
|
+
const lineToGroup = {
|
|
15
|
+
contact: "contact",
|
|
16
|
+
OrgName: "organisation",
|
|
17
|
+
organisation: "organisation",
|
|
18
|
+
OrgAbuseHandle: "contactAbuse",
|
|
19
|
+
irt: "contactAbuse",
|
|
20
|
+
RAbuseHandle: "contactAbuse",
|
|
21
|
+
OrgTechHandle: "contactTechnical",
|
|
22
|
+
RTechHandle: "contactTechnical",
|
|
23
|
+
OrgNOCHandle: "contactNoc",
|
|
24
|
+
RNOCHandle: "contactNoc",
|
|
25
|
+
};
|
|
26
|
+
if (whois.includes("returned 0 objects") ||
|
|
27
|
+
whois.includes("No match found")) {
|
|
28
|
+
throw new Error("No WHOIS data found");
|
|
29
|
+
}
|
|
30
|
+
const { comments, groups } = whoisDataToGroups(whois);
|
|
31
|
+
const data = {
|
|
32
|
+
__comments: comments,
|
|
33
|
+
__raw: whois,
|
|
34
|
+
};
|
|
35
|
+
groups.forEach((group) => {
|
|
36
|
+
const groupLabels = Object.keys(group);
|
|
37
|
+
let isGroup = false;
|
|
38
|
+
// check if a label is marked as group
|
|
39
|
+
groupLabels.forEach((groupLabel) => {
|
|
40
|
+
if (!isGroup && Object.keys(lineToGroup).includes(groupLabel)) {
|
|
41
|
+
isGroup = lineToGroup[groupLabel] || false;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Check if a info group is a Contact in APNIC result
|
|
46
|
+
* @see https://www.apnic.net/manage-ip/using-whois/guide/role/
|
|
47
|
+
*/
|
|
48
|
+
if (!isGroup && groupLabels.includes("role")) {
|
|
49
|
+
isGroup = "Contact " + group["role"].split(" ")[1];
|
|
50
|
+
}
|
|
51
|
+
else if (!isGroup && groupLabels.includes("person")) {
|
|
52
|
+
isGroup = "Contact " + group["nic-hdl"];
|
|
53
|
+
}
|
|
54
|
+
if (isGroup === "contact") {
|
|
55
|
+
data.contacts ||= {};
|
|
56
|
+
data.contacts[group["contact"]] = group;
|
|
57
|
+
}
|
|
58
|
+
else if (isGroup) {
|
|
59
|
+
data[isGroup] = group;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
for (const key in group) {
|
|
63
|
+
const label = renameLabels[key] || key;
|
|
64
|
+
data[label] = group[key];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
70
|
+
exports.parseSimpleWhois = parseSimpleWhois;
|
|
71
|
+
function parseDomainWhois(domain, whois, ignorePrivacy = true) {
|
|
72
|
+
// Text saying there's no useful data in a field
|
|
73
|
+
const noData = [
|
|
74
|
+
"-",
|
|
75
|
+
".",
|
|
76
|
+
"n/a",
|
|
77
|
+
"no data",
|
|
78
|
+
"redacted",
|
|
79
|
+
"privado",
|
|
80
|
+
"datos privados",
|
|
81
|
+
"data protected",
|
|
82
|
+
"not disclosed",
|
|
83
|
+
"data protected, not disclosed",
|
|
84
|
+
"data redacted",
|
|
85
|
+
"not disclosed not disclosed",
|
|
86
|
+
"not disclosed! visit www.eurid.eu for webbased whois.",
|
|
87
|
+
"not available",
|
|
88
|
+
"redacted for privacy",
|
|
89
|
+
"redacted | eu data subject",
|
|
90
|
+
"gdpr redacted",
|
|
91
|
+
"non-public data",
|
|
92
|
+
"gdpr masked",
|
|
93
|
+
"statutory masking enabled",
|
|
94
|
+
"redacted by privacy",
|
|
95
|
+
"not applicable",
|
|
96
|
+
"na",
|
|
97
|
+
"redacted for privacy purposes",
|
|
98
|
+
"redacted | eu registrar",
|
|
99
|
+
"registration private",
|
|
100
|
+
"none",
|
|
101
|
+
"redacted.forprivacy",
|
|
102
|
+
"redacted | registry policy",
|
|
103
|
+
"redacted for gdpr privacy",
|
|
104
|
+
"redacted for gdpr",
|
|
105
|
+
"redacted redacted",
|
|
106
|
+
"not available from registry",
|
|
107
|
+
"hidden upon user request",
|
|
108
|
+
];
|
|
109
|
+
// WHOIS labels to rename. "From" must be lowercase
|
|
110
|
+
// from -> to
|
|
111
|
+
const renameLabels = {
|
|
112
|
+
"domain name": "Domain Name",
|
|
113
|
+
domain: "Domain Name",
|
|
114
|
+
"domain...............": "Domain Name",
|
|
115
|
+
"idn tag": "IDN",
|
|
116
|
+
"internationalized domain name": "IDN",
|
|
117
|
+
nameserver: "Name Server",
|
|
118
|
+
nameservers: "Name Server",
|
|
119
|
+
nserver: "Name Server",
|
|
120
|
+
"name servers": "Name Server",
|
|
121
|
+
"name server information": "Name Server",
|
|
122
|
+
dns: "Name Server",
|
|
123
|
+
"nserver..............": "Name Server",
|
|
124
|
+
hostname: "Name Server",
|
|
125
|
+
"domain nameservers": "Name Server",
|
|
126
|
+
"domain servers in listed order": "Name Server",
|
|
127
|
+
"domain servers": "Name Server",
|
|
128
|
+
"name servers dns": "Name Server",
|
|
129
|
+
"ns 1": "Name Server",
|
|
130
|
+
"ns 2": "Name Server",
|
|
131
|
+
"ns 3": "Name Server",
|
|
132
|
+
"ns 4": "Name Server",
|
|
133
|
+
flags: "Domain Status",
|
|
134
|
+
status: "Domain Status",
|
|
135
|
+
state: "Domain Status",
|
|
136
|
+
"registration status": "Domain Status",
|
|
137
|
+
eppstatus: "Domain Status",
|
|
138
|
+
"sponsoring registrar iana id": "Registrar IANA ID",
|
|
139
|
+
organisation: "Registrar",
|
|
140
|
+
registrar: "Registrar",
|
|
141
|
+
"registrar name": "Registrar",
|
|
142
|
+
"registrar organization": "Registrar",
|
|
143
|
+
"registrar............": "Registrar",
|
|
144
|
+
"record maintained by": "Registrar",
|
|
145
|
+
"sponsoring registrar": "Registrar",
|
|
146
|
+
"registrar organization name": "Registrar",
|
|
147
|
+
url: "Registrar URL",
|
|
148
|
+
"registrar website": "Registrar URL",
|
|
149
|
+
"registrar web": "Registrar URL",
|
|
150
|
+
"www..................": "Registrar URL",
|
|
151
|
+
"mnt-by": "Registrar ID",
|
|
152
|
+
"creation date": "Created Date",
|
|
153
|
+
"registered on": "Created Date",
|
|
154
|
+
"registration date": "Created Date",
|
|
155
|
+
"relevant dates registered on": "Created Date",
|
|
156
|
+
created: "Created Date",
|
|
157
|
+
"created on": "Created Date",
|
|
158
|
+
"additional info created on..............": "Created Date",
|
|
159
|
+
"registration time": "Created Date",
|
|
160
|
+
registered: "Created Date",
|
|
161
|
+
"created..............": "Created Date",
|
|
162
|
+
"domain registered": "Created Date",
|
|
163
|
+
"registered date": "Created Date",
|
|
164
|
+
"last updated": "Updated Date",
|
|
165
|
+
changed: "Updated Date",
|
|
166
|
+
modified: "Updated Date",
|
|
167
|
+
updated: "Updated Date",
|
|
168
|
+
"modification date": "Updated Date",
|
|
169
|
+
"last modified": "Updated Date",
|
|
170
|
+
"relevant dates last updated": "Updated Date",
|
|
171
|
+
"last updated on": "Updated Date",
|
|
172
|
+
"last update": "Updated Date",
|
|
173
|
+
"last-update": "Updated Date",
|
|
174
|
+
"registrar registration expiration date": "Expiry Date",
|
|
175
|
+
"registry expiry date": "Expiry Date",
|
|
176
|
+
"expires on": "Expiry Date",
|
|
177
|
+
expires: "Expiry Date",
|
|
178
|
+
"expiration time": "Expiry Date",
|
|
179
|
+
"expire date": "Expiry Date",
|
|
180
|
+
"expiration date": "Expiry Date",
|
|
181
|
+
"expires..............": "Expiry Date",
|
|
182
|
+
"additional info expires on..............": "Expiry Date",
|
|
183
|
+
"paid-till": "Expiry Date",
|
|
184
|
+
"expiry date": "Expiry Date",
|
|
185
|
+
expiry: "Expiry Date",
|
|
186
|
+
expire: "Expiry Date",
|
|
187
|
+
"relevant dates expiry date": "Expiry Date",
|
|
188
|
+
"record will expire on": "Expiry Date",
|
|
189
|
+
expired: "Expiry Date",
|
|
190
|
+
"renewal date": "Expiry Date",
|
|
191
|
+
"registry registrantid": "Registry Registrant ID",
|
|
192
|
+
"owner name": "Registrant Name",
|
|
193
|
+
registrant: "Registrant Name",
|
|
194
|
+
"registrant contact": "Registrant Name",
|
|
195
|
+
"registrant contact name": "Registrant Name",
|
|
196
|
+
registrantname: "Registrant Name",
|
|
197
|
+
"registrant person": "Registrant Name",
|
|
198
|
+
"registrant email": "Registrant Email",
|
|
199
|
+
"registrant e-mail": "Registrant Email",
|
|
200
|
+
"registrant contact email": "Registrant Email",
|
|
201
|
+
registrantemail: "Registrant Email",
|
|
202
|
+
registrantstreet: "Registrant Street",
|
|
203
|
+
registrantcity: "Registrant City",
|
|
204
|
+
registrantcountry: "Registrant Country",
|
|
205
|
+
"registrant country": "Registrant Country",
|
|
206
|
+
"owner orgname": "Registrant Organization",
|
|
207
|
+
"registrant organisation": "Registrant Organization",
|
|
208
|
+
registrantphone: "Registrant Phone",
|
|
209
|
+
"trading as": "Registrant Organization",
|
|
210
|
+
org: "Registrant Organization",
|
|
211
|
+
"registrant state": "Registrant State/Province",
|
|
212
|
+
"registrant's address": "Registrant Street",
|
|
213
|
+
"owner addr": "Registrant Street",
|
|
214
|
+
dnssec: "DNSSEC",
|
|
215
|
+
};
|
|
216
|
+
const ignoreLabels = [
|
|
217
|
+
"note",
|
|
218
|
+
"notes",
|
|
219
|
+
"please note",
|
|
220
|
+
"important",
|
|
221
|
+
"notice",
|
|
222
|
+
"terms of use",
|
|
223
|
+
"web-based whois",
|
|
224
|
+
"https",
|
|
225
|
+
"to",
|
|
226
|
+
"registration service provider",
|
|
227
|
+
"you acknowledge that",
|
|
228
|
+
];
|
|
229
|
+
const ignoreTexts = [
|
|
230
|
+
"more information",
|
|
231
|
+
"lawful purposes",
|
|
232
|
+
"to contact",
|
|
233
|
+
"use this data",
|
|
234
|
+
"register your domain",
|
|
235
|
+
"copy and paste",
|
|
236
|
+
"find out more",
|
|
237
|
+
"this",
|
|
238
|
+
"please",
|
|
239
|
+
"important",
|
|
240
|
+
"prices",
|
|
241
|
+
"payment",
|
|
242
|
+
"you agree",
|
|
243
|
+
"restrictions",
|
|
244
|
+
"queried object",
|
|
245
|
+
"service",
|
|
246
|
+
"terms",
|
|
247
|
+
];
|
|
248
|
+
let colon = ": ";
|
|
249
|
+
let text = [];
|
|
250
|
+
let data = {
|
|
251
|
+
"Domain Status": [],
|
|
252
|
+
"Name Server": [],
|
|
253
|
+
text: [],
|
|
254
|
+
};
|
|
255
|
+
let lines = whois
|
|
256
|
+
.trim()
|
|
257
|
+
.split("\n")
|
|
258
|
+
.map((line) => line.replace("\t", " "));
|
|
259
|
+
// Parse WHOIS info for specific TLDs
|
|
260
|
+
if (domain.endsWith(".uk") ||
|
|
261
|
+
domain.endsWith(".be") ||
|
|
262
|
+
domain.endsWith(".nl") ||
|
|
263
|
+
domain.endsWith(".eu") ||
|
|
264
|
+
domain.endsWith(".ly") ||
|
|
265
|
+
domain.endsWith(".mx") ||
|
|
266
|
+
domain.endsWith(".gg") ||
|
|
267
|
+
domain.endsWith(".je") ||
|
|
268
|
+
domain.endsWith(".as")) {
|
|
269
|
+
lines = handleMultiLines(lines);
|
|
270
|
+
}
|
|
271
|
+
if (domain.endsWith(".gg") ||
|
|
272
|
+
domain.endsWith(".je") ||
|
|
273
|
+
domain.endsWith(".as")) {
|
|
274
|
+
lines = handleMissingColons(lines);
|
|
275
|
+
}
|
|
276
|
+
if (domain.endsWith(".ua")) {
|
|
277
|
+
lines = handleDotUa(lines);
|
|
278
|
+
colon = ":";
|
|
279
|
+
}
|
|
280
|
+
if (domain.endsWith(".jp")) {
|
|
281
|
+
lines = handleJpLines(lines);
|
|
282
|
+
}
|
|
283
|
+
if (domain.endsWith(".it")) {
|
|
284
|
+
lines = handleDotIt(lines);
|
|
285
|
+
}
|
|
286
|
+
else if (domain.endsWith(".fr")) {
|
|
287
|
+
lines = handleDotFr(lines);
|
|
288
|
+
}
|
|
289
|
+
if (domain.endsWith(".tr")) {
|
|
290
|
+
lines = handleDotTr(lines);
|
|
291
|
+
}
|
|
292
|
+
lines = lines.map((l) => l.trim());
|
|
293
|
+
lines.forEach((line) => {
|
|
294
|
+
if ((line.includes(colon) || line.endsWith(":")) &&
|
|
295
|
+
!line.startsWith("%") &&
|
|
296
|
+
!line.startsWith(";") &&
|
|
297
|
+
!line.startsWith("*")) {
|
|
298
|
+
let [label, value] = (0, utils_1.splitStringBy)(line, line.indexOf(":")).map((info) => info.trim());
|
|
299
|
+
// fix whois line with double color, ex: "Label:: value"
|
|
300
|
+
if (value.startsWith(":")) {
|
|
301
|
+
value = value.slice(1);
|
|
302
|
+
}
|
|
303
|
+
value = value.trim();
|
|
304
|
+
// rename labels to more common format
|
|
305
|
+
if (renameLabels[label.toLowerCase()]) {
|
|
306
|
+
label = renameLabels[label.toLowerCase()];
|
|
307
|
+
}
|
|
308
|
+
// remove redacted data
|
|
309
|
+
if (ignorePrivacy && noData.includes(value.toLowerCase())) {
|
|
310
|
+
value = "";
|
|
311
|
+
}
|
|
312
|
+
if (data[label] && Array.isArray(data[label])) {
|
|
313
|
+
data[label].push(value);
|
|
314
|
+
}
|
|
315
|
+
else if (!ignoreLabels.includes(label.toLowerCase()) &&
|
|
316
|
+
!ignoreTexts.some((text) => label.toLowerCase().includes(text))) {
|
|
317
|
+
// WHOIS field already exists, if so append data
|
|
318
|
+
if (data[label] && data[label] !== value) {
|
|
319
|
+
data[label] = `${data[label]} ${value}`.trim();
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
data[label] = value;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
text.push(line);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
text.push(line);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
// remove invalid Name Servers (not valid hostname)
|
|
334
|
+
data["Name Server"] = data["Name Server"]
|
|
335
|
+
.map((nameServer) => nameServer.split(" "))
|
|
336
|
+
.flat()
|
|
337
|
+
.filter(utils_1.isDomain);
|
|
338
|
+
//todo sometimes the whois includes duplicate NS for a domain
|
|
339
|
+
//data['Name Server'] = uniq(data['Name Server'])
|
|
340
|
+
// filter out empty status lines
|
|
341
|
+
data["Domain Status"] = data["Domain Status"].filter(Boolean);
|
|
342
|
+
// remove multiple empty lines
|
|
343
|
+
let textString = text.join("\n").trim();
|
|
344
|
+
while (textString.includes("\n\n\n")) {
|
|
345
|
+
textString = textString.replace("\n\n\n", "\n");
|
|
346
|
+
}
|
|
347
|
+
data.text = textString.split("\n");
|
|
348
|
+
return data;
|
|
349
|
+
}
|
|
350
|
+
exports.parseDomainWhois = parseDomainWhois;
|
|
351
|
+
const handleDotTr = (lines) => {
|
|
352
|
+
lines = lines.filter((line) => line.trim() !== ""); // Remove blank lines
|
|
353
|
+
const registrantLines = [
|
|
354
|
+
"Name",
|
|
355
|
+
undefined,
|
|
356
|
+
undefined,
|
|
357
|
+
undefined,
|
|
358
|
+
undefined,
|
|
359
|
+
]; // No clue what the other 4 fields are, all domains have them hidden
|
|
360
|
+
const replacement = [];
|
|
361
|
+
let section = "";
|
|
362
|
+
let sectionLine = 0;
|
|
363
|
+
for (let line of lines) {
|
|
364
|
+
line = line.replace(/\s+/g, " ").replace(" :", ":").trim();
|
|
365
|
+
if (line.startsWith("** Domain")) {
|
|
366
|
+
// Keep line for domain name/nameservers
|
|
367
|
+
line = line.replace("** ", "");
|
|
368
|
+
}
|
|
369
|
+
else if (line.includes("** ")) {
|
|
370
|
+
// Start new section
|
|
371
|
+
section = line.replace(":", "").replace("** ", "").trim();
|
|
372
|
+
sectionLine = 0;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
else if (section === "Registrant") {
|
|
376
|
+
// Add registrant info
|
|
377
|
+
if (!registrantLines[sectionLine])
|
|
378
|
+
continue;
|
|
379
|
+
line = `Registrant ${registrantLines[sectionLine]}: ${line}`;
|
|
380
|
+
sectionLine++;
|
|
381
|
+
}
|
|
382
|
+
else if (!line.includes(": ")) {
|
|
383
|
+
// Add multi-line information to one line (nameservers and address)
|
|
384
|
+
replacement[replacement.length - 1] += ` ${line}`;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
else if (section) {
|
|
388
|
+
// Append section name to each line
|
|
389
|
+
line = `${section} ${line}`;
|
|
390
|
+
}
|
|
391
|
+
// Remove period at end of dates
|
|
392
|
+
if (section === "Additional Info") {
|
|
393
|
+
line = line.replace(/\.$/, "");
|
|
394
|
+
}
|
|
395
|
+
replacement.push(line);
|
|
396
|
+
}
|
|
397
|
+
return replacement;
|
|
398
|
+
};
|
|
399
|
+
const handleDotUa = (lines) => {
|
|
400
|
+
const types = ["Registrar", "Registrant", "Admin", "Technical"];
|
|
401
|
+
let flag = "";
|
|
402
|
+
lines.forEach((line, index) => {
|
|
403
|
+
if (line.startsWith("%") && types.some((v) => line.includes(v))) {
|
|
404
|
+
flag = line
|
|
405
|
+
.substring(1, line.length - 1)
|
|
406
|
+
.trim()
|
|
407
|
+
.toLowerCase();
|
|
408
|
+
}
|
|
409
|
+
else if (!line.startsWith("%") && line.includes(": ")) {
|
|
410
|
+
if (line.startsWith("registrar"))
|
|
411
|
+
line = "id";
|
|
412
|
+
lines[index] = flag + " " + line;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
return lines;
|
|
416
|
+
};
|
|
417
|
+
const handleDotIt = (lines) => {
|
|
418
|
+
let section = "";
|
|
419
|
+
const replacement = [];
|
|
420
|
+
for (let line of lines) {
|
|
421
|
+
// Ignore comments and empty lines
|
|
422
|
+
if (line.startsWith("*") || line === "") {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
// Collapse whitespace
|
|
426
|
+
const collapsed = line.replace(/\s+/g, " ").trim();
|
|
427
|
+
// Check for top-level values and new section indicators
|
|
428
|
+
if (/^[^\s]/.test(line)) {
|
|
429
|
+
if (line.includes(":")) {
|
|
430
|
+
replacement.push(collapsed);
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
// Special handling for "Nameservers" section
|
|
434
|
+
if (line === "Nameservers") {
|
|
435
|
+
section = "Name Server:";
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
section = collapsed;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// Make sure sub-section lines are properly labeled
|
|
443
|
+
if (/^\s{2}[^\s]/.test(line)) {
|
|
444
|
+
// New sub-section
|
|
445
|
+
replacement.push(`${section} ${collapsed}`);
|
|
446
|
+
}
|
|
447
|
+
else if (/^\s{4}/.test(line)) {
|
|
448
|
+
// Continuation of previous line
|
|
449
|
+
replacement[replacement.length - 1] += `, ${collapsed}`;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return replacement;
|
|
453
|
+
};
|
|
454
|
+
// Fix "label: \n value" format
|
|
455
|
+
const handleMultiLines = (lines) => {
|
|
456
|
+
lines.forEach((line, index) => {
|
|
457
|
+
// if line is just a WHOIS label ending with ":", then verify next lines
|
|
458
|
+
if (!line.startsWith("*") &&
|
|
459
|
+
!line.startsWith("%") &&
|
|
460
|
+
line.trim().endsWith(":")) {
|
|
461
|
+
let addedLabel = false;
|
|
462
|
+
// Check next lines
|
|
463
|
+
for (let i = 1; i <= 8; i++) {
|
|
464
|
+
// if no line or empty line
|
|
465
|
+
if (!lines[index + i] || !lines[index + i].trim().length) {
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
// if tabbed line or line with value only, prefix the line with main label
|
|
469
|
+
if ((lines[index + i].startsWith(" ") &&
|
|
470
|
+
lines[index + i].includes(": ")) ||
|
|
471
|
+
!lines[index + i].endsWith(":")) {
|
|
472
|
+
let label = line.trim();
|
|
473
|
+
if (lines[index + i].includes(":") && label.endsWith(":")) {
|
|
474
|
+
label = label.slice(0, -1);
|
|
475
|
+
}
|
|
476
|
+
lines[index + i] =
|
|
477
|
+
label +
|
|
478
|
+
" " +
|
|
479
|
+
lines[index + i].replace("\t", " ").trim();
|
|
480
|
+
addedLabel = true;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// remove this line if it was just a label for other lines
|
|
484
|
+
if (addedLabel) {
|
|
485
|
+
lines[index] = "";
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
return lines;
|
|
490
|
+
};
|
|
491
|
+
// Handle formats like this:
|
|
492
|
+
// [Name Server] ns1.jprs.jp
|
|
493
|
+
// [Name Server] ns2.jprs.jp
|
|
494
|
+
const handleJpLines = (lines) => {
|
|
495
|
+
const ret = [];
|
|
496
|
+
while (lines.length > 0) {
|
|
497
|
+
let line = lines.shift();
|
|
498
|
+
// Skip if line is undefined (shouldn't happen with while loop check, but belts and suspenders)
|
|
499
|
+
if (line === undefined)
|
|
500
|
+
continue;
|
|
501
|
+
// handle lines that start with "a. [label]"
|
|
502
|
+
if (/^[a-z]. \[/.test(line)) {
|
|
503
|
+
line = line.replace(/^[a-z]. \[/, "[");
|
|
504
|
+
}
|
|
505
|
+
if (line.startsWith("[ ")) {
|
|
506
|
+
// skip
|
|
507
|
+
}
|
|
508
|
+
else if (line.startsWith("[")) {
|
|
509
|
+
ret.push(line);
|
|
510
|
+
}
|
|
511
|
+
else if (line.startsWith(" ")) {
|
|
512
|
+
const prev = ret.pop();
|
|
513
|
+
ret.push(prev + "\n" + line.trim());
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
// skip
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return ret.map((line) => line.replace(/\[(.*?)\]/g, "$1:"));
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* Normalize WHOIS data for .fr ccTld, make it look more like gTLDs
|
|
523
|
+
*
|
|
524
|
+
* @param {string[]} lines
|
|
525
|
+
* @returns
|
|
526
|
+
*/
|
|
527
|
+
function handleDotFr(lines) {
|
|
528
|
+
const groups = [];
|
|
529
|
+
let group = [];
|
|
530
|
+
const finalLines = [];
|
|
531
|
+
// split data in groups
|
|
532
|
+
lines.forEach((line) => {
|
|
533
|
+
if (line.startsWith("%")) {
|
|
534
|
+
finalLines.push(line);
|
|
535
|
+
}
|
|
536
|
+
else if (!line.trim().length && group.length) {
|
|
537
|
+
// start new group
|
|
538
|
+
groups.push(group);
|
|
539
|
+
group = [];
|
|
540
|
+
}
|
|
541
|
+
else if (line.trim().length && !line.startsWith("source")) {
|
|
542
|
+
group.push((0, utils_1.splitStringBy)(line, line.indexOf(":")).map((str) => str.trim()));
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
if (group.length) {
|
|
546
|
+
groups.push(group);
|
|
547
|
+
}
|
|
548
|
+
groups.forEach((gr) => {
|
|
549
|
+
if (gr[0][0] === "domain") {
|
|
550
|
+
// group with domain info
|
|
551
|
+
gr.forEach((line) => {
|
|
552
|
+
if (line[0] !== "status") {
|
|
553
|
+
finalLines.push(line.join(": "));
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
else if (gr[0][0] === "registrar") {
|
|
558
|
+
// group with Registrar info
|
|
559
|
+
gr.forEach(([label, value]) => {
|
|
560
|
+
if (label === "registrar") {
|
|
561
|
+
finalLines.push(`${label}: ${value}`);
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
finalLines.push(`registrar ${label}: ${value}`);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
else if (gr[0][0] === "nic-hdl") {
|
|
569
|
+
let contactType = "";
|
|
570
|
+
const contactTypeLine = finalLines.find((line) => line.includes(gr[0][1]));
|
|
571
|
+
// Check if contactTypeLine was found before using it
|
|
572
|
+
if (contactTypeLine) {
|
|
573
|
+
if (contactTypeLine.startsWith("admin-c")) {
|
|
574
|
+
contactType = "admin";
|
|
575
|
+
}
|
|
576
|
+
else if (contactTypeLine.startsWith("holder-c")) {
|
|
577
|
+
contactType = "registrant";
|
|
578
|
+
}
|
|
579
|
+
else if (contactTypeLine.startsWith("tech-c")) {
|
|
580
|
+
contactType = "technical";
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// group with contact info
|
|
584
|
+
gr.forEach(([label, value]) => {
|
|
585
|
+
if (label === "nic-hdl") {
|
|
586
|
+
finalLines.push(`${contactType} registry id: ${value}`);
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
finalLines.push(`${contactType} ${label}: ${value}`);
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
gr.forEach((line) => {
|
|
595
|
+
finalLines.push(line.join(": "));
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
return finalLines;
|
|
600
|
+
}
|
|
601
|
+
// Handle formats like this:
|
|
602
|
+
// Registrar Gandi SAS
|
|
603
|
+
const handleMissingColons = (lines) => {
|
|
604
|
+
lines.forEach((line, index) => {
|
|
605
|
+
if (line.startsWith("Registrar ")) {
|
|
606
|
+
lines[index] = line.replace("Registrar ", "Registrar: ");
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
return lines;
|
|
610
|
+
};
|
|
611
|
+
// ----- v2
|
|
612
|
+
function whoisDataToGroups(whois) {
|
|
613
|
+
const comments = [];
|
|
614
|
+
let resultNum = 0;
|
|
615
|
+
const groups = [{}];
|
|
616
|
+
let lastLabel;
|
|
617
|
+
whois.split("\n").forEach((line) => {
|
|
618
|
+
// catch comment lines
|
|
619
|
+
if (line.startsWith("%") || line.startsWith("#")) {
|
|
620
|
+
// detect if an ASN or IP has multiple WHOIS results
|
|
621
|
+
if (line.includes("# start")) {
|
|
622
|
+
// nothing
|
|
623
|
+
}
|
|
624
|
+
else if (line.includes("# end")) {
|
|
625
|
+
resultNum++;
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
comments.push(line);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
else if (resultNum === 0) {
|
|
632
|
+
// for the moment, parse only first WHOIS result
|
|
633
|
+
if (line) {
|
|
634
|
+
if (line.includes(":")) {
|
|
635
|
+
const [label, value] = (0, utils_1.splitStringBy)(line, line.indexOf(":")).map((info) => info.trim());
|
|
636
|
+
lastLabel = label;
|
|
637
|
+
// 1) Filter out unnecessary info, 2) then detect if the label is already added to group
|
|
638
|
+
if (value.includes("---")) {
|
|
639
|
+
// do nothing with useless data
|
|
640
|
+
}
|
|
641
|
+
else if (groups[groups.length - 1][label]) {
|
|
642
|
+
groups[groups.length - 1][label] += "\n" + value;
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
groups[groups.length - 1][label] = value;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
groups[groups.length - 1][lastLabel] += "\n" + line.trim();
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
else if (Object.keys(groups[groups.length - 1]).length) {
|
|
653
|
+
// if empty line, means another info group starts
|
|
654
|
+
groups.push({});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
return {
|
|
659
|
+
comments,
|
|
660
|
+
groups: groups.filter((group) => Object.keys(group).length > 0),
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
exports.whoisDataToGroups = whoisDataToGroups;
|