50 lines
1.6 KiB
JavaScript
50 lines
1.6 KiB
JavaScript
// Byte-capped body readers: never buffer more than a caller-set limit from a
|
|
// peer or remote API.
|
|
|
|
|
|
// A fetch Response body as a Buffer, refusing more than maxBytes: by declared
|
|
// Content-Length first, then by running count, so a lying or chunked response is
|
|
// cut off (leaving the for-await cancels the stream) rather than buffered.
|
|
const readCappedResponse = async (res, maxBytes) => {
|
|
const declared = Number(res.headers.get("content-length"));
|
|
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
throw new Error(`response too large (${declared} bytes exceeds ${maxBytes})`);
|
|
}
|
|
if (!res.body) {
|
|
return Buffer.alloc(0);
|
|
}
|
|
const chunks = [];
|
|
let total = 0;
|
|
for await (const chunk of res.body) {
|
|
total += chunk.length;
|
|
if (total > maxBytes) {
|
|
throw new Error(`response too large (exceeds ${maxBytes} bytes)`);
|
|
}
|
|
chunks.push(Buffer.from(chunk));
|
|
}
|
|
return Buffer.concat(chunks);
|
|
};
|
|
|
|
|
|
// An incoming request stream as a Buffer, or null once it exceeds maxBytes.
|
|
// Leaving the for-await early destroys the stream, so an oversized upload is cut
|
|
// off rather than buffered.
|
|
const readCappedRequest = async (req, maxBytes) => {
|
|
const chunks = [];
|
|
let total = 0;
|
|
for await (const chunk of req) {
|
|
const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
total += buf.length;
|
|
if (total > maxBytes) {
|
|
return null;
|
|
}
|
|
chunks.push(buf);
|
|
}
|
|
return Buffer.concat(chunks);
|
|
};
|
|
|
|
|
|
module.exports = {
|
|
readCappedResponse,
|
|
readCappedRequest
|
|
};
|