diff --git a/src/infra/http-body.test.ts b/src/infra/http-body.test.ts index 81a2575cf8c7..5e1f43b944da 100644 --- a/src/infra/http-body.test.ts +++ b/src/infra/http-body.test.ts @@ -1,6 +1,7 @@ import { EventEmitter } from "node:events"; import type { IncomingMessage } from "node:http"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js"; import { createMockServerResponse } from "../test-utils/mock-http-response.js"; import { installRequestBodyLimitGuard, @@ -8,6 +9,7 @@ import { type RequestBodyLimitErrorCode, readJsonBodyWithLimit, readRequestBodyWithLimit, + testApi, } from "./http-body.js"; type MockIncomingMessage = IncomingMessage & { @@ -254,6 +256,18 @@ describe("http body limits", () => { expect(req["__unhandledDestroyError"]).toBeUndefined(); }); + it("does not overflow oversized request body timeouts into immediate failures", async () => { + expect( + testApi.resolveRequestBodyLimitValues({ + maxBytes: 128, + timeoutMs: Number.MAX_SAFE_INTEGER, + }), + ).toEqual({ + maxBytes: 128, + timeoutMs: MAX_TIMER_TIMEOUT_MS, + }); + }); + it("guard clamps invalid maxBytes to one byte", async () => { const { res } = await expectGuardPayloadTooLarge({ chunks: ["ab"], diff --git a/src/infra/http-body.ts b/src/infra/http-body.ts index d7c14e8c69de..f89eb844a060 100644 --- a/src/infra/http-body.ts +++ b/src/infra/http-body.ts @@ -1,5 +1,6 @@ import type { IncomingMessage, ServerResponse } from "node:http"; import { clearTimeout as clearNodeTimeout, setTimeout as setNodeTimeout } from "node:timers"; +import { resolveTimerTimeoutMs } from "../shared/number-coercion.js"; import { formatErrorMessage } from "./errors.js"; import { parseStrictNonNegativeInteger } from "./parse-finite-number.js"; @@ -101,12 +102,15 @@ function resolveRequestBodyLimitValues(options: { ? Math.max(1, Math.floor(options.maxBytes)) : 1; const timeoutMs = - typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) - ? Math.max(1, Math.floor(options.timeoutMs)) - : DEFAULT_WEBHOOK_BODY_TIMEOUT_MS; + options.timeoutMs === undefined + ? DEFAULT_WEBHOOK_BODY_TIMEOUT_MS + : resolveTimerTimeoutMs(options.timeoutMs, DEFAULT_WEBHOOK_BODY_TIMEOUT_MS); return { maxBytes, timeoutMs }; } +export const testApi = { resolveRequestBodyLimitValues }; +export { testApi as __test__ }; + function advanceRequestBodyChunk( chunk: Buffer | string, totalBytes: number,