CORS-Proxy Deno
const ALLOWED_ORIGINS = ["*"]; const ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS","MKCOL","PROPFIND"];
const TARGET_HOST_ALLOWLIST = new Set<string>([
"v1.hitokoto.cn",
"thequoteshub.com",
]);
const FORWARD_REQ_HEADERS = new Set([
"accept",
"accept-language",
"content-type",
"authorization",
"x-requested-with",
"user-agent",
]);
const EXPOSE_HEADERS = [
"content-type",
"content-length",
"date",
"etag",
"cache-control",
"expires",
"last-modified",
"x-request-id",
];
function pickCorsOrigin(origin: string | null) {
if (!origin) return "*";
if (ALLOWED_ORIGINS.includes("*")) return "*";
return ALLOWED_ORIGINS.includes(origin) ? origin : "null";
}
function isAllowedTarget(url: URL) {
if (!["http:", "https:"].includes(url.protocol)) return false;
return true;
}
function buildCorsHeaders(origin: string | null) {
const allowOrigin = pickCorsOrigin(origin);
return new Headers({
"access-control-allow-origin": allowOrigin,
"access-control-allow-methods": ALLOWED_METHODS.join(", "),
"access-control-allow-headers": "Content-Type, Authorization, X-Requested-With,Depth",
"access-control-expose-headers": EXPOSE_HEADERS.join(", "),
"access-control-max-age": "86400",
"vary": "Origin",
});
}
function filterRequestHeaders(req: Request) {
const headers = new Headers();
for (const [k, v] of req.headers.entries()) {
const key = k.toLowerCase();
if (FORWARD_REQ_HEADERS.has(key)) headers.set(k, v);
}
return headers;
}
Deno.serve(async (req) => {
const url = new URL(req.url);
const origin = req.headers.get("origin");
if (req.method === "OPTIONS") {
return new Response(null, { status: 204, headers: buildCorsHeaders(origin) });
}
if (!ALLOWED_METHODS.includes(req.method)) {
const h = buildCorsHeaders(origin);
return new Response("Method Not Allowed", { status: 405, headers: h });
}
const targetParam = url.searchParams.get("url");
if (!targetParam) {
const h = buildCorsHeaders(origin);
return new Response("Missing ?url= encoded target URL", { status: 400, headers: h });
}
let targetUrl: URL;
try {
targetUrl = new URL(decodeURIComponent(targetParam));
} catch {
const h = buildCorsHeaders(origin);
return new Response("Invalid target url", { status: 400, headers: h });
}
if (!isAllowedTarget(targetUrl)) {
const h = buildCorsHeaders(origin);
return new Response("Target host not allowed", { status: 403, headers: h });
}
for (const [k, v] of url.searchParams.entries()) {
if (k === "url") continue;
targetUrl.searchParams.set(k, v);
}
const forwardHeaders = filterRequestHeaders(req);
const hasBody = !["GET", "HEAD"].includes(req.method);
const body = hasBody ? req.body : undefined;
let upstreamResp: Response;
try {
upstreamResp = await fetch(targetUrl.toString(), {
method: req.method,
headers: forwardHeaders,
body,
redirect: "follow",
});
} catch (e) {
const h = buildCorsHeaders(origin);
return new Response(`Upstream fetch failed: ${String(e)}`, { status: 502, headers: h });
}
const respHeaders = new Headers();
for (const [k, v] of upstreamResp.headers.entries()) {
const key = k.toLowerCase();
if (key === "set-cookie") continue; if (key.startsWith("access-control-")) continue;
respHeaders.set(k, v);
}
const corsHeaders = buildCorsHeaders(origin);
for (const [k, v] of corsHeaders.entries()) respHeaders.set(k, v);
return new Response(upstreamResp.body, {
status: upstreamResp.status,
statusText: upstreamResp.statusText,
headers: respHeaders,
});
});