import type { IncomingMessage } from 'http'; export interface ReadRequestBodyOptions { maxBytes: number; } export class RequestBodyTooLargeError extends Error { readonly maxBytes: number; readonly receivedBytes: number; constructor(maxBytes: number, receivedBytes: number) { super(`Request body too large (${receivedBytes} bytes > ${maxBytes} bytes)`); this.name = 'RequestBodyTooLargeError'; this.maxBytes = maxBytes; this.receivedBytes = receivedBytes; } } /** Read the full request body with an explicit max-size limit. */ export function readRequestBody(req: IncomingMessage, opts: ReadRequestBodyOptions): Promise { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; let totalBytes = 0; let settled = false; const cleanup = () => { req.off('data', onData); req.off('end', onEnd); req.off('error', onError); }; const fail = (err: Error) => { if (settled) {return;} settled = true; cleanup(); reject(err); }; const onData = (chunk: Buffer | string) => { const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); totalBytes += buf.length; if (totalBytes > opts.maxBytes) { if (typeof req.destroy === 'function') { req.destroy(); } fail(new RequestBodyTooLargeError(opts.maxBytes, totalBytes)); return; } chunks.push(buf); }; const onEnd = () => { if (settled) {return;} settled = true; cleanup(); resolve(Buffer.concat(chunks).toString('utf-8')); }; const onError = (err: Error) => fail(err); req.on('data', onData); req.on('end', onEnd); req.on('error', onError); }); }