export async function readRequestBody(req, limitBytes) { const chunks = []; let size = 0; for await (const chunk of req) { size += chunk.length; if (size > limitBytes) { const error = new Error('Request body is too large'); error.status = 413; throw error; } chunks.push(chunk); } return Buffer.concat(chunks); } export function parseMultipart(buffer, contentType) { const match = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType || ''); if (!match) { const error = new Error('Missing multipart boundary'); error.status = 400; throw error; } const boundary = `--${match[1] || match[2]}`; const binary = buffer.toString('binary'); const parts = []; for (const raw of binary.split(boundary)) { if (!raw || raw === '--\r\n' || raw === '--') { continue; } const trimmed = raw.replace(/^\r\n/, '').replace(/\r\n--$/, ''); const separator = trimmed.indexOf('\r\n\r\n'); if (separator === -1) { continue; } const headerText = trimmed.slice(0, separator); let bodyText = trimmed.slice(separator + 4); if (bodyText.endsWith('\r\n')) { bodyText = bodyText.slice(0, -2); } const disposition = /content-disposition:\s*form-data;\s*([^\r\n]+)/i.exec(headerText)?.[1] || ''; const name = /name="([^"]+)"/i.exec(disposition)?.[1] || ''; const filename = /filename="([^"]*)"/i.exec(disposition)?.[1] || ''; parts.push({ name, filename, data: Buffer.from(bodyText, 'binary') }); } return parts; }