Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/server/request-body.test.ts
T

88 lines
5.2 KiB
TypeScript

import { Readable } from "node:stream";
import { describe, expect, test } from "vitest";
import {
createLimitedRequestBody,
DEFAULT_WEB_REQUEST_BODY_BYTES,
EMAIL_WEBHOOK_REQUEST_BODY_BYTES,
MAX_WEB_REQUEST_BODY_BYTES,
OAUTH_REGISTRATION_REQUEST_BODY_BYTES,
parseRequestContentLength,
PORTAL_UPLOAD_REQUEST_BODY_BYTES,
PORTAL_UPLOAD_MULTIPART_OVERHEAD_BYTES,
PUBLIC_AUTH_FORM_REQUEST_BODY_BYTES,
PUBLIC_BOOKING_REQUEST_BODY_BYTES,
PUBLIC_FORM_REQUEST_BODY_BYTES,
RequestBodyTooLargeError,
resolveWebRequestBodyLimit,
} from "./request-body";
describe("web request body limit", () => {
test("parses only safe decimal content lengths", () => {
expect(parseRequestContentLength("42")).toBe(42);
expect(parseRequestContentLength(" 42 ")).toBe(42);
expect(parseRequestContentLength("-1")).toBeNull();
expect(parseRequestContentLength("invalid")).toBeNull();
});
test("streams a body within the limit without buffering it as one payload", async () => {
const input = Readable.from([Buffer.from("ab"), Buffer.from("cd")]);
const output = createLimitedRequestBody(input as never, 4);
const chunks: Buffer[] = [];
for await (const chunk of output) chunks.push(Buffer.from(chunk));
expect(Buffer.concat(chunks).toString("utf8")).toBe("abcd");
});
test("fails as soon as a chunk crosses the configured limit", async () => {
const input = Readable.from([Buffer.from("abc"), Buffer.from("de")]);
const output = createLimitedRequestBody(input as never, 4);
await expect(async () => {
for await (const chunk of output) {
// Consume the stream so the limiter observes every chunk.
void chunk;
}
}).rejects.toBeInstanceOf(RequestBodyTooLargeError);
});
test("uses a small default and explicit upload-route exceptions", () => {
expect(resolveWebRequestBodyLimit("/acme/settings/members")).toBe(DEFAULT_WEB_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/acme/files/upload/up_1/chunk/0")).toBe(5 * 1024 * 1024);
expect(resolveWebRequestBodyLimit("/acme/projects/project_1/upload", "portal")).toBe(PORTAL_UPLOAD_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/projects/project_1/upload", "portal")).toBe(PORTAL_UPLOAD_REQUEST_BODY_BYTES);
expect(PORTAL_UPLOAD_REQUEST_BODY_BYTES).toBe(101 * 1024 * 1024);
expect(PORTAL_UPLOAD_MULTIPART_OVERHEAD_BYTES).toBe(1024 * 1024);
expect(resolveWebRequestBodyLimit("/acme/projects/project_1/upload", "core")).toBe(DEFAULT_WEB_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/acme/settings/profile")).toBe(8 * 1024 * 1024);
expect(resolveWebRequestBodyLimit("/settings/profile")).toBe(8 * 1024 * 1024);
expect(resolveWebRequestBodyLimit("/acme/hr/my-documents")).toBe(26 * 1024 * 1024);
expect(resolveWebRequestBodyLimit("/acme/emails/new")).toBe(MAX_WEB_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/api/embed/forms/form_1/submit")).toBe(PUBLIC_FORM_REQUEST_BODY_BYTES);
expect(PUBLIC_FORM_REQUEST_BODY_BYTES).toBe(30 * 1024 * 1024);
expect(resolveWebRequestBodyLimit("/oauth/register")).toBe(OAUTH_REGISTRATION_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/oauth/token")).toBe(OAUTH_REGISTRATION_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/oauth/revoke")).toBe(OAUTH_REGISTRATION_REQUEST_BODY_BYTES);
expect(OAUTH_REGISTRATION_REQUEST_BODY_BYTES).toBe(64 * 1024);
expect(resolveWebRequestBodyLimit("/webhooks/email/google")).toBe(EMAIL_WEBHOOK_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/webhooks/email/microsoft")).toBe(EMAIL_WEBHOOK_REQUEST_BODY_BYTES);
expect(EMAIL_WEBHOOK_REQUEST_BODY_BYTES).toBe(256 * 1024);
expect(resolveWebRequestBodyLimit("/register")).toBe(PUBLIC_AUTH_FORM_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/signup")).toBe(PUBLIC_AUTH_FORM_REQUEST_BODY_BYTES);
expect(PUBLIC_AUTH_FORM_REQUEST_BODY_BYTES).toBe(64 * 1024);
expect(resolveWebRequestBodyLimit("/b/acme/consulting")).toBe(PUBLIC_BOOKING_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/b/acme/consulting/embed")).toBe(PUBLIC_BOOKING_REQUEST_BODY_BYTES);
expect(PUBLIC_BOOKING_REQUEST_BODY_BYTES).toBe(64 * 1024);
expect(resolveWebRequestBodyLimit("/settings/general")).toBe(MAX_WEB_REQUEST_BODY_BYTES);
});
test("does not grant upload limits to lookalike paths", () => {
expect(resolveWebRequestBodyLimit("/acme/files/upload/up_1/chunk/not-a-number")).toBe(DEFAULT_WEB_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/acme/projects/project_1/upload/extra", "portal")).toBe(DEFAULT_WEB_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/acme/projects/project%2F1/upload", "portal")).toBe(DEFAULT_WEB_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/evil/api/embed/forms/form_1/submit")).toBe(DEFAULT_WEB_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/oauth/register/extra")).toBe(DEFAULT_WEB_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/evil/webhooks/email/microsoft")).toBe(DEFAULT_WEB_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("/evil/b/acme/consulting")).toBe(DEFAULT_WEB_REQUEST_BODY_BYTES);
expect(resolveWebRequestBodyLimit("https://app.example.test/acme/emails/new/extra")).toBe(DEFAULT_WEB_REQUEST_BODY_BYTES);
});
});