@slicedearth/whoisleuth-cli 2.0.1 → 2.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/cli/arguments.mjs +21 -5
- package/cli/artifact-validation/case-response.mjs +771 -0
- package/cli/artifact-validation/investigation-capsule.mjs +426 -0
- package/cli/artifact-validation/lookup-evidence.mjs +718 -0
- package/cli/artifact-validation/signed-review.mjs +598 -0
- package/cli/artifact-validation/structure-primitives.mjs +105 -0
- package/cli/artifact-verify.mjs +1 -1
- package/cli/assurance-command-runner.mjs +324 -0
- package/cli/command-reference.mjs +1175 -843
- package/cli/completion.mjs +56 -9
- package/cli/discriminated-command-handlers.mjs +11 -0
- package/cli/doctor.mjs +2 -2
- package/cli/evidence-command-runner.mjs +41 -31
- package/cli/export-evidence.mjs +1 -1
- package/cli/fail-policy.mjs +33 -8
- package/cli/formatters/terminal-command-formats.mjs +437 -0
- package/cli/formatters/terminal-lookup.mjs +758 -0
- package/cli/formatters/terminal-metadata.mjs +58 -0
- package/cli/formatters/terminal-shared.mjs +83 -0
- package/cli/formatters/terminal.mjs +5 -1232
- package/cli/history-command-runner.mjs +132 -0
- package/cli/inline-command-families.mjs +45 -0
- package/cli/inline-command-runner.mjs +55 -0
- package/cli/investigation-plan.mjs +9 -2
- package/cli/investigation-run.mjs +114 -9
- package/cli/lookup-brief.mjs +141 -23
- package/cli/lookup-browser-view.mjs +289 -0
- package/cli/lookup-browser.mjs +3 -288
- package/cli/mail-review.mjs +2 -6
- package/cli/offline-artifact-validation.mjs +19 -0
- package/cli/review-command-runner.mjs +269 -0
- package/cli/runner.mjs +15 -972
- package/cli/saved-lookup.mjs +19 -2
- package/cli/support-command-runner.mjs +229 -0
- package/cli/workflow-command-runner.mjs +129 -0
- package/docs/cli-reference.md +16 -9
- package/docs/cli.md +16 -2
- package/lib/availability.mjs +12 -9
- package/lib/bounded-contract-normalizers.mjs +2 -9
- package/lib/browser-library-profile.mjs +1 -2
- package/lib/client-behavior-profile.mjs +1 -2
- package/lib/credential-surface-profile.mjs +1 -1
- package/lib/domain-posture-analysis.mjs +10 -1
- package/lib/domain-posture.mjs +51 -23
- package/lib/evidence-export.mjs +47 -5
- package/lib/evidence-report-markdown.mjs +4 -0
- package/lib/evidence-report.mjs +50 -0
- package/lib/generated/cisa-kev-catalog.mjs +34 -4
- package/lib/generated/registrar-standing-catalogue.mjs +367 -0
- package/lib/json-record.mjs +12 -0
- package/lib/lookup-child-profile-contract.mjs +858 -0
- package/lib/lookup-contract-primitives.mjs +98 -0
- package/lib/lookup-diagnostics.mjs +96 -0
- package/lib/lookup-network-evidence-bounds.mjs +1 -0
- package/lib/lookup-response.mjs +367 -0
- package/lib/lookup.mjs +28 -443
- package/lib/page-role-profile.mjs +1 -3
- package/lib/registrar-standing-catalogue-contract.mjs +70 -0
- package/lib/registrar-standing-contract.mjs +300 -0
- package/lib/registrar-standing.mjs +269 -0
- package/lib/registry-insights.mjs +2 -8
- package/lib/risk-scoring.mjs +26 -10
- package/lib/sslbl-certificates.generated.mjs +138 -136
- package/lib/structured-data-identity.mjs +1 -3
- package/lib/technology-evidence-role.mjs +51 -0
- package/lib/threat-intelligence-runtime.mjs +3 -3
- package/lib/threat-intelligence-types.mjs +13 -0
- package/lib/threatfox-intelligence.mjs +1 -3
- package/lib/tls-intelligence.mjs +1 -2
- package/lib/urlhaus-intelligence.mjs +1 -3
- package/lib/urlscan-intelligence.mjs +1 -3
- package/lib/website-security-posture.mjs +1 -2
- package/lib/website-technology.mjs +72 -23
- package/lib/whois-common-formats.mjs +276 -0
- package/lib/whois-dialects.mjs +534 -0
- package/lib/whois-parser-support.mjs +305 -0
- package/lib/whois-parser.mjs +135 -0
- package/lib/whois.mjs +6 -1187
- package/package.json +2 -2
- package/packages/cases/case-evidence-model.mjs +9 -0
- package/packages/cases/case-migration-model.mjs +17 -11
- package/packages/cases/case-record-operations.mjs +235 -0
- package/packages/cases/case-response-model.mjs +13 -9
- package/packages/cases/case-response-packet.mjs +297 -112
- package/packages/comparison/page-similarity.mjs +23 -0
- package/packages/contracts/case-portability.mjs +235 -61
- package/packages/contracts/cli-lookup.mjs +4 -3
- package/packages/contracts/domain-control-manifest.mjs +1 -1
- package/packages/contracts/investigation-portability.mjs +1 -1
- package/packages/contracts/risk-calibration.mjs +2 -2
- package/packages/contracts/workspace-portability.mjs +34 -6
- package/packages/interchange/external-findings-import.mjs +113 -45
- package/packages/investigation/evidence-coverage-ledger.mjs +21 -0
- package/packages/investigation/lookup-claim-readiness.mjs +2 -2
- package/packages/workspace/website-snapshot-model.mjs +79 -6
- package/third-party-notices.txt +1 -1
- package/cli/artifact-structure.mjs +0 -2507
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
// Marker-gated WHOIS registry dialect parsing. Ambiguous aliases stay scoped
|
|
2
|
+
// to complete response signatures and share the generic parser's bounds.
|
|
3
|
+
import { domainToASCII } from 'node:url';
|
|
4
|
+
import { hasNicKgRegistrationEvidence } from "./whois-authority.mjs";
|
|
5
|
+
import { parseIndentedContactBlock, resolveIrnicContact, resolveIsnicRole, } from "./whois-contacts.mjs";
|
|
6
|
+
import { MAX_WHOIS_NAMESERVERS, MAX_WHOIS_STATUSES, WHOIS_DOMAIN_RE, addBoundedWhoisSetValue, assignBoundedWhoisMatch, collectBareWhoisNameservers, } from "./whois-parser-support.mjs";
|
|
7
|
+
import { MAX_WHOIS_FIELD_LENGTH, boundedWhoisValue, parseBoundedWhoisSection, parseIndentedWhoisSubfield, parseIndentedWhoisValue, whoisFieldLimit, } from "./whois-values.mjs";
|
|
8
|
+
function applyWhoisDialects(text, isRootHop, context) {
|
|
9
|
+
const { fields, nameservers, statuses, truncatedFields } = context;
|
|
10
|
+
const isDnsBelgium = !isRootHop
|
|
11
|
+
&& /^%[^\r\n]*\.be Whois Server/im.test(text)
|
|
12
|
+
&& /^[ \t]*Registered[ \t]*:/im.test(text);
|
|
13
|
+
const isEurid = !isRootHop
|
|
14
|
+
&& /^[ \t]*Script[ \t]*:/im.test(text)
|
|
15
|
+
&& /(?:^|\s)(?:www\.)?eurid\.eu(?:\s|\/|$)/im.test(text);
|
|
16
|
+
const isNorid = !isRootHop
|
|
17
|
+
&& /^[ \t]*Domain Information[ \t]*$/im.test(text)
|
|
18
|
+
&& /^[ \t]*NORID Handle[ \t.]*:/im.test(text)
|
|
19
|
+
&& /^[ \t]*Registrar Handle[ \t.]*:/im.test(text)
|
|
20
|
+
&& /^[ \t]*Additional information[ \t]*:/im.test(text);
|
|
21
|
+
const isCnnic = !isRootHop
|
|
22
|
+
&& /^[ \t]*ROID[ \t]*:/im.test(text)
|
|
23
|
+
&& /^[ \t]*Sponsoring Registrar[ \t]*:/im.test(text)
|
|
24
|
+
&& /^[ \t]*Registration Time[ \t]*:/im.test(text)
|
|
25
|
+
&& /^[ \t]*Expiration Time[ \t]*:/im.test(text);
|
|
26
|
+
const isPunktum = !isRootHop
|
|
27
|
+
&& /^[ \t]*Registration period[ \t]*:/im.test(text)
|
|
28
|
+
&& /^[ \t]*VID[ \t]*:/im.test(text)
|
|
29
|
+
&& /^[ \t]*Nameservers[ \t]*$/im.test(text);
|
|
30
|
+
const isPandi = !isRootHop
|
|
31
|
+
&& /^[ \t]*Domain ID[ \t]*:/im.test(text)
|
|
32
|
+
&& /^[ \t]*Sponsoring Registrar Organization[ \t]*:/im.test(text);
|
|
33
|
+
const isIsocIl = !isRootHop
|
|
34
|
+
&& /^[ \t]*query[ \t]*:/im.test(text)
|
|
35
|
+
&& /^[ \t]*reg-name[ \t]*:/im.test(text)
|
|
36
|
+
&& /^[ \t]*validity[ \t]*:/im.test(text);
|
|
37
|
+
const isTwnic = !isRootHop
|
|
38
|
+
&& /^[ \t]*Record created on[ \t]*:/im.test(text)
|
|
39
|
+
&& /^[ \t]*Record expires on[ \t]*:/im.test(text)
|
|
40
|
+
&& /^[ \t]*Registration Service Provider[ \t]*:/im.test(text);
|
|
41
|
+
const isRegisterBg = !isRootHop
|
|
42
|
+
&& /^[ \t]*registration status[ \t]*:/im.test(text)
|
|
43
|
+
&& /^[ \t]*NAME SERVER INFORMATION[ \t]*:[ \t]*$/im.test(text);
|
|
44
|
+
const isEif = !isRootHop
|
|
45
|
+
&& /Estonia \.ee Top Level Domain WHOIS server/i.test(text)
|
|
46
|
+
&& /^[ \t]*Domain[ \t]*:[ \t]*$/im.test(text)
|
|
47
|
+
&& /^[ \t]*Name servers[ \t]*:[ \t]*$/im.test(text);
|
|
48
|
+
const isIszt = !isRootHop
|
|
49
|
+
&& /^% Whois server[^\r\n]*hu ccTLD/im.test(text)
|
|
50
|
+
&& /^[ \t]*record created[ \t]*:/im.test(text);
|
|
51
|
+
const isIsnic = !isRootHop
|
|
52
|
+
&& /^% This is the ISNIC Whois server\./im.test(text)
|
|
53
|
+
&& /^[ \t]*source[ \t]*:[ \t]*ISNIC(?:\s|$)/im.test(text);
|
|
54
|
+
const isNicLv = !isRootHop
|
|
55
|
+
&& /^[ \t]*\[Domain\][ \t]*$/im.test(text)
|
|
56
|
+
&& /^[ \t]*\[Holder\][ \t]*$/im.test(text)
|
|
57
|
+
&& /^[ \t]*\[Whois\][ \t]*$/im.test(text);
|
|
58
|
+
const isSidn = !isRootHop
|
|
59
|
+
&& /^[ \t]*Domain nameservers[ \t]*:[ \t]*$/im.test(text)
|
|
60
|
+
&& /^[ \t]*Abuse Contact[ \t]*:[ \t]*$/im.test(text)
|
|
61
|
+
&& /^[ \t]*Record maintained by[ \t]*:/im.test(text);
|
|
62
|
+
const isRnids = !isRootHop
|
|
63
|
+
&& /^[ \t]*Registration date[ \t]*:/im.test(text)
|
|
64
|
+
&& /^[ \t]*Modification date[ \t]*:/im.test(text)
|
|
65
|
+
&& /^[ \t]*DNSSEC signed[ \t]*:/im.test(text);
|
|
66
|
+
const isCctldBy = !isRootHop
|
|
67
|
+
&& /^[ \t]*Domain name[ \t]*:/im.test(text)
|
|
68
|
+
&& /^[ \t]*Registration or other identification number[ \t]*:/im.test(text);
|
|
69
|
+
const isHkirc = !isRootHop
|
|
70
|
+
&& /^[ \t]*Domain Name Information[ \t]*:[ \t]*$/im.test(text)
|
|
71
|
+
&& /^[ \t]*Name Servers Information[ \t]*:[ \t]*$/im.test(text)
|
|
72
|
+
&& /^[ \t]*Domain Name Commencement Date[ \t]*:/im.test(text);
|
|
73
|
+
const isIrnic = !isRootHop
|
|
74
|
+
&& /^[ \t]*source[ \t]*:[ \t]*IRNIC(?:\s|$)/im.test(text)
|
|
75
|
+
&& /^[ \t]*holder-c[ \t]*:/im.test(text)
|
|
76
|
+
&& /^[ \t]*nic-hdl[ \t]*:/im.test(text);
|
|
77
|
+
const isNicKz = !isRootHop
|
|
78
|
+
&& /^[ \t]*Domain Name[ \t.]*:/im.test(text)
|
|
79
|
+
&& /^[ \t]*Current Registar[ \t]*:/im.test(text)
|
|
80
|
+
&& /^[ \t]*Primary server[ \t.]*:/im.test(text);
|
|
81
|
+
const isDnsLu = !isRootHop
|
|
82
|
+
&& /^[ \t]*domainname[ \t]*:/im.test(text)
|
|
83
|
+
&& /^[ \t]*domaintype[ \t]*:/im.test(text)
|
|
84
|
+
&& /^[ \t]*registrar-name[ \t]*:/im.test(text);
|
|
85
|
+
const isNicMd = !isRootHop
|
|
86
|
+
&& /^[ \t]*Domain[ \t]+name[ \t]*:/im.test(text)
|
|
87
|
+
&& /^[ \t]*Domain state[ \t]*:/im.test(text)
|
|
88
|
+
&& /^[ \t]*Registered on[ \t]*:/im.test(text);
|
|
89
|
+
const isThnic = !isRootHop
|
|
90
|
+
&& /^[ \t]*Domain Holder Organization[ \t]*:/im.test(text)
|
|
91
|
+
&& /^[ \t]*Created date[ \t]*:/im.test(text)
|
|
92
|
+
&& /^[ \t]*Exp date[ \t]*:/im.test(text);
|
|
93
|
+
const isAtiTn = !isRootHop
|
|
94
|
+
&& /^[ \t]*NIC Whois server for cTLDs[ \t.]*:/im.test(text)
|
|
95
|
+
&& /^[ \t]*Domain name[ \t.]*:/im.test(text)
|
|
96
|
+
&& /^[ \t]*Creation date[ \t.]*:/im.test(text);
|
|
97
|
+
const isMonic = !isRootHop
|
|
98
|
+
&& /^%[ \t]*Monic Whois Server Version[ \t]+\d/im.test(text)
|
|
99
|
+
&& /^[ \t]*Domain Name[ \t]*:/im.test(text)
|
|
100
|
+
&& /^[ \t]*Record created on[ \t]+[^:\r\n]/im.test(text)
|
|
101
|
+
&& /^[ \t]*Domain name servers[ \t]*:[ \t]*$/im.test(text);
|
|
102
|
+
const isNicKg = !isRootHop && hasNicKgRegistrationEvidence(text);
|
|
103
|
+
// A small set of ccTLD dialects reuse terse labels that would be unsafe
|
|
104
|
+
// as global aliases (for example `org:` and `state:` also occur inside
|
|
105
|
+
// contact/address blocks). Gate those fields on registry-specific marker
|
|
106
|
+
// combinations, while retaining the same scalar/list bounds as the
|
|
107
|
+
// generic parser and leaving endpoint discovery and authority untouched.
|
|
108
|
+
if (!isRootHop) {
|
|
109
|
+
if (isRegisterBg) {
|
|
110
|
+
const domainLine = text.match(/^[ \t]*DOMAIN NAME[ \t]*:[ \t]*([^\r\n]+)/im);
|
|
111
|
+
const rawDomainLine = domainLine?.[1];
|
|
112
|
+
if (rawDomainLine) {
|
|
113
|
+
const boundedLine = rawDomainLine.slice(0, MAX_WHOIS_FIELD_LENGTH);
|
|
114
|
+
const parenthesized = boundedLine.match(/\(([a-z0-9.-]{1,253})\)[ \t]*$/i)?.[1] || '';
|
|
115
|
+
const leadingAscii = boundedLine.match(/^([a-z0-9.-]{1,253})(?:[ \t]|$)/i)?.[1] || '';
|
|
116
|
+
const unicodeDomain = boundedLine.replace(/[ \t]+\([^\r\n()]{1,253}\)[ \t]*$/, '').trim();
|
|
117
|
+
const domain = [parenthesized, leadingAscii, domainToASCII(unicodeDomain)]
|
|
118
|
+
.find((candidate) => WHOIS_DOMAIN_RE.test(candidate));
|
|
119
|
+
const bounded = boundedWhoisValue(domain, whoisFieldLimit('domainName'));
|
|
120
|
+
if (bounded.value)
|
|
121
|
+
fields.domainName = bounded.value;
|
|
122
|
+
if (bounded.truncated)
|
|
123
|
+
truncatedFields.add('domainName');
|
|
124
|
+
}
|
|
125
|
+
for (const match of text.matchAll(/^[ \t]*registration status[ \t]*:[ \t]*([^\r\n]+)/gim)) {
|
|
126
|
+
if (addBoundedWhoisSetValue(statuses, match[1], {
|
|
127
|
+
maxEntries: MAX_WHOIS_STATUSES, maxLength: 160,
|
|
128
|
+
field: 'statuses', truncatedFields,
|
|
129
|
+
}) === 'capped')
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
collectBareWhoisNameservers(text, /^[ \t]*NAME SERVER INFORMATION[ \t]*:[ \t]*$/im, nameservers, truncatedFields);
|
|
133
|
+
}
|
|
134
|
+
if (isEif) {
|
|
135
|
+
const domainSection = parseBoundedWhoisSection(text, /^[ \t]*Domain[ \t]*:[ \t]*$/im);
|
|
136
|
+
const registrantSection = parseBoundedWhoisSection(text, /^[ \t]*Registrant[ \t]*:[ \t]*$/im);
|
|
137
|
+
const adminSection = parseBoundedWhoisSection(text, /^[ \t]*Administrative contact[ \t]*:[ \t]*$/im);
|
|
138
|
+
const techSection = parseBoundedWhoisSection(text, /^[ \t]*Technical contact[ \t]*:[ \t]*$/im);
|
|
139
|
+
const registrarSection = parseBoundedWhoisSection(text, /^[ \t]*Registrar[ \t]*:[ \t]*$/im);
|
|
140
|
+
assignBoundedWhoisMatch(domainSection, fields, 'domainName', /^[ \t]*name[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
141
|
+
assignBoundedWhoisMatch(registrantSection, fields, 'registrantName', /^[ \t]*name[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
142
|
+
assignBoundedWhoisMatch(registrantSection, fields, 'registrantId', /^[ \t]*org id[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
143
|
+
assignBoundedWhoisMatch(registrantSection, fields, 'registrantCountry', /^[ \t]*country[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
144
|
+
assignBoundedWhoisMatch(adminSection, fields, 'adminName', /^[ \t]*name[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
145
|
+
assignBoundedWhoisMatch(techSection, fields, 'techName', /^[ \t]*name[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
146
|
+
assignBoundedWhoisMatch(registrarSection, fields, 'registrar', /^[ \t]*name[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
147
|
+
assignBoundedWhoisMatch(registrarSection, fields, 'registrarUrl', /^[ \t]*url[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
148
|
+
}
|
|
149
|
+
if (isIszt) {
|
|
150
|
+
assignBoundedWhoisMatch(text, fields, 'createdDate', /^[ \t]*record created[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
151
|
+
}
|
|
152
|
+
if (isIsnic) {
|
|
153
|
+
const handle = text.match(/^[ \t]*registrant[ \t]*:[ \t]*(.+)$/im)?.[1] || null;
|
|
154
|
+
if (handle) {
|
|
155
|
+
const boundedHandle = boundedWhoisValue(handle, whoisFieldLimit('registrantId'));
|
|
156
|
+
if (boundedHandle.value)
|
|
157
|
+
fields.registrantId = boundedHandle.value;
|
|
158
|
+
if (boundedHandle.truncated)
|
|
159
|
+
truncatedFields.add('registrantId');
|
|
160
|
+
delete fields.registrantName;
|
|
161
|
+
const role = resolveIsnicRole(text, boundedHandle.value);
|
|
162
|
+
if (role) {
|
|
163
|
+
if (role.org.value)
|
|
164
|
+
fields.registrantOrg = role.org.value;
|
|
165
|
+
if (role.email.value)
|
|
166
|
+
fields.registrantEmail = role.email.value;
|
|
167
|
+
if (role.phone.value)
|
|
168
|
+
fields.registrantPhone = role.phone.value;
|
|
169
|
+
if (role.address.value)
|
|
170
|
+
fields.registrantAddress = role.address.value;
|
|
171
|
+
if (role.org.truncated)
|
|
172
|
+
truncatedFields.add('registrantOrg');
|
|
173
|
+
if (role.email.truncated)
|
|
174
|
+
truncatedFields.add('registrantEmail');
|
|
175
|
+
if (role.phone.truncated)
|
|
176
|
+
truncatedFields.add('registrantPhone');
|
|
177
|
+
if (role.address.truncated || role.truncated)
|
|
178
|
+
truncatedFields.add('registrantAddress');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
assignBoundedWhoisMatch(text, fields, 'adminId', /^[ \t]*admin-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
182
|
+
assignBoundedWhoisMatch(text, fields, 'techId', /^[ \t]*tech-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
183
|
+
assignBoundedWhoisMatch(text, fields, 'billingId', /^[ \t]*billing-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
184
|
+
}
|
|
185
|
+
if (isNicLv) {
|
|
186
|
+
const holderSection = parseBoundedWhoisSection(text, /^[ \t]*\[Holder\][ \t]*$/im);
|
|
187
|
+
assignBoundedWhoisMatch(holderSection, fields, 'registrantName', /^[ \t]*Name[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
188
|
+
assignBoundedWhoisMatch(holderSection, fields, 'registrantId', /^[ \t]*RegNr[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
189
|
+
assignBoundedWhoisMatch(holderSection, fields, 'registrantAddress', /^[ \t]*Address[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
190
|
+
assignBoundedWhoisMatch(holderSection, fields, 'registrantCountry', /^[ \t]*Country[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
191
|
+
}
|
|
192
|
+
if (isSidn) {
|
|
193
|
+
const registrar = parseIndentedWhoisValue(text, /^[ \t]*Registrar[ \t]*:[ \t]*$/im, whoisFieldLimit('registrar'));
|
|
194
|
+
if (registrar?.value)
|
|
195
|
+
fields.registrar = registrar.value;
|
|
196
|
+
if (registrar?.truncated)
|
|
197
|
+
truncatedFields.add('registrar');
|
|
198
|
+
const abuse = parseIndentedContactBlock(text, /^[ \t]*Abuse Contact[ \t]*:[ \t]*$/im);
|
|
199
|
+
if (abuse?.email)
|
|
200
|
+
fields.abuseEmail = abuse.email;
|
|
201
|
+
if (abuse?.phone)
|
|
202
|
+
fields.abusePhone = abuse.phone;
|
|
203
|
+
if (abuse?.truncated) {
|
|
204
|
+
truncatedFields.add('abuseEmail');
|
|
205
|
+
truncatedFields.add('abusePhone');
|
|
206
|
+
}
|
|
207
|
+
collectBareWhoisNameservers(text, /^[ \t]*Domain nameservers[ \t]*:[ \t]*$/im, nameservers, truncatedFields);
|
|
208
|
+
}
|
|
209
|
+
if (isRnids) {
|
|
210
|
+
assignBoundedWhoisMatch(text, fields, 'adminName', /^[ \t]*Administrative contact[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
211
|
+
assignBoundedWhoisMatch(text, fields, 'techName', /^[ \t]*Technical contact[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
212
|
+
assignBoundedWhoisMatch(text, fields, 'dnssec', /^[ \t]*DNSSEC signed[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
213
|
+
for (const match of text.matchAll(/^[ \t]*Domain status[ \t]*:[ \t]*(.*?)(?:[ \t]+https?:\/\/\S+)?[ \t]*$/gim)) {
|
|
214
|
+
if (addBoundedWhoisSetValue(statuses, match[1], {
|
|
215
|
+
maxEntries: MAX_WHOIS_STATUSES, maxLength: 160,
|
|
216
|
+
field: 'statuses', truncatedFields,
|
|
217
|
+
}) === 'capped')
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (isCctldBy) {
|
|
222
|
+
assignBoundedWhoisMatch(text, fields, 'registrantOrg', /^[ \t]*Org[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
223
|
+
assignBoundedWhoisMatch(text, fields, 'registrantId', /^[ \t]*Registration or other identification number[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
224
|
+
}
|
|
225
|
+
if (isHkirc) {
|
|
226
|
+
assignBoundedWhoisMatch(text, fields, 'createdDate', /^[ \t]*Domain Name Commencement Date[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
227
|
+
collectBareWhoisNameservers(text, /^[ \t]*Name Servers Information[ \t]*:[ \t]*$/im, nameservers, truncatedFields);
|
|
228
|
+
}
|
|
229
|
+
if (isIrnic) {
|
|
230
|
+
const registrantId = text.match(/^[ \t]*holder-c[ \t]*:[ \t]*(.+)$/im)?.[1] || null;
|
|
231
|
+
if (registrantId) {
|
|
232
|
+
assignBoundedWhoisMatch(text, fields, 'registrantId', /^[ \t]*holder-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
233
|
+
const contact = resolveIrnicContact(text, fields.registrantId);
|
|
234
|
+
if (contact) {
|
|
235
|
+
if (contact.name.value)
|
|
236
|
+
fields.registrantName = contact.name.value;
|
|
237
|
+
if (contact.org.value)
|
|
238
|
+
fields.registrantOrg = contact.org.value;
|
|
239
|
+
if (contact.email.value)
|
|
240
|
+
fields.registrantEmail = contact.email.value;
|
|
241
|
+
if (contact.phone.value)
|
|
242
|
+
fields.registrantPhone = contact.phone.value;
|
|
243
|
+
if (contact.address.value)
|
|
244
|
+
fields.registrantAddress = contact.address.value;
|
|
245
|
+
if (contact.name.truncated)
|
|
246
|
+
truncatedFields.add('registrantName');
|
|
247
|
+
if (contact.org.truncated)
|
|
248
|
+
truncatedFields.add('registrantOrg');
|
|
249
|
+
if (contact.email.truncated)
|
|
250
|
+
truncatedFields.add('registrantEmail');
|
|
251
|
+
if (contact.phone.truncated)
|
|
252
|
+
truncatedFields.add('registrantPhone');
|
|
253
|
+
if (contact.address.truncated || contact.truncated)
|
|
254
|
+
truncatedFields.add('registrantAddress');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
assignBoundedWhoisMatch(text, fields, 'adminId', /^[ \t]*admin-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
258
|
+
assignBoundedWhoisMatch(text, fields, 'techId', /^[ \t]*tech-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
259
|
+
assignBoundedWhoisMatch(text, fields, 'billingId', /^[ \t]*bill-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
260
|
+
}
|
|
261
|
+
if (isNicKz) {
|
|
262
|
+
assignBoundedWhoisMatch(text, fields, 'createdDate', /^[ \t]*Domain created[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
263
|
+
assignBoundedWhoisMatch(text, fields, 'updatedDate', /^[ \t]*Last modified[ \t.]*:[ \t]*(.+)$/im, truncatedFields);
|
|
264
|
+
assignBoundedWhoisMatch(text, fields, 'registrar', /^[ \t]*Current Registar[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
265
|
+
for (const pattern of [
|
|
266
|
+
/^[ \t]*Primary server[ \t.]*:[ \t]*([a-zA-Z0-9.-]+)/gim,
|
|
267
|
+
/^[ \t]*Secondary server[ \t.]*:[ \t]*([a-zA-Z0-9.-]+)/gim,
|
|
268
|
+
]) {
|
|
269
|
+
for (const match of text.matchAll(pattern)) {
|
|
270
|
+
const result = addBoundedWhoisSetValue(nameservers, match[1], {
|
|
271
|
+
maxEntries: MAX_WHOIS_NAMESERVERS, maxLength: 253,
|
|
272
|
+
field: 'nameservers', truncatedFields,
|
|
273
|
+
});
|
|
274
|
+
if (result === 'capped')
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (isDnsLu) {
|
|
280
|
+
assignBoundedWhoisMatch(text, fields, 'domainName', /^[ \t]*domainname[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
281
|
+
assignBoundedWhoisMatch(text, fields, 'registrar', /^[ \t]*registrar-name[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
282
|
+
assignBoundedWhoisMatch(text, fields, 'registrarUrl', /^[ \t]*registrar-url[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
283
|
+
}
|
|
284
|
+
if (isNicMd) {
|
|
285
|
+
assignBoundedWhoisMatch(text, fields, 'domainName', /^[ \t]*Domain[ \t]+name[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
286
|
+
for (const match of text.matchAll(/^[ \t]*Domain state[ \t]*:[ \t]*([^\r\n]+)/gim)) {
|
|
287
|
+
if (addBoundedWhoisSetValue(statuses, match[1], {
|
|
288
|
+
maxEntries: MAX_WHOIS_STATUSES, maxLength: 160,
|
|
289
|
+
field: 'statuses', truncatedFields,
|
|
290
|
+
}) === 'capped')
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (isThnic) {
|
|
295
|
+
assignBoundedWhoisMatch(text, fields, 'createdDate', /^[ \t]*Created date[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
296
|
+
assignBoundedWhoisMatch(text, fields, 'expiryDate', /^[ \t]*Exp date[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
297
|
+
assignBoundedWhoisMatch(text, fields, 'registrantOrg', /^[ \t]*Domain Holder Organization[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
298
|
+
assignBoundedWhoisMatch(text, fields, 'registrantCountry', /^[ \t]*Domain Holder Country[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
299
|
+
assignBoundedWhoisMatch(text, fields, 'registrantStreet', /^[ \t]*Domain Holder Street[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
300
|
+
}
|
|
301
|
+
if (isMonic) {
|
|
302
|
+
assignBoundedWhoisMatch(text, fields, 'createdDate', /^[ \t]*Record created on[ \t]+(.+)$/im, truncatedFields);
|
|
303
|
+
collectBareWhoisNameservers(text, /^[ \t]*Domain name servers[ \t]*:[ \t]*$/im, nameservers, truncatedFields);
|
|
304
|
+
}
|
|
305
|
+
if (isNicKg) {
|
|
306
|
+
const domainHeader = text.match(/^[ \t]*Domain[ \t]+([a-z0-9.-]+)[ \t]+\(([A-Z][A-Z0-9_-]*)\)[ \t]*$/im);
|
|
307
|
+
if (domainHeader) {
|
|
308
|
+
const domain = boundedWhoisValue(domainHeader[1], whoisFieldLimit('domainName'));
|
|
309
|
+
if (domain.value)
|
|
310
|
+
fields.domainName = domain.value;
|
|
311
|
+
if (domain.truncated)
|
|
312
|
+
truncatedFields.add('domainName');
|
|
313
|
+
addBoundedWhoisSetValue(statuses, domainHeader[2], {
|
|
314
|
+
maxEntries: MAX_WHOIS_STATUSES,
|
|
315
|
+
maxLength: 160,
|
|
316
|
+
field: 'statuses',
|
|
317
|
+
truncatedFields,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
assignBoundedWhoisMatch(text, fields, 'createdDate', /^[ \t]*Record created[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
321
|
+
assignBoundedWhoisMatch(text, fields, 'updatedDate', /^[ \t]*Record last updated on[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
322
|
+
assignBoundedWhoisMatch(text, fields, 'expiryDate', /^[ \t]*Record expires on[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
323
|
+
collectBareWhoisNameservers(text, /^[ \t]*Name servers in the listed order[ \t]*:[ \t]*$/im, nameservers, truncatedFields);
|
|
324
|
+
}
|
|
325
|
+
const isRegistroBr = /^[ \t]*owner-c[ \t]*:/im.test(text)
|
|
326
|
+
&& /^[ \t]*country[ \t]*:[ \t]*BR(?:\s|$)/im.test(text);
|
|
327
|
+
if (isRegistroBr) {
|
|
328
|
+
assignBoundedWhoisMatch(text, fields, 'registrantName', /^[ \t]*owner[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
329
|
+
assignBoundedWhoisMatch(text, fields, 'registrantId', /^[ \t]*owner-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
330
|
+
assignBoundedWhoisMatch(text, fields, 'adminId', /^[ \t]*admin-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
331
|
+
assignBoundedWhoisMatch(text, fields, 'techId', /^[ \t]*tech-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
332
|
+
}
|
|
333
|
+
const isAfnic = /^[ \t]*source[ \t]*:[ \t]*FRNIC(?:\s|$)/im.test(text);
|
|
334
|
+
if (isAfnic) {
|
|
335
|
+
assignBoundedWhoisMatch(text, fields, 'registrantId', /^[ \t]*holder-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
336
|
+
assignBoundedWhoisMatch(text, fields, 'adminId', /^[ \t]*admin-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
337
|
+
assignBoundedWhoisMatch(text, fields, 'techId', /^[ \t]*tech-c[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
338
|
+
for (const match of text.matchAll(/^[ \t]*eppstatus[ \t]*:[ \t]*(.+)$/gim)) {
|
|
339
|
+
if (addBoundedWhoisSetValue(statuses, match[1], {
|
|
340
|
+
maxEntries: MAX_WHOIS_STATUSES, maxLength: 160,
|
|
341
|
+
field: 'statuses', truncatedFields,
|
|
342
|
+
}) === 'capped')
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const isTci = /^[ \t]*source[ \t]*:[ \t]*TCI(?:\s|$)/im.test(text)
|
|
347
|
+
&& /^[ \t]*paid-till[ \t]*:/im.test(text);
|
|
348
|
+
if (isTci) {
|
|
349
|
+
assignBoundedWhoisMatch(text, fields, 'registrantOrg', /^[ \t]*org[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
350
|
+
for (const match of text.matchAll(/^[ \t]*state[ \t]*:[ \t]*(.+)$/gim)) {
|
|
351
|
+
if (addBoundedWhoisSetValue(statuses, match[1], {
|
|
352
|
+
maxEntries: MAX_WHOIS_STATUSES, maxLength: 160,
|
|
353
|
+
field: 'statuses', truncatedFields,
|
|
354
|
+
}) === 'capped')
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const isInternetstiftelsen = /^[ \t]*registry-lock[ \t]*:/im.test(text)
|
|
359
|
+
&& /^[ \t]*holder[ \t]*:/im.test(text);
|
|
360
|
+
if (isInternetstiftelsen) {
|
|
361
|
+
assignBoundedWhoisMatch(text, fields, 'registrantId', /^[ \t]*holder[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
362
|
+
for (const match of text.matchAll(/^[ \t]*state[ \t]*:[ \t]*(.+)$/gim)) {
|
|
363
|
+
if (addBoundedWhoisSetValue(statuses, match[1], {
|
|
364
|
+
maxEntries: MAX_WHOIS_STATUSES, maxLength: 160,
|
|
365
|
+
field: 'statuses', truncatedFields,
|
|
366
|
+
}) === 'capped')
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const isNask = /^[ \t]*DOMAIN NAME[ \t]*:/im.test(text)
|
|
371
|
+
&& /^[ \t]*registrant type[ \t]*:/im.test(text)
|
|
372
|
+
&& /^[ \t]*renewal date[ \t]*:/im.test(text)
|
|
373
|
+
&& /^[ \t]*REGISTRAR[ \t]*:[ \t]*$/im.test(text);
|
|
374
|
+
if (isNask) {
|
|
375
|
+
if (!fields.registrar) {
|
|
376
|
+
const registrarHeader = text.match(/^[ \t]*REGISTRAR[ \t]*:[ \t]*$/im);
|
|
377
|
+
const followingLines = registrarHeader
|
|
378
|
+
? text.slice((registrarHeader.index ?? 0) + registrarHeader[0].length).split('\n').slice(0, 8)
|
|
379
|
+
: [];
|
|
380
|
+
for (const line of followingLines) {
|
|
381
|
+
if (!line.trim())
|
|
382
|
+
continue;
|
|
383
|
+
const bounded = boundedWhoisValue(line, whoisFieldLimit('registrar'));
|
|
384
|
+
if (bounded.value && !/^[a-z][a-z0-9+.-]*:\/\//i.test(bounded.value)
|
|
385
|
+
&& !/^[a-z][a-z0-9 -]{0,50}:[ \t]/i.test(bounded.value)) {
|
|
386
|
+
fields.registrar = bounded.value;
|
|
387
|
+
}
|
|
388
|
+
if (bounded.truncated)
|
|
389
|
+
truncatedFields.add('registrar');
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const nameserverHeader = text.match(/^[ \t]*nameservers[ \t]*:[ \t]*(.*)$/im);
|
|
394
|
+
if (nameserverHeader) {
|
|
395
|
+
const candidates = [
|
|
396
|
+
nameserverHeader[1] ?? '',
|
|
397
|
+
...text.slice((nameserverHeader.index ?? 0) + nameserverHeader[0].length)
|
|
398
|
+
.split('\n').slice(1, MAX_WHOIS_NAMESERVERS + 2),
|
|
399
|
+
];
|
|
400
|
+
let found = 0;
|
|
401
|
+
for (const line of candidates) {
|
|
402
|
+
if (line === undefined)
|
|
403
|
+
continue;
|
|
404
|
+
const trimmed = line.trim();
|
|
405
|
+
if (!trimmed) {
|
|
406
|
+
if (found > 0)
|
|
407
|
+
break;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const hostMatch = trimmed.match(/^([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\.?(?:\s|$)/);
|
|
411
|
+
if (!hostMatch)
|
|
412
|
+
break;
|
|
413
|
+
const hostname = hostMatch[1];
|
|
414
|
+
if (!hostname)
|
|
415
|
+
break;
|
|
416
|
+
const result = addBoundedWhoisSetValue(nameservers, hostname.replace(/\.$/, ''), {
|
|
417
|
+
maxEntries: MAX_WHOIS_NAMESERVERS, maxLength: 253,
|
|
418
|
+
field: 'nameservers', truncatedFields,
|
|
419
|
+
});
|
|
420
|
+
if (result === 'capped')
|
|
421
|
+
break;
|
|
422
|
+
if (result === 'added')
|
|
423
|
+
found += 1;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if ((isDnsBelgium || isEurid) && !fields.registrar) {
|
|
428
|
+
const registrar = parseIndentedWhoisSubfield(text, /^[ \t]*Registrar[ \t]*:[ \t]*$/im, /^[ \t]*Name[ \t]*:[ \t]*(.+)$/i, whoisFieldLimit('registrar'));
|
|
429
|
+
if (registrar?.value)
|
|
430
|
+
fields.registrar = registrar.value;
|
|
431
|
+
if (registrar?.truncated)
|
|
432
|
+
truncatedFields.add('registrar');
|
|
433
|
+
}
|
|
434
|
+
if (isNorid) {
|
|
435
|
+
assignBoundedWhoisMatch(text, fields, 'registryDomainId', /^[ \t]*NORID Handle[ \t.]*:[ \t]*(.+)$/im, truncatedFields);
|
|
436
|
+
assignBoundedWhoisMatch(text, fields, 'registrar', /^[ \t]*Registrar Handle[ \t.]*:[ \t]*(.+)$/im, truncatedFields);
|
|
437
|
+
}
|
|
438
|
+
// These aliases are meaningful only inside their registry's
|
|
439
|
+
// distinctive response dialect. Keeping them marker-gated avoids
|
|
440
|
+
// treating generic contact IDs, validity text, or service-provider
|
|
441
|
+
// prose as domain-level evidence in unrelated WHOIS responses.
|
|
442
|
+
if (isCnnic) {
|
|
443
|
+
assignBoundedWhoisMatch(text, fields, 'registryDomainId', /^[ \t]*ROID[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
444
|
+
assignBoundedWhoisMatch(text, fields, 'expiryDate', /^[ \t]*Expiration Time[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
445
|
+
}
|
|
446
|
+
if (isPandi) {
|
|
447
|
+
assignBoundedWhoisMatch(text, fields, 'registryDomainId', /^[ \t]*Domain ID[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
448
|
+
assignBoundedWhoisMatch(text, fields, 'registrar', /^[ \t]*Sponsoring Registrar Organization[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
449
|
+
}
|
|
450
|
+
if (isIsocIl) {
|
|
451
|
+
assignBoundedWhoisMatch(text, fields, 'expiryDate', /^[ \t]*validity[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
452
|
+
}
|
|
453
|
+
if (isTwnic) {
|
|
454
|
+
assignBoundedWhoisMatch(text, fields, 'registrar', /^[ \t]*Registration Service Provider[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
455
|
+
assignBoundedWhoisMatch(text, fields, 'createdDate', /^[ \t]*Record created on[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
456
|
+
assignBoundedWhoisMatch(text, fields, 'expiryDate', /^[ \t]*Record expires on[ \t]*:[ \t]*(.+)$/im, truncatedFields);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Sectioned legacy port-43 responses use a distinctive pair of section
|
|
460
|
+
// headers and place several values on following indented lines. Gate the
|
|
461
|
+
// dialect on both markers so generic responses with one blank field are
|
|
462
|
+
// not reinterpreted. This is parser compatibility only: IANA still
|
|
463
|
+
// selects the endpoint and authority analysis remains unchanged.
|
|
464
|
+
const isSectionedRegistryResponse = !isRootHop
|
|
465
|
+
&& /^[ \t]*Relevant dates[ \t]*:[ \t]*$/im.test(text)
|
|
466
|
+
&& /^[ \t]*Registration status[ \t]*:[ \t]*$/im.test(text);
|
|
467
|
+
if (isSectionedRegistryResponse) {
|
|
468
|
+
for (const [key, headerRe] of [
|
|
469
|
+
['domainName', /^[ \t]*Domain(?: name)?[ \t]*:[ \t]*$/im],
|
|
470
|
+
['registrantName', /^[ \t]*Registrant[ \t]*:[ \t]*$/im],
|
|
471
|
+
['registrar', /^[ \t]*Registrar[ \t]*:[ \t]*$/im],
|
|
472
|
+
]) {
|
|
473
|
+
if (fields[key])
|
|
474
|
+
continue;
|
|
475
|
+
const parsed = parseIndentedWhoisValue(text, headerRe, whoisFieldLimit(key));
|
|
476
|
+
if (!parsed?.value)
|
|
477
|
+
continue;
|
|
478
|
+
fields[key] = parsed.value;
|
|
479
|
+
if (parsed.truncated)
|
|
480
|
+
truncatedFields.add(key);
|
|
481
|
+
}
|
|
482
|
+
const registrationStatus = parseIndentedWhoisValue(text, /^[ \t]*Registration status[ \t]*:[ \t]*$/im, 160);
|
|
483
|
+
if (registrationStatus?.value) {
|
|
484
|
+
addBoundedWhoisSetValue(statuses, registrationStatus.value, {
|
|
485
|
+
maxEntries: MAX_WHOIS_STATUSES,
|
|
486
|
+
maxLength: 160,
|
|
487
|
+
field: 'statuses',
|
|
488
|
+
truncatedFields,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
const isChannelIslandsResponse = /^[ \t]*Domain Status[ \t]*:[ \t]*$/im.test(text)
|
|
492
|
+
&& /^[ \t]*WHOIS lookup made on[ \t]+[^\r\n]+$/im.test(text);
|
|
493
|
+
if (isChannelIslandsResponse) {
|
|
494
|
+
assignBoundedWhoisMatch(text, fields, 'createdDate', /^[ \t]*Registered on[ \t]+(.+)$/im, truncatedFields);
|
|
495
|
+
const domainStatus = parseIndentedWhoisValue(text, /^[ \t]*Domain Status[ \t]*:[ \t]*$/im, 160);
|
|
496
|
+
if (domainStatus?.value) {
|
|
497
|
+
addBoundedWhoisSetValue(statuses, domainStatus.value, {
|
|
498
|
+
maxEntries: MAX_WHOIS_STATUSES,
|
|
499
|
+
maxLength: 160,
|
|
500
|
+
field: 'statuses',
|
|
501
|
+
truncatedFields,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
isDnsBelgium,
|
|
508
|
+
isEurid,
|
|
509
|
+
isNorid,
|
|
510
|
+
isCnnic,
|
|
511
|
+
isPunktum,
|
|
512
|
+
isPandi,
|
|
513
|
+
isIsocIl,
|
|
514
|
+
isTwnic,
|
|
515
|
+
isRegisterBg,
|
|
516
|
+
isEif,
|
|
517
|
+
isIszt,
|
|
518
|
+
isIsnic,
|
|
519
|
+
isNicLv,
|
|
520
|
+
isSidn,
|
|
521
|
+
isRnids,
|
|
522
|
+
isCctldBy,
|
|
523
|
+
isHkirc,
|
|
524
|
+
isIrnic,
|
|
525
|
+
isNicKz,
|
|
526
|
+
isDnsLu,
|
|
527
|
+
isNicMd,
|
|
528
|
+
isThnic,
|
|
529
|
+
isAtiTn,
|
|
530
|
+
isMonic,
|
|
531
|
+
isNicKg,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
export { applyWhoisDialects };
|