UNPKG

@whatwg-node/server

Version:

Fetch API compliant HTTP Server adapter

96 lines (95 loc) • 4.28 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InvalidContentLengthError = exports.RequestBodyTooLargeError = void 0; exports.useLimitRequestBodySize = useLimitRequestBodySize; const useErrorHandling_js_1 = require("./useErrorHandling.js"); class RequestBodyTooLargeError extends useErrorHandling_js_1.HTTPError { name = 'RequestBodyTooLargeError'; constructor(message = 'Request body too large') { super(413, message); } } exports.RequestBodyTooLargeError = RequestBodyTooLargeError; class InvalidContentLengthError extends useErrorHandling_js_1.HTTPError { name = 'InvalidContentLengthError'; constructor(message = 'Content-Length header is invalid.') { super(400, message); } } exports.InvalidContentLengthError = InvalidContentLengthError; // Only a single non-negative integer is a valid Content-Length. Anything else (non-numeric, // negative, or multiple comma-joined values as seen in request-smuggling attempts) is rejected // outright instead of being allowed to silently skip this check. const CONTENT_LENGTH_RE = /^\d+$/; function defaultResponseFromError(error, fetchAPI) { return new fetchAPI.Response(error.message, { status: error.status, headers: error.headers, }); } /** * Limits the size of incoming HTTP request bodies. * * Requests whose `Content-Length` exceeds `limit` (or whose `Content-Length` is invalid) are * rejected early via `endResponse`. Bodies are also counted while streaming (via * `TransformStream`), including when `Content-Length` is missing, wrong, or overridden — so a * short `Content-Length` cannot bypass the limit. `Transfer-Encoding` and `Content-Encoding` * cases are covered by the same byte counter. * * When using `useContentEncoding`, register it **before** this plugin so the byte counter sees * decoded bytes. `onRequest` hooks run in `plugins` array order; the reverse order would count * compressed size and then allow decompression past `limit`. * * To disable limiting, omit this plugin from the adapter. */ function useLimitRequestBodySize(limit, options) { if (!Number.isFinite(limit) || limit < 0) { throw new TypeError(`useLimitRequestBodySize: expected a finite non-negative limit, got ${String(limit)}`); } const responseFromError = options?.responseFromError ?? defaultResponseFromError; return { onRequest({ request, setRequest, fetchAPI, endResponse }) { const contentLength = request.headers.get('content-length'); if (contentLength != null) { if (!CONTENT_LENGTH_RE.test(contentLength)) { endResponse(responseFromError(new InvalidContentLengthError(), fetchAPI)); return; } if (Number(contentLength) > limit) { endResponse(responseFromError(new RequestBodyTooLargeError(), fetchAPI)); return; } } if (!request.body) { return; } let bytesRead = 0; const limitedBody = request.body.pipeThrough(new fetchAPI.TransformStream({ transform(chunk, controller) { bytesRead += chunk.byteLength; if (bytesRead > limit) { controller.error(new RequestBodyTooLargeError()); return; } controller.enqueue(chunk); }, })); setRequest(new fetchAPI.Request(request.url, { body: limitedBody, cache: request.cache, credentials: request.credentials, headers: request.headers, integrity: request.integrity, keepalive: request.keepalive, method: request.method, mode: request.mode, redirect: request.redirect, referrer: request.referrer, referrerPolicy: request.referrerPolicy, signal: request.signal, // @ts-expect-error duplex is required for streamed bodies in some runtimes duplex: 'half', })); }, }; }