UNPKG

graphql-yoga

Version:
86 lines (85 loc) 3.45 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.limitRequestBodySize = limitRequestBodySize; exports.useLimitRequestBodySize = useLimitRequestBodySize; const utils_1 = require("@graphql-tools/utils"); function createRequestBodyTooLargeError() { return (0, utils_1.createGraphQLError)(`Request body too large`, { extensions: { http: { status: 413, }, code: 'REQUEST_ENTITY_TOO_LARGE', }, }); } function createInvalidContentLengthError() { return (0, utils_1.createGraphQLError)('Content-Length header is invalid.', { extensions: { http: { status: 400, }, code: 'BAD_REQUEST', }, }); } // 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+$/; // Covers requests with a missing/incorrect Content-Length (e.g. chunked transfer-encoding). function limitRequestBodySize(request, limit, fetchAPI) { const body = request.body; if (!body) { return request; } // Workaround until the next version of whatwg-node automatically normalizes the request body // Once the normalization implemented in whatwg-node, this workaround can be removed. // Since the request body is the native ReadableStream, it conflicts the ponyfill implementation of the TransformStream. // See https://github.com/graphql-hive/graphql-yoga/issues/4583 const TransformStreamCtor = request.body instanceof ReadableStream ? globalThis.TransformStream : fetchAPI.TransformStream; let bytesRead = 0; const limitedBody = body.pipeThrough(new TransformStreamCtor({ transform(chunk, controller) { bytesRead += chunk.byteLength; if (bytesRead > limit) { controller.error(createRequestBodyTooLargeError()); return; } controller.enqueue(chunk); }, })); return new fetchAPI.Request(request.url, { method: request.method, headers: request.headers, signal: request.signal, body: limitedBody, // @ts-expect-error Missing TypeScript types duplex: 'half', }); } // Must run after all request parsers (built-in and user-provided) have registered, so // `requestParser` reflects whichever one was ultimately selected. function useLimitRequestBodySize(limit) { if (limit === false) { return {}; } return { onRequestParse({ request, requestParser, setRequestParser, fetchAPI }) { const contentLength = request.headers.get('content-length'); if (contentLength != null) { if (!CONTENT_LENGTH_RE.test(contentLength)) { throw createInvalidContentLengthError(); } if (Number(contentLength) > limit) { throw createRequestBodyTooLargeError(); } } if (requestParser == null || request.body == null) { return; } const originalParser = requestParser; setRequestParser(req => originalParser(limitRequestBodySize(req, limit, fetchAPI))); }, }; }