fix: read .it DNSSEC from the domain, not the registrar section

.it's registrar section says "DNSSEC: yes" when the registrar supports DNSSEC,
and that line was read instead of the domain's own "Signed: no", so most
unsigned .it domains looked signed. Read DNSSEC from the first block that has
it, preferring "Signed"; accept .kr's Korean "서명" ("signed"), which now comes
first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Opus 5.5 committed 2026-10-03 15:24:00 -04:00
1 parent 81a02a7eb3
commit 6e96e49c28
2 files changed
+32 -5

No files matched your search

+16
View File
@@ -606,3 +606,19 @@ test("WHOIS date keys and forms across ccTLDs", () => {
expect(rec("changed: 20200427 16:03:40\n").updatedDate).toBe("2020-04-27T16:03:40Z");
expect(rec("Registered On: Before 2001\n").creationDate).toBeUndefined();
});
test("WHOIS DNSSEC comes from the domain's block, not the registrar's capability (.it)", () => {
const it = `
Domain: example.it
Status: ok
Signed: no
Registrar
Organization: Example Registrar S.r.l.
DNSSEC: yes
`;
expect(normalizeWhois("example.it", "it", it, "whois.nic.it").dnssec).toEqual({ enabled: false });
// .kr writes the Korean section first
const kr = "DNSSEC : 서명\n\nDNSSEC : signed\n";
expect(normalizeWhois("example.kr", "kr", kr, "whois.kr").dnssec).toEqual({ enabled: true });
});
+16 -5
View File
@@ -224,11 +224,13 @@ export function normalizeWhois(
const registrant = contacts?.find((c) => c.type === "registrant");
const privacyEnabled = isPrivacyContact(registrant);
// "signedDelegation", "Signed delegation", "yes", "active" (.bg, .md); not "unsigned" or
// "Inactive". .kr repeats the field in Korean, so any value may carry the answer.
const dnssecValues = [...(map.dnssec ?? []), ...(map["dnssec signed"] ?? [])]; // .rs
const dnssec = dnssecValues.length
? { enabled: dnssecValues.some((v) => /^(?:signed|yes|true|active)/i.test(v.trim())) }
// "signedDelegation", "Signed delegation", "yes", "active" (.bg, .md), "서명" (.kr); not
// "unsigned" or "Inactive". Read from the first block that has it, preferring "Signed": .it's
// registrar section has "DNSSEC: yes" for the registrar's support, beside the domain's
// "Signed: no".
const dnssecValues = valuesOf(blocks, ["signed", "dnssec signed", "dnssec"]); // "dnssec signed": .rs
const dnssec = dnssecValues
? { enabled: dnssecValues.some((v) => /^(?:signed|yes|true|active|서명)/i.test(v.trim())) }
: undefined;
// Simple lock derivation from statuses (the EPP code, or the registry's wording in raw)
@@ -307,6 +309,15 @@ function parseNameserverLine(line: string, domain: string): Nameserver | undefin
return ns;
}
/** Values of the first key (by priority) present in the first block that has any of them. */
function valuesOf(blocks: Array<Record<string, string[]>>, keys: string[]): string[] | undefined {
for (const block of blocks) {
const key = keys.find((k) => block[k]?.length);
if (key) return block[key];
}
return undefined;
}
// Status keys by preference: .fr lists EPP codes as "eppstatus" beside a plain "status: ACTIVE"
const STATUS_KEYS = ["eppstatus", "domain status", "status", "registration status"];