Files
bun.sh/test/js/web/fetch/response.test.ts
Claude Bot b3d14c9d34 Fix Response constructor crash when passed a function as init
Fixes a stack overflow crash that occurred when passing a function as
the second argument (ResponseInit) to the Response constructor.

The issue was that the constructor checked `isObject()` which returns
true for functions, and then proceeded to call `Init.init()` which
would attempt to read properties from the function. This could cause
infinite recursion in certain edge cases.

The fix adds an explicit check to reject callable objects before
processing them as ResponseInit, throwing a proper TypeError instead
of causing a stack overflow.
2025-10-22 07:56:42 +00:00

125 lines
4.2 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { normalizeBunSnapshot } from "harness";
test("zero args returns an otherwise empty 200 response", () => {
const response = new Response();
expect(response.status).toBe(200);
expect(response.statusText).toBe("");
});
test("calling cancel() on response body doesn't throw", () => {
expect(() => new Response("").body?.cancel()).not.toThrow();
});
test("undefined args don't throw", () => {
const response = new Response("", {
status: undefined,
statusText: undefined,
headers: undefined,
});
expect(response.status).toBe(200);
expect(response.statusText).toBe("");
});
test("1-arg form returns a 200 response", () => {
const response = new Response("body text");
expect(response.status).toBe(200);
expect(response.statusText).toBe("");
});
describe("2-arg form", () => {
test("can fill in status/statusText, and it works", () => {
const response = new Response("body text", {
status: 202,
statusText: "Accepted.",
});
expect(response.status).toBe(202);
expect(response.statusText).toBe("Accepted.");
});
test('empty object continues to return 200/""', () => {
const response = new Response("body text", {});
expect(response.status).toBe(200);
expect(response.statusText).toBe("");
});
});
test("print size", () => {
expect(normalizeBunSnapshot(Bun.inspect(new Response(Bun.file(import.meta.filename)))), import.meta.dir)
.toMatchInlineSnapshot(`
"Response (4.28 KB) {
ok: true,
url: "",
status: 200,
statusText: "",
headers: Headers {
"content-type": "text/javascript;charset=utf-8",
},
redirected: false,
bodyUsed: false,
FileRef ("<cwd>/test/js/web/fetch/response.test.ts") {
type: "text/javascript;charset=utf-8"
}
}"
`);
});
test("Response.redirect with invalid arguments should not crash", () => {
// This should not crash - issue #18414
// Passing a number as URL and string as init should handle gracefully
expect(() => Response.redirect(400, "a")).not.toThrow();
// Test various invalid argument combinations - should not crash
expect(() => Response.redirect(42, "test")).not.toThrow();
expect(() => Response.redirect(true, "string")).not.toThrow();
expect(() => Response.redirect(null, "init")).not.toThrow();
expect(() => Response.redirect(undefined, "value")).not.toThrow();
});
test("Response.redirect status code validation", () => {
// Valid redirect status codes should work
expect(() => Response.redirect("url", 301)).not.toThrow();
expect(() => Response.redirect("url", 302)).not.toThrow();
expect(() => Response.redirect("url", 303)).not.toThrow();
expect(() => Response.redirect("url", 307)).not.toThrow();
expect(() => Response.redirect("url", 308)).not.toThrow();
// Invalid status codes should throw RangeError
expect(() => Response.redirect("url", 200)).toThrow(RangeError);
expect(() => Response.redirect("url", 400)).toThrow(RangeError);
expect(() => Response.redirect("url", 500)).toThrow(RangeError);
// Status in object should also be validated
expect(() => Response.redirect("url", { status: 307 })).not.toThrow();
expect(() => Response.redirect("url", { status: 400 })).toThrow(RangeError);
// Check that the correct status is set
expect(Response.redirect("url", 301).status).toBe(301);
expect(Response.redirect("url", { status: 308 }).status).toBe(308);
});
test("new Response(123, { statusText: 123 }) does not throw", () => {
// @ts-expect-error
expect(new Response("123", { statusText: 123 }).statusText).toBe("123");
});
test("new Response(123, { method: 456 }) does not throw", () => {
// @ts-expect-error
expect(() => new Response("123", { method: 456 })).not.toThrow();
});
test("Response constructor should reject function as init (regression test for stack overflow)", () => {
function f0() {
const v1 = [1046375616, -1024, -1024, -268435456, 536870887, -77930801, -53473, 24365];
// @ts-expect-error - Testing invalid usage
Response(v1, f0, f0).arrayBuffer(v1, Response);
v1.forEach(f0);
return Response;
}
// This should throw an error instead of causing a stack overflow
expect(() => f0()).toThrow();
});