import { lookup } from "node:dns/promises"; import { isIP } from "node:net"; const MAX_MEDIA_SOURCE_BYTES = 100 * 1024 * 1024; const PRIVATE_IPV4_RANGES = [ /^10\./, /^0\./, /^127\./, /^169\.254\./, /^172\.(1[6-9]|2\d|3[01])\./, /^192\.168\./, /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./, /^(22[4-9]|23\d|24\d|25[0-5])\./, ] as const; function isPrivateAddress(address: string) { const normalized = address.toLowerCase(); if (isIP(normalized) === 4) { return PRIVATE_IPV4_RANGES.some((pattern) => pattern.test(normalized)); } if (isIP(normalized) === 6) { const mappedIpv4 = normalized.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/)?.[1]; return ( normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:") || Boolean(mappedIpv4 && PRIVATE_IPV4_RANGES.some((pattern) => pattern.test(mappedIpv4))) ); } return false; } async function resolvesToPrivateAddress(hostname: string) { const normalized = hostname.toLowerCase().replace(/\.$/, "").replace(/^\[|\]$/g, ""); if ( normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || normalized.endsWith(".internal") ) { return true; } if (isIP(normalized)) return isPrivateAddress(normalized); const addresses = await lookup(normalized, { all: true, verbatim: true }); return addresses.length === 0 || addresses.some(({ address }) => isPrivateAddress(address)); } type MediaSourceProxyOptions = { accept: string; contentTypePrefix: "audio/" | "video/"; extensionContentTypes: Readonly>; }; function inferMediaContentType(sourceUrl: URL, options: MediaSourceProxyOptions) { const extension = sourceUrl.pathname.toLowerCase().match(/\.([a-z0-9]+)$/)?.[1]; return extension ? options.extensionContentTypes[extension] : undefined; } async function proxyCanvasMediaSource( request: Request, options: MediaSourceProxyOptions, fetcher: typeof fetch = fetch, ) { const source = new URL(request.url).searchParams.get("url"); if (!source) { return Response.json({ error: "Audio URL is required" }, { status: 400 }); } let sourceUrl: URL; try { sourceUrl = new URL(source); } catch { return Response.json({ error: "Invalid audio URL" }, { status: 400 }); } if (sourceUrl.protocol !== "https:") { return Response.json({ error: "Unsupported audio URL" }, { status: 400 }); } try { if (await resolvesToPrivateAddress(sourceUrl.hostname)) { return Response.json({ error: "Unsupported audio URL" }, { status: 400 }); } const upstream = await fetcher(sourceUrl, { headers: { Accept: options.accept }, redirect: "error", signal: AbortSignal.timeout(15_000), }); if (!upstream.ok || !upstream.body) { return Response.json({ error: "Audio source unavailable" }, { status: 502 }); } const upstreamContentType = upstream.headers.get("content-type")?.toLowerCase() || ""; const inferredContentType = inferMediaContentType(sourceUrl, options); const isGenericOrMislabeled = upstreamContentType === "application/octet-stream" || upstreamContentType === "text/plain"; if ( !upstreamContentType.startsWith(options.contentTypePrefix) && !(isGenericOrMislabeled && inferredContentType) ) { return Response.json({ error: "Unsupported media response" }, { status: 415 }); } const contentType = upstreamContentType.startsWith(options.contentTypePrefix) ? upstreamContentType : inferredContentType || "application/octet-stream"; const contentLength = Number(upstream.headers.get("content-length") || 0); if (contentLength > MAX_MEDIA_SOURCE_BYTES) { return Response.json({ error: "Media source is too large" }, { status: 413 }); } let transferredBytes = 0; const limitedBody = upstream.body.pipeThrough( new TransformStream({ transform(chunk, controller) { transferredBytes += chunk.byteLength; if (transferredBytes > MAX_MEDIA_SOURCE_BYTES) { throw new Error("Media source is too large"); } controller.enqueue(chunk); }, }), ); return new Response(limitedBody, { headers: { "Cache-Control": "private, max-age=300", "Content-Type": contentType || "application/octet-stream", }, }); } catch { return Response.json({ error: "Audio source unavailable" }, { status: 502 }); } } export function proxyCanvasAudioSource(request: Request, fetcher: typeof fetch = fetch) { return proxyCanvasMediaSource( request, { accept: "audio/*,application/octet-stream;q=0.8", contentTypePrefix: "audio/", extensionContentTypes: { aac: "audio/aac", flac: "audio/flac", m4a: "audio/mp4", mp3: "audio/mpeg", ogg: "audio/ogg", wav: "audio/wav", }, }, fetcher, ); } export function proxyCanvasVideoSource(request: Request, fetcher: typeof fetch = fetch) { return proxyCanvasMediaSource( request, { accept: "video/*,application/octet-stream;q=0.8", contentTypePrefix: "video/", extensionContentTypes: { m4v: "video/mp4", mov: "video/quicktime", mp4: "video/mp4", ogv: "video/ogg", webm: "video/webm", }, }, fetcher, ); }