feat: add contact redacted flag, placeholder detection, and country name/code resolution

- Add `redacted?: boolean` to `Contact`; set when any field was dropped as a placeholder, the name matches a privacy-service heuristic, or an RFC 9537 redaction targets that entity's role
- Add `isPlaceholderValue` to `privacy.ts` to identify boilerplate registrar strings (`"Please query the RDDS…"`, `"redacted"`, `"n/a"`, etc.) used in place of real email/phone/fax values; drop them from the contact during finalization
- Add `countries.ts` backed by `Intl.DisplayNames` (with a curated `ALIASES` table for common alternate names) providing `countryNameFromCode`, `countryCodeFromName`, and `resolveCountry`; a two-letter `country` value is now treated as a code and expanded to a full name
- Introduce `finalizeContact` in `contacts.ts` to centralise all contact post-processing (placeholder stripping, country resolution, `redacted` flag); call it from both RDAP and WHOIS normalizers
- Wire RFC 9537 redaction role-matching into `extractContacts` via a new `redactionTargetsRole` helper so entities targeted by a `redacted` entry automatically receive `redacted: true`
- Add `countries.test.ts` and extend RDAP/WHOIS normalizer tests to cover placeholder stripping, country fill-in, and redaction flag propagation
This commit is contained in:
2026-09-19 23:07:38 -04:00
parent 1d04be5c74
commit 89fa13ffef
10 changed files with 286 additions and 23 deletions
+21
View File
@@ -280,3 +280,24 @@ Name servers:
expect(rec.registrar).toEqual({ name: "epag", url: "http://www.epag.de" });
expect(rec.nameservers?.map((n) => n.host)).toEqual(["ns1.vercel-dns.com", "ns2.vercel-dns.com"]);
});
test("WHOIS resolves country code/name and flags placeholder contact values", () => {
const rec = normalizeWhois(
"example.com",
"com",
`Domain Name: EXAMPLE.COM
Registrant Name: Jane Doe
Registrant Email: Please query the RDDS service of the Registrar of Record identified in this output
Registrant Country: DE
Admin Name: John Roe
Admin Country: Canada
`,
"whois.example",
);
const registrant = rec.contacts?.find((c) => c.type === "registrant");
expect(registrant).toMatchObject({ country: "Germany", countryCode: "DE", redacted: true });
expect(registrant?.email).toBeUndefined();
const admin = rec.contacts?.find((c) => c.type === "admin");
expect(admin).toMatchObject({ country: "Canada", countryCode: "CA" });
expect(admin?.redacted).toBeUndefined();
});