diff --git a/src/gateway/public-gateway.mjs b/src/gateway/public-gateway.mjs index f11192b..a78ab84 100644 --- a/src/gateway/public-gateway.mjs +++ b/src/gateway/public-gateway.mjs @@ -267,6 +267,32 @@ function injectBase(html, href) { if (/]*>/i.test(html)) return html.replace(/]*>/i, (m) => m + ``); return `` + html; } +// TLDs the gateway will route through /bns/ when it sees them on a cross-name +// absolute link inside served HTML. Restricted to TLDs that are BCDN-only or +// deliberately BCDN-shadowed (never a live ICANN answer today) so we don't +// mis-hijack an ordinary web reference. .st, .com, .gt, .sc are intentionally +// EXCLUDED — they live in ICANN and rewriting them would break real links. +const BNS_TLDS_REWRITABLE = new Set(["bch","x","p2p","deviant","bit","nav","test","coin","cash"]); +// Rewrite to +// so an ordinary browser served through the relay follows the link back through +// the relay instead of trying to resolve the BCDN name via DNS (which returns +// NXDOMAIN). This is a NO-OP on the native origin (Theseus/Ariadne), because +// the gateway never serves there — those clients see the original URLs. +function rewriteBnsLinks(html) { + return html.replace( + /\bhref\s*=\s*"(https?:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*\.([a-z0-9-]+))(\/[^"]*)?)"/gi, + (m, full, host, tld, path) => { + if (!BNS_TLDS_REWRITABLE.has(tld.toLowerCase())) return m; + return `href="/bns/${host.toLowerCase()}${path || "/"}"`; + } + ); +} +// Apply both transforms (base first, then link rewrite) — order doesn't +// matter because they touch disjoint constructs, but grouping them here +// means every text/html response goes through the same path. +function transformHtml(html, baseHref) { + return rewriteBnsLinks(injectBase(html, baseHref)); +} const nx = (h) => `

NXDOMAIN

${h} is not registered on the Bitcoin Cash chain.

`; // BNS `ip`-record fetch. The default `fetch` fails here for two reasons: @@ -404,7 +430,7 @@ async function serve(res, name, reqPath, baseHref) { case "h": if (reqPath !== "/") { res.writeHead(404); return res.end("not found"); } res.writeHead(200, { "content-type": "text/html; charset=utf-8" }); - return res.end(injectBase(r.h, baseHref)); + return res.end(transformHtml(r.h, baseHref)); case "s3": { const sub = subFolder(name); let base = r.s3; if (base.endsWith("/") && sub) base += sub + "/"; @@ -442,7 +468,7 @@ async function serve(res, name, reqPath, baseHref) { } const buf = Buffer.from(await up.arrayBuffer()); let ct = up.headers.get("content-type"); if (!ct || ct === "application/octet-stream") ct = guess(key); - if (ct.includes("text/html")) { res.writeHead(up.status, { "content-type": ct }); return res.end(injectBase(buf.toString("utf8"), baseHref)); } + if (ct.includes("text/html")) { res.writeHead(up.status, { "content-type": ct }); return res.end(transformHtml(buf.toString("utf8"), baseHref)); } res.writeHead(up.status, { "content-type": ct }); return res.end(buf); } case "ip": { @@ -464,7 +490,7 @@ async function serve(res, name, reqPath, baseHref) { if (lk === "content-type") continue; outHeaders[k] = v; } - if (ct.includes("text/html")) { res.writeHead(up.status, outHeaders); return res.end(injectBase(buf.toString("utf8"), baseHref)); } + if (ct.includes("text/html")) { res.writeHead(up.status, outHeaders); return res.end(transformHtml(buf.toString("utf8"), baseHref)); } res.writeHead(up.status, outHeaders); return res.end(buf); } case "p": { @@ -483,7 +509,7 @@ async function serve(res, name, reqPath, baseHref) { if (lk === "content-type") continue; outHeaders[k] = v; } - if (ct.includes("text/html")) { res.writeHead(up.status, outHeaders); return res.end(injectBase(buf.toString("utf8"), baseHref)); } + if (ct.includes("text/html")) { res.writeHead(up.status, outHeaders); return res.end(transformHtml(buf.toString("utf8"), baseHref)); } res.writeHead(up.status, outHeaders); return res.end(buf); } case "u":