diff --git a/src/gateway/public-gateway.mjs b/src/gateway/public-gateway.mjs index 23817d9..8d8f59c 100644 --- a/src/gateway/public-gateway.mjs +++ b/src/gateway/public-gateway.mjs @@ -77,6 +77,19 @@ const nx = (h) => `

// apex, current priority stands (h → s3 → ip → u). This does not break // Sia-per-subdomain owners: if they omit `ip` from the parent, the fallback // still tries `s3` first as before. +function ipFetch(ip, reqPath, hostHeader) { + return new Promise((resolve, reject) => { + const req = http.request({ host: ip, port: 80, path: reqPath, method: "GET", headers: { Host: hostHeader } }, (r) => { + const chunks = []; + r.on("data", (c) => chunks.push(c)); + r.on("end", () => resolve({ status: r.statusCode, headers: r.headers, body: Buffer.concat(chunks) })); + }); + req.on("error", reject); + req.setTimeout(15000, () => req.destroy(new Error("upstream timeout"))); + req.end(); + }); +} + async function serve(res, name, reqPath, baseHref) { let entry; try { entry = await resolve(name); } catch (e) { res.writeHead(502); return res.end("resolve error: " + e.message); } @@ -102,11 +115,15 @@ async function serve(res, name, reqPath, baseHref) { res.writeHead(up.status, { "content-type": ct }); return res.end(buf); } case "ip": { - const up = await fetch(`http://${r.ip}${reqPath}`, { headers: { host: name } }); - const buf = Buffer.from(await up.arrayBuffer()); - let ct = up.headers.get("content-type") || guess(reqPath); - if (ct.includes("text/html")) { res.writeHead(up.status, { "content-type": ct }); return res.end(injectBase(buf.toString("utf8"), baseHref)); } - res.writeHead(up.status, { "content-type": ct }); return res.end(buf); + // http.request instead of fetch(): WHATWG fetch treats Host as a + // forbidden header, so undici silently drops { headers: { host: name } } + // and sends Host: . nginx then falls to default_server and 404s. + // The whole point of an ip record is to reach a server_name matching the + // BCNR name, so Host must be right. + const up = await ipFetch(r.ip, reqPath, name); + let ct = up.headers["content-type"] || guess(reqPath); + if (ct.includes("text/html")) { res.writeHead(up.status, { "content-type": ct }); return res.end(injectBase(up.body.toString("utf8"), baseHref)); } + res.writeHead(up.status, { "content-type": ct }); return res.end(up.body); } case "u": res.writeHead(302, { location: r.u }); return res.end();