import { error } from '@sveltejs/kit'; import { MAX_ARTIFACT_BYTES } from '../artifact-limits.js'; export const JSON_BODY_LIMIT = 1024 * 1024; // Leave room for form fields and multipart framing around a maximum-size file. export const ARTIFACT_FORM_LIMIT = MAX_ARTIFACT_BYTES + 1024 * 1024; export const BETA_REVISION_FORM_LIMIT = 2 * MAX_ARTIFACT_BYTES + 1024 * 1024; /** Enforce the limit before parsing, including absent or understated lengths. */ export async function boundedBody(request: Request, maxBytes: number) { if (Number(request.headers.get('content-length')) > maxBytes) { void request.body?.cancel().catch(() => {}); error(413, 'Request is too large.'); } const reader = request.body?.getReader(); if (!reader) return Buffer.alloc(0); const chunks: Buffer[] = []; let block = Buffer.alloc(0); let used = 0; let received = 0; try { while (true) { const { done, value } = await reader.read(); if (done) break; received += value.byteLength; if (received > maxBytes) { // Cancellation may wait for another branch of a cloned request. void reader.cancel().catch(() => {}); error(413, 'Request is too large.'); } // Coalesce tiny chunks so bookkeeping cannot outgrow the byte limit. let offset = 0; while (offset < value.byteLength) { if (used === block.length) { block = Buffer.allocUnsafe(Math.min(maxBytes, 64 * 1024)); chunks.push(block); used = 0; } const length = Math.min(value.byteLength - offset, block.length - used); block.set(value.subarray(offset, offset + length), used); used += length; offset += length; } } } finally { reader.releaseLock(); } return Buffer.concat(chunks, received); } // Match Request.json()'s result type; each route retains its input validation. export async function jsonBody( request: Request, maxBytes = JSON_BODY_LIMIT ): ReturnType { return JSON.parse((await boundedBody(request, maxBytes)).toString('utf8')); } export async function artifactFormBody( request: Request, maxBytes = ARTIFACT_FORM_LIMIT ): Promise { const body = await boundedBody(request, maxBytes); let form: FormData; try { form = await new Response(body, { headers: { 'Content-Type': request.headers.get('content-type') ?? '' } }).formData(); } catch { error(400, 'Request body must be valid form data.'); } // Validate every part before any handler copies file bytes or writes versions. for (const value of form.values()) { const size = typeof value === 'string' ? Buffer.byteLength(value, 'utf8') : value.size; if (size > MAX_ARTIFACT_BYTES) error(413, 'Artifacts must not exceed 32 MiB.'); } return form; }