mirror of
https://github.com/jakejarvis/rdapper.git
synced 2026-10-26 08:26:22 -04:00
fix: don't mark registered WHOIS records available over a remark
Availability phrases were matched anywhere in the reply, so a registered record
whose remarks or footer used one was reported available: nic.ir ("only
available for registration under certain conditions"), or any record with
"Registrant Fax: not found" or "This WHOIS service is free of charge". The
referral chain used the same check and could drop a registrar's reply.
Phrases now count only when the reply has no registration data (dates,
registrar, nameservers); explicit statuses such as "status: free" or .br's
"release process: waiting" still win. Checked live: unregistered names under
.com, .ir, .it, .jp, .fi, .cz, .nl, and .br are still reported available.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2 files changed
+52
-7
No files matched your search
@@ -457,3 +457,31 @@ nserver: ho1.ns.hostmaster.ua
|
||||
const rec = normalizeWhois("hostmaster.ua", "ua", text, "whois.ua");
|
||||
expect(rec.nameservers?.map((n) => n.host)).toEqual(["bg.ns.ua", "ho1.ns.hostmaster.ua"]);
|
||||
});
|
||||
|
||||
test("WHOIS availability phrases in remarks of a real record don't mark it available", () => {
|
||||
// Excerpt of whois.nic.ir for nic.ir: the remarks mention "available for registration"
|
||||
const ir = `
|
||||
domain: nic.ir
|
||||
holder-c: ir00-irnic
|
||||
nserver: ns1.nic.ir
|
||||
nserver: ns2.nic.ir
|
||||
source: IRNIC
|
||||
|
||||
remarks: This domain is only available for registration under certain conditions
|
||||
`;
|
||||
expect(normalizeWhois("nic.ir", "ir", ir, "whois.nic.ir").isRegistered).toBe(true);
|
||||
const fax =
|
||||
"Domain Name: example.com\nCreation Date: 2020-01-01T00:00:00Z\nRegistrant Fax: not found\n";
|
||||
expect(normalizeWhois("example.com", "com", fax, undefined).isRegistered).toBe(true);
|
||||
expect(isAvailableByWhois(ir)).toBe(false);
|
||||
});
|
||||
|
||||
test("WHOIS availability phrases still mark a reply with no record data available", () => {
|
||||
const text =
|
||||
'No match for "EXAMPLE-AVAILABLE.COM".\n>>> Last update of whois database: 2026-10-03T18:00:00Z <<<\n';
|
||||
expect(normalizeWhois("example-available.com", "com", text, undefined).isRegistered).toBe(false);
|
||||
// A release status wins even though the old record data is still shown
|
||||
const releasing =
|
||||
"domain: example.com.br\ncreated: 20100101\nnserver: ns1.example.com.br\nstatus: release process: waiting\n";
|
||||
expect(normalizeWhois("example.com.br", "com.br", releasing, undefined).isRegistered).toBe(false);
|
||||
});
|
||||
+24
-7
@@ -5,8 +5,11 @@ import { isEppStatus, normalizeEppStatus } from "../lib/status";
|
||||
import { parseKeyValueBlocks, parseKeyValueLines } from "../lib/text";
|
||||
import type { Contact, DomainRecord, Nameserver, RegistrarInfo } from "../types";
|
||||
|
||||
// Common WHOIS availability phrases seen across registries/registrars
|
||||
const WHOIS_AVAILABLE_PATTERNS: RegExp[] = [
|
||||
// Phrases registries use to say a name is not registered. They also turn up in the remarks and
|
||||
// footers of real records ("only available for registration under certain conditions" on .ir,
|
||||
// "Registrant Fax: not found", "This WHOIS service is free of charge"), so they count only when
|
||||
// the reply carries no registration data.
|
||||
const WHOIS_AVAILABLE_PHRASES: RegExp[] = [
|
||||
/\bno match\b/i,
|
||||
/\bnot found\b/i,
|
||||
/\bno entries found\b/i,
|
||||
@@ -16,7 +19,6 @@ const WHOIS_AVAILABLE_PATTERNS: RegExp[] = [
|
||||
/\bno data was found\b/i,
|
||||
/\bavailable for registration\b/i,
|
||||
/\bdomain\s+available\b/i,
|
||||
/\bdomain status[:\s]+available\b/i,
|
||||
/\bobject does not exist\b/i,
|
||||
/\bthe queried object does not exist\b/i,
|
||||
/\bqueried object does not exist\b/i,
|
||||
@@ -26,12 +28,18 @@ const WHOIS_AVAILABLE_PATTERNS: RegExp[] = [
|
||||
/\bunassignable\b/i,
|
||||
/\bis free\b/i,
|
||||
// Common variants across ccTLDs/registrars
|
||||
/\bstatus:\s*free\b/i,
|
||||
/\bstatus:\s*available\b/i,
|
||||
/\bno object found\b/i,
|
||||
/\bobject_not_found\b/i,
|
||||
/\bno se encuentra registrado\b/i, // Spanish: "not found registered"
|
||||
/\bnicht gefunden\b/i, // German: "not found"
|
||||
];
|
||||
|
||||
// Statuses that mark a name unregistered even though the record still carries data, as a name
|
||||
// being released keeps its old dates and nameservers
|
||||
const WHOIS_AVAILABLE_STATUSES: RegExp[] = [
|
||||
/\bdomain status[:\s]+available\b/i,
|
||||
/\bstatus:\s*free\b/i,
|
||||
/\bstatus:\s*available\b/i,
|
||||
/\bpending release\b/i, // often signals not registered/being deleted
|
||||
/\brelease process:\s*waiting\b/i, // .br: expired, awaiting release
|
||||
];
|
||||
@@ -41,7 +49,13 @@ const WHOIS_AVAILABLE_PATTERNS: RegExp[] = [
|
||||
*/
|
||||
export function isAvailableByWhois(text: string | undefined): boolean {
|
||||
if (!text) return false;
|
||||
return WHOIS_AVAILABLE_PATTERNS.some((re) => re.test(text));
|
||||
return !normalizeWhois("", "", text, undefined).isRegistered;
|
||||
}
|
||||
|
||||
/** Whether a reply says the name is unregistered, given whether it carries registration data. */
|
||||
function saysAvailable(text: string, hasRegistrationData: boolean): boolean {
|
||||
if (WHOIS_AVAILABLE_STATUSES.some((re) => re.test(text))) return true;
|
||||
return !hasRegistrationData && WHOIS_AVAILABLE_PHRASES.some((re) => re.test(text));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,7 +265,10 @@ export function normalizeWhois(
|
||||
const record: DomainRecord = {
|
||||
domain,
|
||||
tld,
|
||||
isRegistered: !isAvailableByWhois(whoisText),
|
||||
isRegistered: !saysAvailable(
|
||||
whoisText,
|
||||
!!(creationDate || expirationDate || registrar || nameservers?.length),
|
||||
),
|
||||
isIDN: /(^|\.)xn--/i.test(domain),
|
||||
unicodeName: undefined,
|
||||
punycodeName: undefined,
|
||||
|
||||
Reference in new issue
Block a user