Files
immich-folio/lib/requestBody.ts
Ralksta ad8b09700b release: v0.19.0
Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of
[CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md)
for the full list.

Highlights: client proofing links (#683), custom content pages (#738),
asset picker search (#740), concurrent-edit detection (#742), env-locked
settings (#739), an accessibility baseline (#741), three security fixes
with advisories (#737, #755, #759), and a pre-release quality pass
(#780–#797).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ralf <rohlenbo79@gmail.com>
Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com>
Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
2026-09-29 22:50:30 +02:00

60 lines
1.9 KiB
TypeScript

/**
* Request bodies read with a hard size cap.
*
* `request.json()` parses the whole body before anything can object, which is
* the one place an unbounded payload could land. These read the stream by
* hand and stop the moment the cap is passed, so a chunked body is capped too.
*/
import type { NextRequest } from 'next/server';
/**
* Read a request body as raw bytes, aborting once it exceeds `limit` bytes.
* Null when the cap is passed. For callers that need the exact bytes on the
* wire, such as an HMAC over the body.
*/
export async function readBytesCapped(request: NextRequest, limit: number): Promise<Buffer | null> {
const stream = request.body;
if (!stream) return Buffer.alloc(0);
const reader = stream.getReader();
const chunks: Uint8Array[] = [];
let bytes = 0;
try {
for (;;) {
const { done, value } = await reader.read();
if (done) break;
bytes += value.byteLength;
if (bytes > limit) {
await reader.cancel();
return null;
}
chunks.push(value);
}
return Buffer.concat(chunks, bytes);
} finally {
reader.releaseLock();
}
}
/** Read a request body as text, aborting once it exceeds `limit` bytes. */
export async function readBodyCapped(request: NextRequest, limit: number): Promise<string | null> {
const bytes = await readBytesCapped(request, limit);
return bytes === null ? null : new TextDecoder().decode(bytes);
}
/**
* Read a JSON body of at most `limit` bytes. Null for an oversized, empty or
* unparseable body — the caller answers 400 either way.
*/
export async function readJsonCapped(request: NextRequest, limit: number): Promise<unknown> {
const declared = Number(request.headers.get('content-length') ?? '');
if (Number.isFinite(declared) && declared > limit) return null;
try {
const text = await readBodyCapped(request, limit);
return text ? JSON.parse(text) : null;
} catch {
return null;
}
}