Spaces:
Running
Running
Fix img proxy: browser UA + Referer to bypass CDN hotlink blocks, only serve image content-type (404 for HTML)
Browse files
index.ts
CHANGED
|
@@ -2085,13 +2085,22 @@ const server = Bun.serve({
|
|
| 2085 |
const cached = IMAGE_CACHE.get(key);
|
| 2086 |
if (cached) return new Response(cached.body, { headers: { "Content-Type": cached.type, "Cache-Control": "public, max-age=86400", "Access-Control-Allow-Origin": "*" } });
|
| 2087 |
const resp = await fetch(upstream.href, {
|
| 2088 |
-
headers: {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2089 |
signal: AbortSignal.timeout(15000),
|
|
|
|
| 2090 |
});
|
| 2091 |
if (!resp.ok) return new Response("upstream " + resp.status, { status: 502 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2092 |
const buf = Buffer.from(await resp.arrayBuffer());
|
| 2093 |
if (buf.length > 8 * 1024 * 1024) return new Response("too large", { status: 413 });
|
| 2094 |
-
const type = resp.headers.get("content-type") || "image/jpeg";
|
| 2095 |
IMAGE_CACHE.set(key, { body: buf, type });
|
| 2096 |
if (IMAGE_CACHE.size > 300) { const first = IMAGE_CACHE.keys().next().value; if (first) IMAGE_CACHE.delete(first); }
|
| 2097 |
return new Response(buf, { headers: { "Content-Type": type, "Cache-Control": "public, max-age=86400", "Access-Control-Allow-Origin": "*" } });
|
|
|
|
| 2085 |
const cached = IMAGE_CACHE.get(key);
|
| 2086 |
if (cached) return new Response(cached.body, { headers: { "Content-Type": cached.type, "Cache-Control": "public, max-age=86400", "Access-Control-Allow-Origin": "*" } });
|
| 2087 |
const resp = await fetch(upstream.href, {
|
| 2088 |
+
headers: {
|
| 2089 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36",
|
| 2090 |
+
"Referer": upstream.origin + "/",
|
| 2091 |
+
"Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
|
| 2092 |
+
},
|
| 2093 |
signal: AbortSignal.timeout(15000),
|
| 2094 |
+
redirect: "follow",
|
| 2095 |
});
|
| 2096 |
if (!resp.ok) return new Response("upstream " + resp.status, { status: 502 });
|
| 2097 |
+
const type = (resp.headers.get("content-type") || "image/jpeg").split(";")[0].trim().toLowerCase();
|
| 2098 |
+
// Only serve actual image content — a CDN that returns HTML (bot
|
| 2099 |
+
// protection page, 404 page) must NOT be cached/served as an image.
|
| 2100 |
+
// 404 lets the frontend onerror hide the broken <img> cleanly.
|
| 2101 |
+
if (!/^image\//.test(type)) return new Response("not an image (" + type + ")", { status: 404 });
|
| 2102 |
const buf = Buffer.from(await resp.arrayBuffer());
|
| 2103 |
if (buf.length > 8 * 1024 * 1024) return new Response("too large", { status: 413 });
|
|
|
|
| 2104 |
IMAGE_CACHE.set(key, { body: buf, type });
|
| 2105 |
if (IMAGE_CACHE.size > 300) { const first = IMAGE_CACHE.keys().next().value; if (first) IMAGE_CACHE.delete(first); }
|
| 2106 |
return new Response(buf, { headers: { "Content-Type": type, "Cache-Control": "public, max-age=86400", "Access-Control-Allow-Origin": "*" } });
|