Add Fedimint discovery, dual SQLite/Postgres storage, richer review handling, and generated social imagery.
40 lines
1.3 KiB
TypeScript
40 lines
1.3 KiB
TypeScript
/**
|
|
* Read a response body up to `maxBytes`, or refuse it.
|
|
*
|
|
* `res.arrayBuffer()` and `res.json()` buffer however much the server sends before any
|
|
* size check can run, and a mint is an untrusted server: the abort timer bounds how
|
|
* *long* a read may take, this bounds how *large* it may get, and both are needed. The
|
|
* Content-Length header is checked first as a courtesy — a chunked or lying response
|
|
* still hits the streaming cap.
|
|
*
|
|
* Returns null when the body is over the limit or unreadable. The caller treats that
|
|
* exactly like a failed fetch.
|
|
*/
|
|
export async function readBodyBounded(res: Response, maxBytes: number): Promise<Buffer | null> {
|
|
const declared = Number(res.headers.get('content-length') ?? '');
|
|
if (Number.isFinite(declared) && declared > maxBytes) return null;
|
|
|
|
const reader = res.body?.getReader();
|
|
if (!reader) return null;
|
|
|
|
const chunks: Uint8Array[] = [];
|
|
let size = 0;
|
|
try {
|
|
for (;;) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
size += value.byteLength;
|
|
if (size > maxBytes) {
|
|
await reader.cancel().catch(() => undefined);
|
|
return null;
|
|
}
|
|
chunks.push(value);
|
|
}
|
|
} catch {
|
|
// Aborted by the caller's timer, or the connection died mid-body.
|
|
return null;
|
|
}
|
|
|
|
return Buffer.concat(chunks);
|
|
}
|