Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced turbopack alias for canvas in next.config.mjs. - Updated package.json scripts for development and branding tasks. - Added new dependencies for Tiptap extensions. - Implemented new demo layouts for agenda, contacts, drive, and mail applications. - Enhanced globals.css for improved theming and splash screen animations. - Added OAuth callback handling for drive mounts. - Updated layout components to integrate new demo shells and improve structure.
61 lines
1.7 KiB
TypeScript
61 lines
1.7 KiB
TypeScript
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 })
|
|
}
|
|
}
|