mirror of
https://github.com/jakejarvis/rdapper.git
synced 2026-01-27 20:35:18 -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", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
15
src/index.ts
15
src/index.ts
@@ -47,7 +47,20 @@ export async function lookup(
|
||||
for (const base of bases) {
|
||||
tried.push(base);
|
||||
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(
|
||||
domain,
|
||||
json,
|
||||
|
||||
@@ -3,15 +3,27 @@ import { DEFAULT_TIMEOUT_MS } from "../lib/constants";
|
||||
import { resolveFetch } from "../lib/fetch";
|
||||
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.
|
||||
* 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(
|
||||
domain: string,
|
||||
baseUrl: string,
|
||||
options?: LookupOptions,
|
||||
): Promise<{ url: string; json: unknown }> {
|
||||
): Promise<RdapFetchResult> {
|
||||
const url = new URL(
|
||||
`domain/${encodeURIComponent(domain)}`,
|
||||
baseUrl,
|
||||
@@ -26,6 +38,11 @@ export async function fetchRdapDomain(
|
||||
options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
||||
"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) {
|
||||
const bodyText = await res.text();
|
||||
throw new Error(`RDAP ${res.status}: ${bodyText.slice(0, 500)}`);
|
||||
|
||||
Reference in New Issue
Block a user