import { NextResponse } from "next/server" const MAX_ICS_BYTES = 2 * 1024 * 1024 function isAllowedIcsUrl(raw: string): boolean { try { const url = new URL(raw) if (url.protocol !== "http:" && url.protocol !== "https:") return false const host = url.hostname.toLowerCase() if (host === "localhost" || host === "127.0.0.1" || host === "::1") { return false } return true } catch { return false } } export async function GET(request: Request) { const url = new URL(request.url).searchParams.get("url")?.trim() if (!url || !isAllowedIcsUrl(url)) { return NextResponse.json({ error: "invalid_url" }, { status: 400 }) } try { const res = await fetch(url, { headers: { Accept: "text/calendar, text/plain, */*" }, redirect: "follow", cache: "no-store", }) if (!res.ok) { return NextResponse.json({ error: "fetch_failed" }, { status: 502 }) } const contentType = res.headers.get("content-type") ?? "" if ( contentType && !contentType.includes("text/calendar") && !contentType.includes("text/plain") && !contentType.includes("application/octet-stream") ) { return NextResponse.json({ error: "unsupported_content_type" }, { status: 415 }) } const buffer = await res.arrayBuffer() if (buffer.byteLength > MAX_ICS_BYTES) { return NextResponse.json({ error: "payload_too_large" }, { status: 413 }) } return new NextResponse(new TextDecoder("utf-8").decode(buffer), { status: 200, headers: { "Content-Type": "text/calendar; charset=utf-8", "Cache-Control": "private, max-age=300", }, }) } catch { return NextResponse.json({ error: "fetch_failed" }, { status: 502 }) } }