@slicedearth/whoisleuth-cli 1.32.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/LICENSE +661 -0
- package/LICENSES/Retire.js-Apache-2.0.txt +199 -0
- package/NOTICE +76 -0
- package/README.md +84 -0
- package/TRADEMARKS.md +41 -0
- package/bin/whoisleuth.mjs +35 -0
- package/cli/archive-inspect.mjs +198 -0
- package/cli/arguments.mjs +1022 -0
- package/cli/artifact-verify.mjs +210 -0
- package/cli/bulk-checkpoint.mjs +218 -0
- package/cli/bulk-command-runner.mjs +109 -0
- package/cli/bulk-output.mjs +112 -0
- package/cli/bulk.mjs +133 -0
- package/cli/compare.mjs +238 -0
- package/cli/completion.mjs +175 -0
- package/cli/csv.mjs +1 -0
- package/cli/discover.mjs +50 -0
- package/cli/discovery-command-runner.mjs +36 -0
- package/cli/discovery-observation-snapshot.mjs +315 -0
- package/cli/discovery-scan-command-runner.mjs +141 -0
- package/cli/discovery-scan.mjs +325 -0
- package/cli/discovery-snapshot.mjs +133 -0
- package/cli/discovery-workflow.mjs +73 -0
- package/cli/doctor.mjs +137 -0
- package/cli/errors.mjs +21 -0
- package/cli/evidence-command-arguments.mjs +145 -0
- package/cli/evidence-command-runner.mjs +106 -0
- package/cli/evidence-signing.mjs +215 -0
- package/cli/exit-codes.mjs +9 -0
- package/cli/export-evidence.mjs +28 -0
- package/cli/formatters/evidence-report.mjs +3 -0
- package/cli/formatters/html.mjs +69 -0
- package/cli/formatters/json.mjs +135 -0
- package/cli/formatters/markdown.mjs +3 -0
- package/cli/formatters/terminal.mjs +658 -0
- package/cli/http.mjs +27 -0
- package/cli/lookup-command-runner.mjs +87 -0
- package/cli/lookup-diff.mjs +171 -0
- package/cli/mail-review.mjs +204 -0
- package/cli/manual.mjs +34 -0
- package/cli/network-command-runner.mjs +88 -0
- package/cli/output-file.mjs +97 -0
- package/cli/page-compare.mjs +123 -0
- package/cli/posture.mjs +20 -0
- package/cli/progress-events.mjs +53 -0
- package/cli/progress.mjs +56 -0
- package/cli/registry-access.mjs +3 -0
- package/cli/registry-support.mjs +120 -0
- package/cli/risk-calibration.mjs +294 -0
- package/cli/runner-types.mjs +1 -0
- package/cli/runner.mjs +726 -0
- package/cli/saved-lookup.mjs +87 -0
- package/cli/source-reliability.mjs +727 -0
- package/cli/strict-exit.mjs +55 -0
- package/cli/terminal-presentation.mjs +147 -0
- package/docs/cli.md +827 -0
- package/frontend/src/lib/analysis/acquisition-decision-packet.js +93 -0
- package/frontend/src/lib/analysis/acquisition-due-diligence.js +391 -0
- package/frontend/src/lib/analysis/artifact-integrity.js +21 -0
- package/frontend/src/lib/analysis/brand-profile-model.js +421 -0
- package/frontend/src/lib/analysis/bulk-domain-comparison.js +246 -0
- package/frontend/src/lib/analysis/bulk-mail-exposure.js +192 -0
- package/frontend/src/lib/analysis/bulk-retry-plan.js +86 -0
- package/frontend/src/lib/analysis/bulk-review-export.js +62 -0
- package/frontend/src/lib/analysis/bulk-review-model.js +266 -0
- package/frontend/src/lib/analysis/bulk-session-model.js +515 -0
- package/frontend/src/lib/analysis/bulk-sort.js +49 -0
- package/frontend/src/lib/analysis/campaign-model.js +265 -0
- package/frontend/src/lib/analysis/case-evidence-model.js +642 -0
- package/frontend/src/lib/analysis/case-migration-model.js +248 -0
- package/frontend/src/lib/analysis/case-model.js +5 -0
- package/frontend/src/lib/analysis/case-record-contracts.js +44 -0
- package/frontend/src/lib/analysis/case-record-core.js +232 -0
- package/frontend/src/lib/analysis/case-record-model.js +7 -0
- package/frontend/src/lib/analysis/case-record-operations.js +224 -0
- package/frontend/src/lib/analysis/case-response-model.js +640 -0
- package/frontend/src/lib/analysis/case-response-packet.js +573 -0
- package/frontend/src/lib/analysis/case-storage-model.js +93 -0
- package/frontend/src/lib/analysis/detection-rule-model.js +306 -0
- package/frontend/src/lib/analysis/dns-record-normalization.js +11 -0
- package/frontend/src/lib/analysis/http-summary.js +165 -0
- package/frontend/src/lib/analysis/investigation-guide.js +500 -0
- package/frontend/src/lib/analysis/investigation-template-model.js +144 -0
- package/frontend/src/lib/analysis/page-baseline.js +272 -0
- package/frontend/src/lib/analysis/page-similarity.js +179 -0
- package/frontend/src/lib/analysis/relationship-evidence.js +265 -0
- package/frontend/src/lib/analysis/relationship-observation-model.js +423 -0
- package/frontend/src/lib/analysis/scoring.js +135 -0
- package/frontend/src/lib/analysis/shortlist-model.js +209 -0
- package/frontend/src/lib/analysis/utils.js +265 -0
- package/frontend/src/lib/analysis/watchlist-history.js +643 -0
- package/frontend/src/lib/analysis/watchlist-store.js +127 -0
- package/frontend/src/lib/analysis/website-snapshot-model.js +302 -0
- package/frontend/src/lib/analysis/workspace-archive-crypto.js +282 -0
- package/frontend/src/lib/analysis/workspace-archive.js +472 -0
- package/lib/abort.mjs +26 -0
- package/lib/abusech-auth.mjs +19 -0
- package/lib/availability.mjs +716 -0
- package/lib/bounded-contract-normalizers.mjs +61 -0
- package/lib/browser-library-profile.mjs +310 -0
- package/lib/bulk-comparison-evidence.mjs +83 -0
- package/lib/classify.mjs +93 -0
- package/lib/client-behavior-profile.mjs +83 -0
- package/lib/credential-surface-profile.mjs +66 -0
- package/lib/csv.mjs +23 -0
- package/lib/ct-query.mjs +27 -0
- package/lib/ct-search.mjs +393 -0
- package/lib/distributed-operation-budget.mjs +461 -0
- package/lib/dns-delegation-health.mjs +344 -0
- package/lib/dns-intelligence.mjs +739 -0
- package/lib/dns-mx.mjs +24 -0
- package/lib/dns-resolver-selection.mjs +33 -0
- package/lib/domain-posture-analysis.mjs +254 -0
- package/lib/domain-posture-parsers.mjs +358 -0
- package/lib/domain-posture.mjs +745 -0
- package/lib/error-detail.mjs +10 -0
- package/lib/evidence-export.mjs +431 -0
- package/lib/evidence-report-markdown.mjs +71 -0
- package/lib/evidence-report.mjs +391 -0
- package/lib/external-intelligence-risk.mjs +108 -0
- package/lib/favicon.mjs +146 -0
- package/lib/feature-policy.mjs +70 -0
- package/lib/generated/retire-browser-catalog.mjs +8724 -0
- package/lib/generated/unicode-confusables-17.mjs +93 -0
- package/lib/hostname.mjs +38 -0
- package/lib/html-signals.mjs +680 -0
- package/lib/http-intelligence.mjs +232 -0
- package/lib/idn-confusable-policy.mjs +86 -0
- package/lib/idn-confusables.mjs +465 -0
- package/lib/lookup-cache.mjs +130 -0
- package/lib/lookup-progress.mjs +291 -0
- package/lib/lookup-source-progress.mjs +109 -0
- package/lib/lookup-threat-provider-inventory.mjs +13 -0
- package/lib/lookup.mjs +592 -0
- package/lib/observation.mjs +114 -0
- package/lib/observed-network-context.mjs +355 -0
- package/lib/operation-budget.mjs +489 -0
- package/lib/page-fingerprints.mjs +481 -0
- package/lib/page-role-profile.mjs +117 -0
- package/lib/perceptual-hash.mjs +451 -0
- package/lib/rdap-attempts.mjs +34 -0
- package/lib/rdap-bootstrap.mjs +173 -0
- package/lib/rdap-capabilities.mjs +292 -0
- package/lib/rdap-client.mjs +125 -0
- package/lib/rdap-normalization.mjs +691 -0
- package/lib/rdap-registrar.mjs +196 -0
- package/lib/rdap-transport.mjs +39 -0
- package/lib/rdap-types.mjs +1 -0
- package/lib/rdap-validation.mjs +94 -0
- package/lib/rdap.mjs +16 -0
- package/lib/registry-access.mjs +32 -0
- package/lib/registry-capabilities.mjs +117 -0
- package/lib/registry-capability-catalogue.mjs +1206 -0
- package/lib/registry-comparison.mjs +318 -0
- package/lib/registry-dates.mjs +190 -0
- package/lib/registry-insights.mjs +325 -0
- package/lib/response-policy.mjs +256 -0
- package/lib/risk-scoring.mjs +164 -0
- package/lib/safe-fetch.mjs +446 -0
- package/lib/security-txt.mjs +359 -0
- package/lib/service-binding-dns.mjs +608 -0
- package/lib/sslbl-certificates.generated.mjs +143 -0
- package/lib/sslbl-intelligence.mjs +241 -0
- package/lib/static-html-analysis.mjs +340 -0
- package/lib/structured-data-identity.mjs +323 -0
- package/lib/threat-intelligence-contract.mjs +5 -0
- package/lib/threat-intelligence-definition-registry.mjs +34 -0
- package/lib/threat-intelligence-fixtures.mjs +82 -0
- package/lib/threat-intelligence-manifests.mjs +512 -0
- package/lib/threat-intelligence-runtime.mjs +662 -0
- package/lib/threat-intelligence-targets.mjs +168 -0
- package/lib/threat-intelligence-types.mjs +3 -0
- package/lib/threatfox-intelligence.mjs +307 -0
- package/lib/tls-intelligence.mjs +814 -0
- package/lib/typosquat-generator.mjs +884 -0
- package/lib/urlhaus-intelligence.mjs +361 -0
- package/lib/urlscan-intelligence.mjs +349 -0
- package/lib/website-security-posture.mjs +325 -0
- package/lib/website-technology.mjs +437 -0
- package/lib/whois-authority.mjs +98 -0
- package/lib/whois-chain.mjs +261 -0
- package/lib/whois-contacts.mjs +163 -0
- package/lib/whois-contracts.mjs +3 -0
- package/lib/whois-normalization.mjs +27 -0
- package/lib/whois-transport.mjs +100 -0
- package/lib/whois-values.mjs +92 -0
- package/lib/whois.mjs +1187 -0
- package/package.json +55 -0
- package/third-party-notices.txt +5571 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
// Bounded normalization of untrusted RDAP response objects.
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import { registryDateIso } from "./registry-dates.mjs";
|
|
4
|
+
import {} from "./rdap-types.mjs";
|
|
5
|
+
const MAX_RDAP_ENTITIES = 100;
|
|
6
|
+
const MAX_RDAP_ENTITY_DEPTH = 6;
|
|
7
|
+
const MAX_ENTITIES_PER_ROLE = 5;
|
|
8
|
+
const MAX_VCARD_ENTRIES = 100;
|
|
9
|
+
const MAX_ENTITY_ROLES = 12;
|
|
10
|
+
const MAX_CONTACT_VALUES = 8;
|
|
11
|
+
const MAX_ENTITY_LINKS = 10;
|
|
12
|
+
const MAX_RDAP_LINKS = 20;
|
|
13
|
+
const MAX_RDAP_REDACTIONS = 100;
|
|
14
|
+
const MAX_RDAP_VARIANT_GROUPS = 20;
|
|
15
|
+
const MAX_RDAP_VARIANT_NAMES = 50;
|
|
16
|
+
const MAX_RDAP_SERVER_TRUNCATION_REASONS = 8;
|
|
17
|
+
const RDAP_SERVER_TRUNCATION_TYPES = new Set([
|
|
18
|
+
'result set truncated due to authorization',
|
|
19
|
+
'result set truncated due to excessive load',
|
|
20
|
+
'result set truncated due to unexplainable reasons',
|
|
21
|
+
'object truncated due to authorization',
|
|
22
|
+
'object truncated due to excessive load',
|
|
23
|
+
'object truncated due to unexplainable reasons',
|
|
24
|
+
// RFC 9083 erratum 7986 corrects this registered value to singular.
|
|
25
|
+
'object truncated due to unexplainable reason',
|
|
26
|
+
]);
|
|
27
|
+
const RDAP_CONTACT_ROLES = new Set([
|
|
28
|
+
'registrar', 'registrant', 'administrative', 'technical', 'billing', 'abuse', 'noc',
|
|
29
|
+
'reseller', 'sponsor', 'proxy', 'notifications',
|
|
30
|
+
]);
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// RDAP response parsing (turns the raw JSON into a readable summary)
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
function boundedString(value, maxLength, { lower = false } = {}) {
|
|
35
|
+
if (typeof value !== 'string' || value.length > maxLength || /[\u0000-\u001f\u007f]/.test(value))
|
|
36
|
+
return null;
|
|
37
|
+
const normalized = value.replace(/\s+/g, ' ').trim();
|
|
38
|
+
if (!normalized)
|
|
39
|
+
return null;
|
|
40
|
+
return lower ? normalized.toLowerCase() : normalized;
|
|
41
|
+
}
|
|
42
|
+
function boundedInteger(value, min = 0, max = Number.MAX_SAFE_INTEGER) {
|
|
43
|
+
return Number.isSafeInteger(value) && value >= min && value <= max
|
|
44
|
+
? value
|
|
45
|
+
: null;
|
|
46
|
+
}
|
|
47
|
+
function truncatedText(value, maxLength) {
|
|
48
|
+
if (typeof value !== 'string' || /[\u0000-\u001f\u007f]/.test(value))
|
|
49
|
+
return null;
|
|
50
|
+
return value.replace(/\s+/g, ' ').trim().slice(0, maxLength).trim() || null;
|
|
51
|
+
}
|
|
52
|
+
function flattenScalarValues(value, maxValues = 32) {
|
|
53
|
+
const output = [];
|
|
54
|
+
const stack = [{ value, depth: 0 }];
|
|
55
|
+
let visited = 0;
|
|
56
|
+
while (stack.length && output.length < maxValues && visited < 128) {
|
|
57
|
+
const current = stack.pop();
|
|
58
|
+
if (!current)
|
|
59
|
+
continue;
|
|
60
|
+
visited += 1;
|
|
61
|
+
if (Array.isArray(current.value)) {
|
|
62
|
+
if (current.depth >= MAX_RDAP_ENTITY_DEPTH)
|
|
63
|
+
continue;
|
|
64
|
+
for (let i = Math.min(current.value.length, maxValues) - 1; i >= 0; i -= 1) {
|
|
65
|
+
stack.push({ value: current.value[i], depth: current.depth + 1 });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (typeof current.value === 'string') {
|
|
69
|
+
output.push(current.value);
|
|
70
|
+
}
|
|
71
|
+
else if (typeof current.value === 'number' && Number.isFinite(current.value)) {
|
|
72
|
+
output.push(String(current.value));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return output;
|
|
76
|
+
}
|
|
77
|
+
function vcardRawValues(vcardArray, field) {
|
|
78
|
+
if (!Array.isArray(vcardArray) || !Array.isArray(vcardArray[1]))
|
|
79
|
+
return [];
|
|
80
|
+
return vcardArray[1]
|
|
81
|
+
.slice(0, MAX_VCARD_ENTRIES)
|
|
82
|
+
.filter((entry) => Array.isArray(entry) && typeof entry[0] === 'string' && entry[0].toLowerCase() === field)
|
|
83
|
+
.map((entry) => entry[3]);
|
|
84
|
+
}
|
|
85
|
+
function normalizeContactValues(vcardArray, field, maxLength, { lower = false } = {}) {
|
|
86
|
+
const values = [];
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
for (const raw of vcardRawValues(vcardArray, field)) {
|
|
89
|
+
for (const scalar of flattenScalarValues(raw, MAX_CONTACT_VALUES)) {
|
|
90
|
+
const normalized = boundedString(scalar, maxLength, { lower });
|
|
91
|
+
if (!normalized)
|
|
92
|
+
continue;
|
|
93
|
+
const key = normalized.toLowerCase();
|
|
94
|
+
if (seen.has(key))
|
|
95
|
+
continue;
|
|
96
|
+
seen.add(key);
|
|
97
|
+
values.push(normalized);
|
|
98
|
+
if (values.length >= MAX_CONTACT_VALUES)
|
|
99
|
+
return values;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return values;
|
|
103
|
+
}
|
|
104
|
+
function normalizeAddresses(vcardArray) {
|
|
105
|
+
const addresses = [];
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
for (const raw of vcardRawValues(vcardArray, 'adr')) {
|
|
108
|
+
const parts = flattenScalarValues(raw, 32)
|
|
109
|
+
.map((part) => boundedString(part, 300))
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
const address = boundedString(parts.join(', '), 1000);
|
|
112
|
+
if (!address || seen.has(address.toLowerCase()))
|
|
113
|
+
continue;
|
|
114
|
+
seen.add(address.toLowerCase());
|
|
115
|
+
addresses.push(address);
|
|
116
|
+
if (addresses.length >= MAX_CONTACT_VALUES)
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
return addresses;
|
|
120
|
+
}
|
|
121
|
+
function contactValuesTruncated(vcardArray) {
|
|
122
|
+
for (const field of ['fn', 'org', 'email', 'tel']) {
|
|
123
|
+
let count = 0;
|
|
124
|
+
for (const raw of vcardRawValues(vcardArray, field)) {
|
|
125
|
+
count += flattenScalarValues(raw, MAX_CONTACT_VALUES + 1).length;
|
|
126
|
+
if (count > MAX_CONTACT_VALUES)
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return vcardRawValues(vcardArray, 'adr').length > MAX_CONTACT_VALUES;
|
|
131
|
+
}
|
|
132
|
+
function normalizeLinks(links, maxLinks = MAX_RDAP_LINKS) {
|
|
133
|
+
if (!Array.isArray(links))
|
|
134
|
+
return [];
|
|
135
|
+
const normalized = [];
|
|
136
|
+
for (const linkValue of links.slice(0, 100)) {
|
|
137
|
+
if (!linkValue || typeof linkValue !== 'object' || Array.isArray(linkValue))
|
|
138
|
+
continue;
|
|
139
|
+
const link = linkValue;
|
|
140
|
+
const href = boundedString(link.href, 2048);
|
|
141
|
+
if (!href)
|
|
142
|
+
continue;
|
|
143
|
+
try {
|
|
144
|
+
const parsed = new URL(href);
|
|
145
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
normalized.push({
|
|
152
|
+
rel: boundedString(link.rel, 100, { lower: true }),
|
|
153
|
+
href,
|
|
154
|
+
type: boundedString(link.type, 160, { lower: true }),
|
|
155
|
+
title: boundedString(link.title, 300),
|
|
156
|
+
});
|
|
157
|
+
if (normalized.length >= maxLinks)
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
return normalized;
|
|
161
|
+
}
|
|
162
|
+
function normalizePublicIds(publicIds) {
|
|
163
|
+
if (!Array.isArray(publicIds))
|
|
164
|
+
return [];
|
|
165
|
+
const normalized = [];
|
|
166
|
+
for (const itemValue of publicIds.slice(0, 100)) {
|
|
167
|
+
if (!itemValue || typeof itemValue !== 'object' || Array.isArray(itemValue))
|
|
168
|
+
continue;
|
|
169
|
+
const item = itemValue;
|
|
170
|
+
const type = boundedString(item.type, 160);
|
|
171
|
+
const identifier = boundedString(item.identifier, 300);
|
|
172
|
+
if (!type || !identifier)
|
|
173
|
+
continue;
|
|
174
|
+
normalized.push({ type, identifier });
|
|
175
|
+
if (normalized.length >= 20)
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
return normalized;
|
|
179
|
+
}
|
|
180
|
+
function normalizeStringList(value, maxItems, maxLength, { lower = false } = {}) {
|
|
181
|
+
if (!Array.isArray(value))
|
|
182
|
+
return [];
|
|
183
|
+
const output = [];
|
|
184
|
+
const seen = new Set();
|
|
185
|
+
for (const item of value.slice(0, Math.max(maxItems * 4, maxItems))) {
|
|
186
|
+
const normalized = boundedString(item, maxLength, { lower });
|
|
187
|
+
if (!normalized || seen.has(normalized.toLowerCase()))
|
|
188
|
+
continue;
|
|
189
|
+
seen.add(normalized.toLowerCase());
|
|
190
|
+
output.push(normalized);
|
|
191
|
+
if (output.length >= maxItems)
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
return output;
|
|
195
|
+
}
|
|
196
|
+
function redactionLabel(value) {
|
|
197
|
+
if (typeof value === 'string')
|
|
198
|
+
return boundedString(value, 300);
|
|
199
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
200
|
+
return null;
|
|
201
|
+
const record = value;
|
|
202
|
+
return boundedString(record.type, 160) || boundedString(record.description, 300);
|
|
203
|
+
}
|
|
204
|
+
function normalizeRedactions(redacted) {
|
|
205
|
+
if (!Array.isArray(redacted))
|
|
206
|
+
return { redactions: [], redactionsTruncated: false };
|
|
207
|
+
const redactions = [];
|
|
208
|
+
const candidates = redacted.slice(0, MAX_RDAP_REDACTIONS * 2);
|
|
209
|
+
let stoppedAt = candidates.length;
|
|
210
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
211
|
+
const itemValue = candidates[index];
|
|
212
|
+
if (!itemValue || typeof itemValue !== 'object' || Array.isArray(itemValue))
|
|
213
|
+
continue;
|
|
214
|
+
const item = itemValue;
|
|
215
|
+
const entry = {
|
|
216
|
+
name: redactionLabel(item.name),
|
|
217
|
+
reason: redactionLabel(item.reason),
|
|
218
|
+
method: boundedString(item.method, 80, { lower: true }),
|
|
219
|
+
pathLanguage: boundedString(item.pathLang, 80, { lower: true }),
|
|
220
|
+
prePath: boundedString(item.prePath, 512),
|
|
221
|
+
postPath: boundedString(item.postPath, 512),
|
|
222
|
+
replacementPath: boundedString(item.replacementPath, 512),
|
|
223
|
+
};
|
|
224
|
+
if (!Object.values(entry).some(Boolean))
|
|
225
|
+
continue;
|
|
226
|
+
redactions.push(entry);
|
|
227
|
+
if (redactions.length >= MAX_RDAP_REDACTIONS) {
|
|
228
|
+
stoppedAt = index + 1;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
redactions,
|
|
234
|
+
redactionsTruncated: stoppedAt < redacted.length,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function normalizeDomainVariants(value) {
|
|
238
|
+
if (!Array.isArray(value))
|
|
239
|
+
return { variants: [], variantsTruncated: false };
|
|
240
|
+
const variants = [];
|
|
241
|
+
let truncated = value.length > MAX_RDAP_VARIANT_GROUPS;
|
|
242
|
+
for (const groupValue of value.slice(0, MAX_RDAP_VARIANT_GROUPS)) {
|
|
243
|
+
if (!groupValue || typeof groupValue !== 'object' || Array.isArray(groupValue))
|
|
244
|
+
continue;
|
|
245
|
+
const group = groupValue;
|
|
246
|
+
const sourceNames = Array.isArray(group.variantNames) ? group.variantNames : [];
|
|
247
|
+
const variantNames = [];
|
|
248
|
+
for (const nameValue of sourceNames.slice(0, MAX_RDAP_VARIANT_NAMES * 2)) {
|
|
249
|
+
if (!nameValue || typeof nameValue !== 'object' || Array.isArray(nameValue))
|
|
250
|
+
continue;
|
|
251
|
+
const name = nameValue;
|
|
252
|
+
const ldhName = boundedString(name.ldhName, 253);
|
|
253
|
+
const unicodeName = boundedString(name.unicodeName, 253);
|
|
254
|
+
if (!ldhName && !unicodeName)
|
|
255
|
+
continue;
|
|
256
|
+
variantNames.push({ ldhName, unicodeName });
|
|
257
|
+
if (variantNames.length >= MAX_RDAP_VARIANT_NAMES)
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
if (sourceNames.length > MAX_RDAP_VARIANT_NAMES)
|
|
261
|
+
truncated = true;
|
|
262
|
+
const relation = normalizeStringList(group.relation, 20, 100, { lower: true });
|
|
263
|
+
const idnTable = boundedString(group.idnTable, 300);
|
|
264
|
+
if (!variantNames.length && !relation.length && !idnTable)
|
|
265
|
+
continue;
|
|
266
|
+
variants.push({ relation, idnTable, variantNames });
|
|
267
|
+
}
|
|
268
|
+
return { variants, variantsTruncated: truncated };
|
|
269
|
+
}
|
|
270
|
+
function publicId(entity, typePattern) {
|
|
271
|
+
const match = entity && Array.isArray(entity.publicIds)
|
|
272
|
+
? entity.publicIds.find((item) => typePattern.test(String(item.type || '')))
|
|
273
|
+
: null;
|
|
274
|
+
return match ? match.identifier : null;
|
|
275
|
+
}
|
|
276
|
+
function textWouldTruncate(value, maxLength) {
|
|
277
|
+
if (typeof value !== 'string' || /[\u0000-\u001f\u007f]/.test(value))
|
|
278
|
+
return false;
|
|
279
|
+
return value.replace(/\s+/g, ' ').trim().length > maxLength;
|
|
280
|
+
}
|
|
281
|
+
function summarizeTextBlocks(blocks) {
|
|
282
|
+
if (!Array.isArray(blocks))
|
|
283
|
+
return { items: [], truncated: false };
|
|
284
|
+
const output = [];
|
|
285
|
+
let truncated = blocks.length > 50;
|
|
286
|
+
for (const blockValue of blocks.slice(0, 50)) {
|
|
287
|
+
if (!blockValue || typeof blockValue !== 'object' || Array.isArray(blockValue))
|
|
288
|
+
continue;
|
|
289
|
+
const block = blockValue;
|
|
290
|
+
const descriptions = [];
|
|
291
|
+
const sourceDescriptions = Array.isArray(block.description) ? block.description : [];
|
|
292
|
+
if (sourceDescriptions.length > 20)
|
|
293
|
+
truncated = true;
|
|
294
|
+
for (const text of sourceDescriptions.slice(0, 20)) {
|
|
295
|
+
if (textWouldTruncate(text, 800))
|
|
296
|
+
truncated = true;
|
|
297
|
+
const description = truncatedText(text, 800);
|
|
298
|
+
if (!description)
|
|
299
|
+
continue;
|
|
300
|
+
if (descriptions.length < 6)
|
|
301
|
+
descriptions.push(description);
|
|
302
|
+
else
|
|
303
|
+
truncated = true;
|
|
304
|
+
}
|
|
305
|
+
if (!descriptions.length)
|
|
306
|
+
continue;
|
|
307
|
+
if (textWouldTruncate(block.title, 160))
|
|
308
|
+
truncated = true;
|
|
309
|
+
const item = {
|
|
310
|
+
title: truncatedText(block.title, 160) || 'Notice',
|
|
311
|
+
type: boundedString(block.type, 160, { lower: true }),
|
|
312
|
+
descriptions,
|
|
313
|
+
};
|
|
314
|
+
if (output.length < 12)
|
|
315
|
+
output.push(item);
|
|
316
|
+
else
|
|
317
|
+
truncated = true;
|
|
318
|
+
}
|
|
319
|
+
return { items: output, truncated };
|
|
320
|
+
}
|
|
321
|
+
function normalizeServerTruncationType(value) {
|
|
322
|
+
const type = boundedString(value, 160, { lower: true });
|
|
323
|
+
return type && RDAP_SERVER_TRUNCATION_TYPES.has(type) ? type : null;
|
|
324
|
+
}
|
|
325
|
+
function summarizeServerTruncation(...groups) {
|
|
326
|
+
const reasons = new Set();
|
|
327
|
+
for (const blocks of groups) {
|
|
328
|
+
if (!Array.isArray(blocks))
|
|
329
|
+
continue;
|
|
330
|
+
// Match the same bounded source window used by the text-block normalizer.
|
|
331
|
+
// If a server publishes more blocks, noticesTruncated/remarksTruncated
|
|
332
|
+
// separately disclose that the local inspection window was exceeded.
|
|
333
|
+
for (const blockValue of blocks.slice(0, 50)) {
|
|
334
|
+
if (!blockValue || typeof blockValue !== 'object' || Array.isArray(blockValue))
|
|
335
|
+
continue;
|
|
336
|
+
const type = normalizeServerTruncationType(blockValue.type);
|
|
337
|
+
if (!type)
|
|
338
|
+
continue;
|
|
339
|
+
reasons.add(type);
|
|
340
|
+
if (reasons.size >= MAX_RDAP_SERVER_TRUNCATION_REASONS)
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
if (reasons.size >= MAX_RDAP_SERVER_TRUNCATION_REASONS)
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
return [...reasons].sort();
|
|
347
|
+
}
|
|
348
|
+
function boundedEventString(value, maxLength) {
|
|
349
|
+
if (typeof value !== 'string' || value.length > maxLength || /[\u0000-\u001f\u007f]/.test(value))
|
|
350
|
+
return null;
|
|
351
|
+
return value.trim() || null;
|
|
352
|
+
}
|
|
353
|
+
function normalizeRdapEvents(value) {
|
|
354
|
+
if (!Array.isArray(value))
|
|
355
|
+
return [];
|
|
356
|
+
return value.slice(0, 100).map((event) => {
|
|
357
|
+
if (!event || typeof event !== 'object' || Array.isArray(event))
|
|
358
|
+
return null;
|
|
359
|
+
const record = event;
|
|
360
|
+
const action = boundedEventString(record.eventAction, 100)?.toLowerCase().replace(/\s+/g, ' ') || null;
|
|
361
|
+
const date = boundedEventString(record.eventDate, 64);
|
|
362
|
+
const actor = boundedEventString(record.eventActor, 160);
|
|
363
|
+
return action || date ? { action, date, actor } : null;
|
|
364
|
+
}).filter((event) => event !== null);
|
|
365
|
+
}
|
|
366
|
+
function lifecycleDate(events, action, newest) {
|
|
367
|
+
let selected = null;
|
|
368
|
+
let selectedTime = newest ? -Infinity : Infinity;
|
|
369
|
+
for (const event of events) {
|
|
370
|
+
if (event.action !== action || !event.date)
|
|
371
|
+
continue;
|
|
372
|
+
const time = Date.parse(event.date);
|
|
373
|
+
if (!Number.isFinite(time))
|
|
374
|
+
continue;
|
|
375
|
+
if ((newest && time > selectedTime) || (!newest && time < selectedTime)) {
|
|
376
|
+
selected = event.date;
|
|
377
|
+
selectedTime = time;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return selected;
|
|
381
|
+
}
|
|
382
|
+
function summarizeLifecycle(events) {
|
|
383
|
+
const lifecycle = {
|
|
384
|
+
createdDate: lifecycleDate(events, 'registration', false),
|
|
385
|
+
reregistrationDate: lifecycleDate(events, 'reregistration', true),
|
|
386
|
+
expiryDate: lifecycleDate(events, 'expiration', true),
|
|
387
|
+
updatedDate: lifecycleDate(events, 'last changed', true),
|
|
388
|
+
transferDate: lifecycleDate(events, 'transfer', true),
|
|
389
|
+
deletionDate: lifecycleDate(events, 'deletion', true),
|
|
390
|
+
reinstantiationDate: lifecycleDate(events, 'reinstantiation', true),
|
|
391
|
+
databaseUpdatedDate: lifecycleDate(events, 'last update of rdap database', true),
|
|
392
|
+
};
|
|
393
|
+
return {
|
|
394
|
+
...lifecycle,
|
|
395
|
+
createdDateIso: registryDateIso(lifecycle.createdDate),
|
|
396
|
+
reregistrationDateIso: registryDateIso(lifecycle.reregistrationDate),
|
|
397
|
+
expiryDateIso: registryDateIso(lifecycle.expiryDate),
|
|
398
|
+
updatedDateIso: registryDateIso(lifecycle.updatedDate),
|
|
399
|
+
transferDateIso: registryDateIso(lifecycle.transferDate),
|
|
400
|
+
deletionDateIso: registryDateIso(lifecycle.deletionDate),
|
|
401
|
+
reinstantiationDateIso: registryDateIso(lifecycle.reinstantiationDate),
|
|
402
|
+
databaseUpdatedDateIso: registryDateIso(lifecycle.databaseUpdatedDate),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function summarizeEntity(entity) {
|
|
406
|
+
if (!entity || typeof entity !== 'object' || Array.isArray(entity))
|
|
407
|
+
return null;
|
|
408
|
+
const record = entity;
|
|
409
|
+
const roles = [];
|
|
410
|
+
if (Array.isArray(record.roles)) {
|
|
411
|
+
for (const rawRole of record.roles.slice(0, 100)) {
|
|
412
|
+
const role = boundedString(rawRole, 80, { lower: true });
|
|
413
|
+
if (!role || roles.includes(role))
|
|
414
|
+
continue;
|
|
415
|
+
roles.push(role);
|
|
416
|
+
if (roles.length >= MAX_ENTITY_ROLES)
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const names = normalizeContactValues(record.vcardArray, 'fn', 300);
|
|
421
|
+
const organizations = normalizeContactValues(record.vcardArray, 'org', 300);
|
|
422
|
+
const emails = normalizeContactValues(record.vcardArray, 'email', 320, { lower: true });
|
|
423
|
+
const phones = normalizeContactValues(record.vcardArray, 'tel', 100);
|
|
424
|
+
const addresses = normalizeAddresses(record.vcardArray);
|
|
425
|
+
const vcardEntries = Array.isArray(record.vcardArray) && Array.isArray(record.vcardArray[1])
|
|
426
|
+
? record.vcardArray[1]
|
|
427
|
+
: [];
|
|
428
|
+
const summary = {
|
|
429
|
+
handle: boundedString(record.handle, 200),
|
|
430
|
+
roles,
|
|
431
|
+
name: names[0] || null,
|
|
432
|
+
names,
|
|
433
|
+
org: organizations[0] || null,
|
|
434
|
+
organizations,
|
|
435
|
+
email: emails[0] || null,
|
|
436
|
+
emails,
|
|
437
|
+
phone: phones[0] || null,
|
|
438
|
+
phones,
|
|
439
|
+
address: addresses[0] || null,
|
|
440
|
+
addresses,
|
|
441
|
+
publicIds: normalizePublicIds(record.publicIds),
|
|
442
|
+
links: normalizeLinks(record.links, MAX_ENTITY_LINKS),
|
|
443
|
+
truncated: Boolean((Array.isArray(record.roles) && record.roles.length > MAX_ENTITY_ROLES)
|
|
444
|
+
|| vcardEntries.length > MAX_VCARD_ENTRIES
|
|
445
|
+
|| contactValuesTruncated(record.vcardArray)
|
|
446
|
+
|| (Array.isArray(record.publicIds) && record.publicIds.length > 20)
|
|
447
|
+
|| (Array.isArray(record.links) && record.links.length > MAX_ENTITY_LINKS)),
|
|
448
|
+
};
|
|
449
|
+
const hasAny = Boolean(summary.handle || summary.name || summary.org || summary.email
|
|
450
|
+
|| summary.phone || summary.address || summary.publicIds.length || summary.links.length);
|
|
451
|
+
return hasAny ? summary : null;
|
|
452
|
+
}
|
|
453
|
+
function summarizeEntities(entities) {
|
|
454
|
+
const summaries = [];
|
|
455
|
+
const source = Array.isArray(entities) ? entities : [];
|
|
456
|
+
let truncated = source.length > MAX_RDAP_ENTITIES;
|
|
457
|
+
const stack = source
|
|
458
|
+
.slice(0, MAX_RDAP_ENTITIES)
|
|
459
|
+
.reverse()
|
|
460
|
+
.map((entity) => ({ entity, depth: 0 }));
|
|
461
|
+
const seen = new WeakSet();
|
|
462
|
+
let visited = 0;
|
|
463
|
+
while (stack.length && visited < MAX_RDAP_ENTITIES) {
|
|
464
|
+
const current = stack.pop();
|
|
465
|
+
if (!current)
|
|
466
|
+
continue;
|
|
467
|
+
const { entity, depth } = current;
|
|
468
|
+
if (!entity || typeof entity !== 'object' || Array.isArray(entity) || seen.has(entity))
|
|
469
|
+
continue;
|
|
470
|
+
seen.add(entity);
|
|
471
|
+
visited += 1;
|
|
472
|
+
const summary = summarizeEntity(entity);
|
|
473
|
+
if (summary)
|
|
474
|
+
summaries.push(summary);
|
|
475
|
+
const record = entity;
|
|
476
|
+
if (depth >= MAX_RDAP_ENTITY_DEPTH || !Array.isArray(record.entities))
|
|
477
|
+
continue;
|
|
478
|
+
const remaining = Math.max(0, MAX_RDAP_ENTITIES - visited - stack.length);
|
|
479
|
+
const nested = record.entities.slice(0, remaining);
|
|
480
|
+
if (nested.length < record.entities.length)
|
|
481
|
+
truncated = true;
|
|
482
|
+
for (let i = nested.length - 1; i >= 0; i -= 1)
|
|
483
|
+
stack.push({ entity: nested[i], depth: depth + 1 });
|
|
484
|
+
}
|
|
485
|
+
if (stack.length)
|
|
486
|
+
truncated = true;
|
|
487
|
+
return { summaries, truncated };
|
|
488
|
+
}
|
|
489
|
+
function groupEntitiesByRole(entities) {
|
|
490
|
+
const grouped = {};
|
|
491
|
+
const truncatedRoles = new Set();
|
|
492
|
+
for (const entity of entities) {
|
|
493
|
+
for (const role of entity.roles) {
|
|
494
|
+
if (!RDAP_CONTACT_ROLES.has(role))
|
|
495
|
+
continue;
|
|
496
|
+
if (!grouped[role])
|
|
497
|
+
grouped[role] = [];
|
|
498
|
+
if (grouped[role].length < MAX_ENTITIES_PER_ROLE)
|
|
499
|
+
grouped[role].push(entity);
|
|
500
|
+
else
|
|
501
|
+
truncatedRoles.add(role);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return { entitiesByRole: grouped, truncatedEntityRoles: [...truncatedRoles].sort() };
|
|
505
|
+
}
|
|
506
|
+
function entityInventory(entities) {
|
|
507
|
+
const collected = summarizeEntities(entities);
|
|
508
|
+
const grouped = groupEntitiesByRole(collected.summaries);
|
|
509
|
+
return {
|
|
510
|
+
...grouped,
|
|
511
|
+
entitiesTruncated: collected.truncated || grouped.truncatedEntityRoles.length > 0
|
|
512
|
+
|| collected.summaries.some((entity) => entity.truncated),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function parseRdapObject(type, data) {
|
|
516
|
+
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
517
|
+
return null;
|
|
518
|
+
const events = normalizeRdapEvents(data.events);
|
|
519
|
+
const redactionInfo = normalizeRedactions(data.redacted);
|
|
520
|
+
const noticesInfo = summarizeTextBlocks(data.notices);
|
|
521
|
+
const remarksInfo = summarizeTextBlocks(data.remarks);
|
|
522
|
+
const serverTruncationReasons = summarizeServerTruncation(data.notices, data.remarks);
|
|
523
|
+
// Preserve the established status-array contract while making it available
|
|
524
|
+
// to every RDAP object type. Unlike set-like fields, status order and
|
|
525
|
+
// repetition remain as published for backwards compatibility.
|
|
526
|
+
const statuses = Array.isArray(data.status)
|
|
527
|
+
? data.status.slice(0, 100)
|
|
528
|
+
.map((status) => boundedString(status, 160))
|
|
529
|
+
.filter((status) => status !== null)
|
|
530
|
+
: [];
|
|
531
|
+
const common = {
|
|
532
|
+
objectClassName: boundedString(data.objectClassName, 80, { lower: true }),
|
|
533
|
+
language: boundedString(data.lang, 35, { lower: true }),
|
|
534
|
+
conformance: normalizeStringList(data.rdapConformance, 50, 160, { lower: true }),
|
|
535
|
+
conformanceTruncated: Array.isArray(data.rdapConformance) && data.rdapConformance.length > 50,
|
|
536
|
+
...redactionInfo,
|
|
537
|
+
port43: boundedString(data.port43, 300),
|
|
538
|
+
parentHandle: boundedString(data.parentHandle, 300),
|
|
539
|
+
links: normalizeLinks(data.links),
|
|
540
|
+
linksTruncated: Array.isArray(data.links) && data.links.length > MAX_RDAP_LINKS,
|
|
541
|
+
notices: noticesInfo.items,
|
|
542
|
+
noticesTruncated: noticesInfo.truncated,
|
|
543
|
+
remarks: remarksInfo.items,
|
|
544
|
+
remarksTruncated: remarksInfo.truncated,
|
|
545
|
+
serverTruncated: serverTruncationReasons.length > 0,
|
|
546
|
+
serverTruncationReasons,
|
|
547
|
+
statuses,
|
|
548
|
+
statusesTruncated: Array.isArray(data.status) && data.status.length > 100,
|
|
549
|
+
events,
|
|
550
|
+
eventsTruncated: Array.isArray(data.events) && data.events.length > 100,
|
|
551
|
+
lifecycle: summarizeLifecycle(events),
|
|
552
|
+
};
|
|
553
|
+
if (type === 'domain') {
|
|
554
|
+
const { entitiesByRole, entitiesTruncated, truncatedEntityRoles } = entityInventory(data.entities);
|
|
555
|
+
const registrarEntity = entitiesByRole.registrar && entitiesByRole.registrar[0];
|
|
556
|
+
let nameserverAddressesTruncated = false;
|
|
557
|
+
const nameserverDetails = Array.isArray(data.nameservers)
|
|
558
|
+
? data.nameservers.slice(0, 200).map((ns) => {
|
|
559
|
+
if (!ns || typeof ns !== 'object' || Array.isArray(ns))
|
|
560
|
+
return null;
|
|
561
|
+
const nameserver = ns;
|
|
562
|
+
const ipAddresses = nameserver.ipAddresses && typeof nameserver.ipAddresses === 'object' && !Array.isArray(nameserver.ipAddresses)
|
|
563
|
+
? nameserver.ipAddresses
|
|
564
|
+
: {};
|
|
565
|
+
const v4 = Array.isArray(ipAddresses.v4) ? ipAddresses.v4 : [];
|
|
566
|
+
const v6 = Array.isArray(ipAddresses.v6) ? ipAddresses.v6 : [];
|
|
567
|
+
if (v4.length + v6.length > 20)
|
|
568
|
+
nameserverAddressesTruncated = true;
|
|
569
|
+
const addresses = [
|
|
570
|
+
...v4.map((address) => boundedString(address, 80)).filter((address) => address && net.isIP(address) === 4),
|
|
571
|
+
...v6.map((address) => boundedString(address, 80)).filter((address) => address && net.isIP(address) === 6),
|
|
572
|
+
].slice(0, 20);
|
|
573
|
+
return {
|
|
574
|
+
name: boundedString(nameserver.ldhName || nameserver.unicodeName, 253),
|
|
575
|
+
addresses,
|
|
576
|
+
};
|
|
577
|
+
}).filter((ns) => Boolean(ns?.name))
|
|
578
|
+
: [];
|
|
579
|
+
const secureDns = data.secureDNS
|
|
580
|
+
&& typeof data.secureDNS === 'object'
|
|
581
|
+
&& !Array.isArray(data.secureDNS)
|
|
582
|
+
? data.secureDNS
|
|
583
|
+
: null;
|
|
584
|
+
const variantInfo = normalizeDomainVariants(data.variants);
|
|
585
|
+
const dsData = secureDns && Array.isArray(secureDns.dsData)
|
|
586
|
+
? secureDns.dsData.slice(0, 50).map((ds) => {
|
|
587
|
+
if (!ds || typeof ds !== 'object' || Array.isArray(ds))
|
|
588
|
+
return null;
|
|
589
|
+
const record = ds;
|
|
590
|
+
const digest = boundedString(record.digest, 512);
|
|
591
|
+
const normalized = {
|
|
592
|
+
keyTag: boundedInteger(record.keyTag, 0, 65535), algorithm: boundedInteger(record.algorithm, 0, 255),
|
|
593
|
+
digestType: boundedInteger(record.digestType, 0, 255),
|
|
594
|
+
digest: digest && digest.length % 2 === 0 && /^[0-9a-f]+$/i.test(digest) ? digest : null,
|
|
595
|
+
};
|
|
596
|
+
return Object.values(normalized).every((value) => value !== null)
|
|
597
|
+
? normalized
|
|
598
|
+
: null;
|
|
599
|
+
}).filter((value) => value !== null)
|
|
600
|
+
: [];
|
|
601
|
+
return {
|
|
602
|
+
...common,
|
|
603
|
+
domain: boundedString(data.ldhName || data.unicodeName, 253),
|
|
604
|
+
unicodeDomain: data.unicodeName && data.unicodeName !== data.ldhName
|
|
605
|
+
? boundedString(data.unicodeName, 253) : null,
|
|
606
|
+
handle: boundedString(data.handle, 300),
|
|
607
|
+
nameservers: nameserverDetails.map((ns) => ns.name),
|
|
608
|
+
nameserverDetails,
|
|
609
|
+
nameserversTruncated: Array.isArray(data.nameservers) && data.nameservers.length > 200,
|
|
610
|
+
nameserverAddressesTruncated,
|
|
611
|
+
dnssec: secureDns && secureDns.delegationSigned === true
|
|
612
|
+
? 'Signed' : secureDns && secureDns.delegationSigned === false ? 'Unsigned' : 'Unknown',
|
|
613
|
+
zoneSigned: secureDns && typeof secureDns.zoneSigned === 'boolean' ? secureDns.zoneSigned : null,
|
|
614
|
+
delegationSigned: secureDns && typeof secureDns.delegationSigned === 'boolean'
|
|
615
|
+
? secureDns.delegationSigned : null,
|
|
616
|
+
dsData,
|
|
617
|
+
dsDataTruncated: Boolean(secureDns && Array.isArray(secureDns.dsData) && secureDns.dsData.length > 50),
|
|
618
|
+
...variantInfo,
|
|
619
|
+
registrarIanaId: publicId(registrarEntity, /iana registrar id/i),
|
|
620
|
+
entitiesByRole,
|
|
621
|
+
entitiesTruncated,
|
|
622
|
+
truncatedEntityRoles,
|
|
623
|
+
registrar: registrarEntity || null,
|
|
624
|
+
registrant: entitiesByRole.registrant?.[0] || null,
|
|
625
|
+
administrative: entitiesByRole.administrative?.[0] || null,
|
|
626
|
+
technical: entitiesByRole.technical?.[0] || null,
|
|
627
|
+
billing: entitiesByRole.billing?.[0] || null,
|
|
628
|
+
abuse: entitiesByRole.abuse?.[0] || null,
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
if (type === 'ipv4' || type === 'ipv6') {
|
|
632
|
+
const { entitiesByRole, entitiesTruncated, truncatedEntityRoles } = entityInventory(data.entities);
|
|
633
|
+
const cidrs = Array.isArray(data.cidr0_cidrs)
|
|
634
|
+
? data.cidr0_cidrs.slice(0, 200)
|
|
635
|
+
.map((c) => {
|
|
636
|
+
if (!c || typeof c !== 'object' || Array.isArray(c))
|
|
637
|
+
return null;
|
|
638
|
+
const expectedFamily = type === 'ipv4' ? 4 : 6;
|
|
639
|
+
const prefix = boundedString(expectedFamily === 4 ? c.v4prefix : c.v6prefix, 80);
|
|
640
|
+
if (!prefix || net.isIP(prefix) !== expectedFamily)
|
|
641
|
+
return null;
|
|
642
|
+
const length = boundedInteger(c.length, 0, expectedFamily === 4 ? 32 : 128);
|
|
643
|
+
return prefix && length !== null ? `${prefix}/${length}` : null;
|
|
644
|
+
})
|
|
645
|
+
.filter((value) => value !== null)
|
|
646
|
+
: [];
|
|
647
|
+
return {
|
|
648
|
+
...common,
|
|
649
|
+
handle: boundedString(data.handle, 300),
|
|
650
|
+
name: boundedString(data.name, 300),
|
|
651
|
+
startAddress: boundedString(data.startAddress, 80),
|
|
652
|
+
endAddress: boundedString(data.endAddress, 80),
|
|
653
|
+
cidrs,
|
|
654
|
+
cidrsTruncated: Array.isArray(data.cidr0_cidrs) && data.cidr0_cidrs.length > 200,
|
|
655
|
+
country: boundedString(data.country, 2),
|
|
656
|
+
networkType: boundedString(data.type, 160),
|
|
657
|
+
entitiesByRole,
|
|
658
|
+
entitiesTruncated,
|
|
659
|
+
truncatedEntityRoles,
|
|
660
|
+
org: entitiesByRole.registrant?.[0] || entitiesByRole.administrative?.[0] || null,
|
|
661
|
+
abuse: entitiesByRole.abuse?.[0] || null,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
if (type === 'asn') {
|
|
665
|
+
const { entitiesByRole, entitiesTruncated, truncatedEntityRoles } = entityInventory(data.entities);
|
|
666
|
+
return {
|
|
667
|
+
...common,
|
|
668
|
+
handle: boundedString(data.handle, 300),
|
|
669
|
+
name: boundedString(data.name, 300),
|
|
670
|
+
startAutnum: boundedInteger(data.startAutnum, 0, 4294967295),
|
|
671
|
+
endAutnum: boundedInteger(data.endAutnum, 0, 4294967295),
|
|
672
|
+
country: boundedString(data.country, 2),
|
|
673
|
+
autnumType: boundedString(data.type, 160),
|
|
674
|
+
entitiesByRole,
|
|
675
|
+
entitiesTruncated,
|
|
676
|
+
truncatedEntityRoles,
|
|
677
|
+
org: entitiesByRole.registrant?.[0] || null,
|
|
678
|
+
abuse: entitiesByRole.abuse?.[0] || null,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
function parseRdap(type, data) {
|
|
684
|
+
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
685
|
+
return null;
|
|
686
|
+
// parseRdapObject performs the matching runtime branch before returning.
|
|
687
|
+
// This cast preserves that relationship for literal callers without
|
|
688
|
+
// weakening the untrusted JSON boundary to any.
|
|
689
|
+
return parseRdapObject(type, data);
|
|
690
|
+
}
|
|
691
|
+
export { parseRdap, normalizeRdapEvents, summarizeServerTruncation as summarizeRdapServerTruncation, summarizeTextBlocks as summarizeRdapTextBlocks, summarizeLifecycle, };
|