bep40 commited on
Commit
7b4c475
·
verified ·
1 Parent(s): edafc28

Fix img proxy: browser UA + Referer to bypass CDN hotlink blocks, only serve image content-type (404 for HTML)

Browse files
Files changed (1) hide show
  1. index.ts +11 -2
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: { "User-Agent": "Mozilla/5.0 (compatible; VAI-Avatar2/1.0)" },
 
 
 
 
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": "*" } });