fix: extend Retry-After parsing to RDAP 503 and fix block pattern across newlines

- Parse `Retry-After` on RDAP `503` responses (previously only `429`), exposing `retryAfterMs` on the attempt and on the top-level result when `rdapOnly` makes it terminal
- Fix the WHOIS `BLOCK_PATTERNS` regex to use `[\s\S]` instead of `.` so a block notice that spans multiple lines (e.g. `"Your IP address\nhas been blocked"`) is still classified as `blocked`
- Update README to document `retryAfterMs` on `LookupResult`, clarify `rate_limited` / `blocked` / `unparseable` semantics, and note referral-host validation behaviour
This commit is contained in:
2026-09-19 12:30:06 -04:00
parent 35959b208b
commit 93f877c357
5 changed files with 32 additions and 3 deletions
+13
View File
@@ -91,6 +91,19 @@ describe("throttle and empty-response guards", () => {
});
describe("rdapOnly rate limiting", () => {
it("parses Retry-After on a 503", async () => {
const customFetch: FetchLike = vi.fn(
async () => new Response("", { status: 503, headers: { "retry-after": "5" } }),
);
const res = await lookup("example.com", {
customBootstrapData: bootstrap,
customFetch,
rdapOnly: true,
});
expect(res.errorCode).toBe("rdap_unavailable");
expect(res.retryAfterMs).toBe(5_000);
});
it("surfaces retryAfterMs on the result", async () => {
const customFetch: FetchLike = vi.fn(
async () => new Response("", { status: 429, headers: { "retry-after": "12" } }),
+7 -1
View File
@@ -64,7 +64,13 @@ export async function fetchRdapDomain(
}
if (!res.ok) {
const bodyText = await res.text().catch(() => "");
throw new RdapperError("http_error", `RDAP ${res.status}: ${bodyText.slice(0, 500)}`);
const retryAfterMs =
res.status === 503 ? parseRetryAfterMs(res.headers.get("retry-after")) : undefined;
throw new RdapperError(
"http_error",
`RDAP ${res.status}: ${bodyText.slice(0, 500)}`,
retryAfterMs !== undefined ? { retryAfterMs } : undefined,
);
}
const json = await res.json();
return { url, json };
+1
View File
@@ -15,6 +15,7 @@ describe("detectWhoisRefusal", () => {
it.each([
"Requests of this client are not permitted. Please use https://www.nic.ch/whois/ for queries.",
"Your IP address has been blocked",
"Your IP address\nhas been blocked",
])("classifies %j as blocked", (text) => {
expect(detectWhoisRefusal(text)).toBe("blocked");
});
+1 -1
View File
@@ -17,7 +17,7 @@ const THROTTLE_PATTERNS: RegExp[] = [
// Permanent: this client is refused outright, so retrying will not help.
const BLOCK_PATTERNS: RegExp[] = [
/requests\s+of\s+this\s+client\s+are\s+not\s+permitted/i, // .ch/.li
/\b(your|this)\s+(ip|address|client)\b.{0,60}\b(blocked|banned|blacklisted|not\s+permitted)\b/i,
/\b(your|this)\s+(ip|address|client)\b[\s\S]{0,60}\b(blocked|banned|blacklisted|not\s+permitted)\b/i,
];
/**