diff --git a/app/api/links/route.ts b/app/api/links/route.ts index d3b86a9..029ee48 100644 --- a/app/api/links/route.ts +++ b/app/api/links/route.ts @@ -26,6 +26,37 @@ function passouDoLimite(ip: string): boolean { const erro = (status: number, mensagem: string) => Response.json({ erro: mensagem }, { status, headers: CORS }); +const CORPO_INVALIDO = 'Corpo inválido. Envie JSON no formato {"url": "https://..."}.'; +// Um pedido de encurtar link cabe com folga em 10 KB; mais que isso é recusado +// sem ler o resto (o app é público: ninguém deve conseguir mandar megabytes). +const TAMANHO_MAXIMO_CORPO = 10 * 1024; + +/** Lê o corpo em pedaços e para assim que passar do limite (não confia só no Content-Length). */ +async function lerJsonLimitado(request: Request): Promise<{ url?: unknown } | null> { + const declarado = request.headers.get('content-length'); + if (declarado !== null && !(/^\d+$/.test(declarado) && Number(declarado) <= TAMANHO_MAXIMO_CORPO)) return null; + if (!request.body) return null; + const leitor = request.body.getReader(); + const pedacos: Uint8Array[] = []; + let total = 0; + for (;;) { + const { done, value } = await leitor.read(); + if (done) break; + total += value.byteLength; + if (total > TAMANHO_MAXIMO_CORPO) { + await leitor.cancel(); + return null; + } + pedacos.push(value); + } + try { + const dados: unknown = JSON.parse(Buffer.concat(pedacos).toString('utf8')); + return dados && typeof dados === 'object' && !Array.isArray(dados) ? (dados as { url?: unknown }) : null; + } catch { + return null; + } +} + export function OPTIONS() { return new Response(null, { status: 204, headers: CORS }); } @@ -42,14 +73,10 @@ export async function POST(request: Request) { const ip = request.headers.get('cf-connecting-ip') ?? request.headers.get('x-forwarded-for')?.split(',')[0].trim() ?? '?'; if (passouDoLimite(ip)) return erro(429, 'Muitos links em pouco tempo. Espere alguns minutos e tente de novo.'); - let corpo: { url?: unknown } | null; + const corpo = await lerJsonLimitado(request); + if (!corpo) return erro(400, CORPO_INVALIDO); try { - corpo = await request.json(); - } catch { - return erro(400, 'Corpo inválido. Envie JSON no formato {"url": "https://..."}.'); - } - try { - const link = criarLink(corpo?.url); + const link = criarLink(corpo.url); return Response.json(paraJson(link, enderecoBase(request.headers)), { status: 201, headers: CORS }); } catch (e) { if (e instanceof ErroDeValidacao) return erro(400, e.message);