export const REQUEST_HOP_BY_HOP_HEADERS = new Set([ "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", "host", ]); export const RESPONSE_HOP_BY_HOP_HEADERS = new Set([ "connection", "keep-alive", "transfer-encoding", "content-encoding", ]); export const readBearer = (req) => { const raw = req.headers.authorization ?? ""; const m = /^Bearer\s+(.+)$/i.exec(raw); return m?.[1]?.trim() ?? null; }; export const readBodyBuffer = async (req, maxBytes) => { const chunks = []; let total = 0; for await (const chunk of req) { const b = typeof chunk === "string" ? Buffer.from(chunk) : chunk; total += b.length; if (total > maxBytes) { throw new Error(`Request body exceeds ${maxBytes} bytes`); } chunks.push(b); } return Buffer.concat(chunks); }; export const copyHeadersForProxy = (req, opts) => { const out = new Headers(); for (const [k, v] of Object.entries(req.headers)) { if (!v) { continue; } const lk = k.toLowerCase(); if (REQUEST_HOP_BY_HOP_HEADERS.has(lk)) { continue; } if (lk === "authorization") { continue; } if (opts?.skipLowercase?.has(lk)) { continue; } out.set(k, Array.isArray(v) ? v.join(", ") : v); } return out; }; export const copyOutgoingHeadersForProxy = (req, opts) => { const out = {}; for (const [k, v] of Object.entries(req.headers)) { if (v === undefined) { continue; } const lk = k.toLowerCase(); if (REQUEST_HOP_BY_HOP_HEADERS.has(lk)) { continue; } if (lk === "authorization") { continue; } if (opts?.skipLowercase?.has(lk)) { continue; } out[k] = v; } return out; }; export const isSafeProxyPath = (p) => { if (!p.startsWith("/")) { return false; } for (const rawSeg of p.split("/")) { if (rawSeg.length === 0) { continue; } if (rawSeg === "." || rawSeg === "..") { return false; } let seg; try { seg = decodeURIComponent(rawSeg); } catch { return false; } if (seg === "." || seg === "..") { return false; } if (seg.includes("/") || seg.includes("\\")) { return false; } } return true; };