Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
43 lines
1.2 KiB
TypeScript
43 lines
1.2 KiB
TypeScript
/** Reads a request body without buffering more than maxBytes. */
|
|
export async function readBoundedBody(request: Request, maxBytes: number): Promise<string | null> {
|
|
const contentLengthHeader = request.headers.get("content-length");
|
|
if (contentLengthHeader !== null) {
|
|
const contentLength = Number(contentLengthHeader);
|
|
if (!Number.isFinite(contentLength) || contentLength < 0 || contentLength > maxBytes) {
|
|
cancelBody(request.body);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (!request.body) return "";
|
|
|
|
const reader = request.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let bytes = 0;
|
|
let body = "";
|
|
try {
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
bytes += value.byteLength;
|
|
if (bytes > maxBytes) {
|
|
cancelBody(reader);
|
|
return null;
|
|
}
|
|
body += decoder.decode(value, { stream: true });
|
|
}
|
|
return body + decoder.decode();
|
|
} finally {
|
|
reader.releaseLock();
|
|
}
|
|
}
|
|
|
|
/** Best-effort body/reader cancel; must not delay a rejection response. */
|
|
export function cancelBody(body: { cancel(): Promise<void> } | null): void {
|
|
try {
|
|
void body?.cancel().catch(() => undefined);
|
|
} catch {
|
|
// Ignore cancel failures.
|
|
}
|
|
}
|