import type { NextApiRequest, NextApiResponse } from 'next' export const config = { api: { bodyParser: false, responseLimit: '8mb' } } export default async function handler(req: NextApiRequest, res: NextApiResponse) { const path = req.query.path if (!Array.isArray(path) || path.some((p) => p === '..' || p.includes('/') || p.includes('\\'))) return res.status(400).json({ error: 'Invalid path' }) const base = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000' const target = new URL('/' + path.map(encodeURIComponent).join('/'), base) const incoming = new URL(req.url ?? '/', 'http://localhost') target.search = incoming.search const headers = new Headers() const hop = new Set([ 'host', 'connection', 'transfer-encoding', 'content-length', 'keep-alive', 'upgrade', 'x-forwarded-host', 'x-forwarded-proto', ]) for (const [k, v] of Object.entries(req.headers)) if (v && !hop.has(k)) headers.set(k, Array.isArray(v) ? v.join(',') : v) headers.set('accept-encoding', 'identity') try { const chunks: Buffer[] = [] let bytes = 0 for await (const chunk of req) { const b = Buffer.from(chunk) bytes += b.length if (bytes > 1024 * 1024) return res.status(413).end() chunks.push(b) } const body = req.method === 'GET' || req.method === 'HEAD' ? undefined : Buffer.concat(chunks) const upstream = await fetch(target, { method: req.method, headers, body, signal: AbortSignal.timeout(30_000), redirect: 'manual', }) upstream.headers.forEach((v, k) => { if (!hop.has(k) && k !== 'content-encoding') res.setHeader(k, v) }) res.status(upstream.status).send(Buffer.from(await upstream.arrayBuffer())) } catch { res.status(502).json({ error: 'Upstream request failed' }) } }