fix: follow WHOIS referrals in indented replies

Verisign indents every line of its WHOIS output, so the referral patterns
(anchored at column 0) never matched and .com/.net lookups stopped at the thin
registry record without registrant data. Allow leading whitespace and keep the
value on the same line so a blank field no longer borrows the next line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Opus 5.5 committed 2026-10-03 14:35:50 -04:00
1 parent c81a51df09
commit 8f601194fa
2 files changed
+75 -3

No files matched your search

+5 -3
View File
@@ -129,12 +129,14 @@ export async function ianaWhoisServerForTld(
/**
* Extract registrar referral WHOIS server from a WHOIS response, if present.
* Labels may be indented (Verisign indents every line), and a blank value never borrows the
* next line.
*/
export function extractWhoisReferral(text: string): string | undefined {
const patterns = [
/^Registrar WHOIS Server:\s*(.+)$/im,
/^Whois Server:\s*(.+)$/im,
/^ReferralServer:\s*whois:\/\/(.+)$/im,
/^[ \t]*Registrar WHOIS Server:[ \t]*(\S.*)$/im,
/^[ \t]*Whois Server:[ \t]*(\S.*)$/im,
/^[ \t]*ReferralServer:[ \t]*whois:\/\/(\S.*)$/im,
];
for (const re of patterns) {
const m = text.match(re);
+70
View File
@@ -17,8 +17,58 @@ vi.mock("./client.js", () => ({
}),
}));
import { extractWhoisReferral } from "./discovery";
import { collectWhoisReferralChain } from "./referral";
// Excerpt of Verisign's reply for google.com: every line is indented and CRLF-terminated
const VERISIGN_GOOGLE_COM = [
" Domain Name: GOOGLE.COM",
" Registry Domain ID: 2138514_DOMAIN_COM-VRSN",
" Registrar WHOIS Server: whois.markmonitor.com",
" Registrar URL: http://www.markmonitor.com",
" Creation Date: 1997-09-15T04:00:00Z",
" Registry Expiry Date: 2028-09-14T04:00:00Z",
" Registrar: MarkMonitor Inc.",
" Name Server: NS1.GOOGLE.COM",
">>> Last update of whois database: 2026-10-03T18:28:44Z <<<",
"",
].join("\r\n");
// Excerpt of MarkMonitor's registrar reply for the same domain
const MARKMONITOR_GOOGLE_COM = [
"Domain Name: google.com",
"Registrar WHOIS Server: whois.markmonitor.com",
"Creation Date: 1997-09-15T07:00:00+0000",
"Registrar Registration Expiration Date: 2028-09-13T07:00:00+0000",
"Registrar: MarkMonitor, Inc.",
"Registrant Organization: Google LLC",
"Registrant Country: US",
"Name Server: ns1.google.com",
"",
].join("\n");
describe("extractWhoisReferral", () => {
it("reads an indented, CRLF-terminated referral (Verisign)", () => {
expect(extractWhoisReferral(VERISIGN_GOOGLE_COM)).toBe("whois.markmonitor.com");
});
it("does not borrow the next line when the referral field is blank", () => {
const text = " Registrar WHOIS Server: \r\n Registrar URL: http://registrar.test\r\n";
expect(extractWhoisReferral(text)).toBeUndefined();
});
it("falls through a blank field to a later pattern", () => {
const text = "Registrar WHOIS Server:\nWhois Server: whois.registrar.test\n";
expect(extractWhoisReferral(text)).toBe("whois.registrar.test");
});
it("reads an indented ARIN-style ReferralServer", () => {
expect(extractWhoisReferral(" ReferralServer: whois://whois.ripe.net\n")).toBe(
"whois.ripe.net",
);
});
});
describe("WHOIS referral contradiction handling", () => {
it("collects chain and does not append contradictory registrar", async () => {
const { results: chain } = await collectWhoisReferralChain("whois.nic.io", "raindrop.io", {
@@ -49,6 +99,26 @@ describe("WHOIS referral safety", () => {
expect(warnings[0]).toMatch(/unsafe host/);
});
it("follows the registrar referral in Verisign's indented reply", async () => {
const { whoisQuery } = await import("./client.js");
const mocked = vi.mocked(whoisQuery);
mocked.mockClear();
mocked.mockImplementation(async (server: string) => ({
serverQueried: server,
text: server === "whois.verisign-grs.com" ? VERISIGN_GOOGLE_COM : MARKMONITOR_GOOGLE_COM,
}));
const { results, warnings } = await collectWhoisReferralChain(
"whois.verisign-grs.com",
"google.com",
{ followWhoisReferral: true },
);
expect(results.map((r) => r.serverQueried)).toEqual([
"whois.verisign-grs.com",
"whois.markmonitor.com",
]);
expect(warnings).toEqual([]);
});
it("keeps the registry record when the registrar throttles", async () => {
const { whoisQuery } = await import("./client.js");
vi.mocked(whoisQuery).mockImplementation(async (server: string) => ({