mirror of
https://github.com/jakejarvis/rdapper.git
synced 2026-01-27 21:45:19 -05:00
fix: treat RDAP 404 responses as "domain not found" instead of failure (#29)
This commit is contained in:
@@ -110,7 +110,48 @@ describe("lookup orchestration", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2) WHOIS referral toggle and includeRaw behavior
|
// 2) RDAP 404 handling (domain not registered)
|
||||||
|
describe("RDAP 404 handling", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns isRegistered=false when RDAP returns 404", async () => {
|
||||||
|
vi.mocked(rdapClient.fetchRdapDomain).mockResolvedValueOnce({
|
||||||
|
url: "https://rdap.example/domain/nonexistent.gallery",
|
||||||
|
json: null,
|
||||||
|
notFound: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await lookup("nonexistent.gallery", {
|
||||||
|
rdapOnly: true,
|
||||||
|
timeoutMs: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.ok).toBe(true);
|
||||||
|
expect(res.record?.isRegistered).toBe(false);
|
||||||
|
expect(res.record?.source).toBe("rdap");
|
||||||
|
expect(res.record?.domain).toBe("nonexistent.gallery");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not fall back to WHOIS after RDAP 404", async () => {
|
||||||
|
vi.mocked(rdapClient.fetchRdapDomain).mockResolvedValueOnce({
|
||||||
|
url: "https://rdap.example/domain/available.com",
|
||||||
|
json: null,
|
||||||
|
notFound: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await lookup("available.com", { timeoutMs: 200 });
|
||||||
|
|
||||||
|
expect(res.ok).toBe(true);
|
||||||
|
expect(res.record?.isRegistered).toBe(false);
|
||||||
|
expect(res.record?.source).toBe("rdap");
|
||||||
|
// Should NOT call WHOIS because RDAP gave us a definitive answer
|
||||||
|
expect(vi.mocked(whoisClient.whoisQuery)).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3) WHOIS referral toggle and includeRaw behavior
|
||||||
describe("WHOIS referral & includeRaw", () => {
|
describe("WHOIS referral & includeRaw", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|||||||
15
src/index.ts
15
src/index.ts
@@ -47,7 +47,20 @@ export async function lookup(
|
|||||||
for (const base of bases) {
|
for (const base of bases) {
|
||||||
tried.push(base);
|
tried.push(base);
|
||||||
try {
|
try {
|
||||||
const { json } = await fetchRdapDomain(domain, base, opts);
|
const { json, notFound } = await fetchRdapDomain(domain, base, opts);
|
||||||
|
|
||||||
|
// HTTP 404 = domain not registered
|
||||||
|
if (notFound) {
|
||||||
|
const record: DomainRecord = {
|
||||||
|
domain,
|
||||||
|
tld,
|
||||||
|
isRegistered: false,
|
||||||
|
rdapServers: tried,
|
||||||
|
source: "rdap",
|
||||||
|
};
|
||||||
|
return { ok: true, record };
|
||||||
|
}
|
||||||
|
|
||||||
const rdapEnriched = await fetchAndMergeRdapRelated(
|
const rdapEnriched = await fetchAndMergeRdapRelated(
|
||||||
domain,
|
domain,
|
||||||
json,
|
json,
|
||||||
|
|||||||
@@ -3,15 +3,27 @@ import { DEFAULT_TIMEOUT_MS } from "../lib/constants";
|
|||||||
import { resolveFetch } from "../lib/fetch";
|
import { resolveFetch } from "../lib/fetch";
|
||||||
import type { LookupOptions } from "../types";
|
import type { LookupOptions } from "../types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of an RDAP fetch operation.
|
||||||
|
* - `json` contains the RDAP response if successful
|
||||||
|
* - `notFound` is true if the server returned 404 (domain not registered)
|
||||||
|
*/
|
||||||
|
export interface RdapFetchResult {
|
||||||
|
url: string;
|
||||||
|
json: unknown;
|
||||||
|
notFound?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch RDAP JSON for a domain from a specific RDAP base URL.
|
* Fetch RDAP JSON for a domain from a specific RDAP base URL.
|
||||||
* Throws on HTTP >= 400 (includes RDAP error JSON payloads).
|
* Returns `{ notFound: true }` for HTTP 404 (domain not registered).
|
||||||
|
* Throws on other HTTP errors (5xx, network errors, etc.).
|
||||||
*/
|
*/
|
||||||
export async function fetchRdapDomain(
|
export async function fetchRdapDomain(
|
||||||
domain: string,
|
domain: string,
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
options?: LookupOptions,
|
options?: LookupOptions,
|
||||||
): Promise<{ url: string; json: unknown }> {
|
): Promise<RdapFetchResult> {
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`domain/${encodeURIComponent(domain)}`,
|
`domain/${encodeURIComponent(domain)}`,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
@@ -26,6 +38,11 @@ export async function fetchRdapDomain(
|
|||||||
options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
||||||
"RDAP lookup timeout",
|
"RDAP lookup timeout",
|
||||||
);
|
);
|
||||||
|
// HTTP 404 = domain not found (not registered)
|
||||||
|
// Per RFC 9083, RDAP servers return 404 for objects that don't exist
|
||||||
|
if (res.status === 404) {
|
||||||
|
return { url, json: null, notFound: true };
|
||||||
|
}
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const bodyText = await res.text();
|
const bodyText = await res.text();
|
||||||
throw new Error(`RDAP ${res.status}: ${bodyText.slice(0, 500)}`);
|
throw new Error(`RDAP ${res.status}: ${bodyText.slice(0, 500)}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user