import { serve } from "https://deno.land/std@0.208.0/http/server.ts"; const HOST = Deno.env.get("HOST") ?? "0.0.0.0"; const PORT = Number(Deno.env.get("PORT") ?? "7860"); const DOH = (Deno.env.get("DOH") ?? "cloudflare-dns.com") .replace(/^https?:\/\//, "") .split("/")[0]; const CUSTOM_PATH = (Deno.env.get("DOH_PATH") ?? "").trim().replace(/^\/+|\/+$/g, ""); function isDnsQueryPath(p: string): boolean { if (!CUSTOM_PATH || CUSTOM_PATH === "dns-query") return p === "/dns-query"; return p === `/${CUSTOM_PATH}`; } function cors(h = new Headers()) { h.set("access-control-allow-origin", "*"); h.set("access-control-allow-methods", "GET,POST,OPTIONS"); h.set("access-control-allow-headers", "*"); return h; } const JSON_HEADERS: HeadersInit = { accept: "application/dns-json", "user-agent": "HongShi-DoH/edge", }; const BIN_HEADERS: HeadersInit = { accept: "application/dns-message", "user-agent": "HongShi-DoH/edge", }; const mime: Record = { html: "text/html; charset=utf-8", css: "text/css; charset=utf-8", js: "text/javascript; charset=utf-8", json: "application/json; charset=utf-8", png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", svg: "image/svg+xml", ico: "image/x-icon", txt: "text/plain; charset=utf-8", wasm: "application/wasm", webp: "image/webp", woff2: "font/woff2", }; async function serveFile(path: string) { try { const data = await Deno.readFile(path); const ext = path.split(".").pop()?.toLowerCase() ?? "txt"; const ct = mime[ext] ?? "application/octet-stream"; return new Response(data, { headers: cors(new Headers({ "content-type": ct })) }); } catch { return null; } } function safeJoinPublic(urlPath: string) { const clean = urlPath.replace(/^\/+/, "").replace(/\.\.+/g, ""); return `./public/${clean}`; } async function dohBinaryProxy(req: Request) { const url = new URL(req.url); const upstream = `https://${DOH}/dns-query` + (url.search || ""); const isGet = url.searchParams.has("dns"); const isPost = req.method === "POST" && (req.headers.get("content-type") || "").startsWith("application/dns-message"); if (!isGet && !isPost) { return new Response("Bad Request", { status: 400, headers: cors() }); } const init: RequestInit = isGet ? { headers: BIN_HEADERS } : { method: "POST", headers: { "content-type": "application/dns-message" }, body: await req.arrayBuffer(), }; const r = await fetch(upstream, init); return new Response(r.body, { status: r.status, headers: cors(new Headers({ "content-type": "application/dns-message" })), }); } async function resolveJson(name: string) { const mk = (t: string) => `https://${DOH}/dns-query?name=${encodeURIComponent(name)}&type=${t}`; const [a, aaaa, ns] = await Promise.all([ fetch(mk("A"), { headers: JSON_HEADERS }).then((r) => r.json()).catch(() => null), fetch(mk("AAAA"), { headers: JSON_HEADERS }).then((r) => r.json()).catch(() => null), fetch(mk("NS"), { headers: JSON_HEADERS }).then((r) => r.json()).catch(() => null), ]); return { A: a?.Answer ?? [], AAAA: aaaa?.Answer ?? [], NS: ns?.Answer ?? [] }; } async function handler(req: Request) { const url = new URL(req.url); const p = url.pathname; if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: cors() }); if (req.method === "GET" && p === "/") { const home = await serveFile("./public/index.html"); if (home) return home; const html = `HongShi-DoH

HongShi-DoH

UI · /meta · /ip · /resolve

`; return new Response(html, { headers: cors(new Headers({ "content-type": "text/html; charset=utf-8" })), }); } if (req.method === "GET" && (p === "/ui" || p === "/ui/")) { if (p === "/ui") return new Response("", { status: 302, headers: { Location: "/ui/" } }); const ui = await serveFile("./public/ui/index.html"); return ui ?? new Response("Not Found", { status: 404, headers: cors() }); } if (req.method === "GET" && p.startsWith("/ui/")) { const file = safeJoinPublic(p.replace(/^\/ui\//, "ui/")); const res = await serveFile(file); return res ?? new Response("Not Found", { status: 404, headers: cors() }); } if (req.method === "GET") { const file = safeJoinPublic(p); const res = await serveFile(file); if (res) return res; } if (req.method === "GET" && p === "/meta") { const effectivePath = (!CUSTOM_PATH || CUSTOM_PATH === "dns-query") ? "/dns-query" : `/${CUSTOM_PATH}`; const body = { doh: DOH, doh_path: CUSTOM_PATH || "dns-query", effective_path: effectivePath, platform: "hf-space", env: ["HOST", "PORT", "DOH", "DOH_PATH"] .filter((k) => Deno.env.get(k)) .reduce((o, k) => { o[k] = Deno.env.get(k)!; return o; }, {} as Record), }; return new Response(JSON.stringify(body), { headers: cors(new Headers({ "content-type": "application/json" })), }); } if (req.method === "GET" && p === "/host") { const body = { hostname: Deno.env.get("HOSTNAME") ?? null, platform: "hf-space" }; return new Response(JSON.stringify(body), { headers: cors(new Headers({ "content-type": "application/json" })), }); } if (req.method === "GET" && p === "/ip") { const h = req.headers; const ip = h.get("x-real-ip") ?? h.get("cf-connecting-ip") ?? h.get("fly-client-ip") ?? ((h.get("x-forwarded-for") || "").split(",")[0].trim() || ""); const body = { ip, source: { platform: "hf-space", provider: "headers", enriched: false } }; return new Response(JSON.stringify(body), { headers: cors(new Headers({ "content-type": "application/json" })), }); } if (req.method === "GET" && p === "/resolve") { const name = url.searchParams.get("name"); if (!name) { return new Response(JSON.stringify({ ok: false, err: "Missing name" }), { status: 400, headers: cors(new Headers({ "content-type": "application/json" })), }); } const data = await resolveJson(name); return new Response(JSON.stringify({ ok: true, name, records: data }), { headers: cors(new Headers({ "content-type": "application/json" })), }); } if ((req.method === "GET" || req.method === "POST") && isDnsQueryPath(p)) { return dohBinaryProxy(req); } return new Response("Not Found", { status: 404, headers: cors() }); } console.log( `[HongShi-DoH] listening on http://${HOST}:${PORT} (upstream DoH: ${DOH}, path: ${ (!CUSTOM_PATH || CUSTOM_PATH === "dns-query") ? "/dns-query" : `/${CUSTOM_PATH}` })`, ); serve(handler, { hostname: HOST, port: PORT });