mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 20:09:04 +00:00
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.
125 lines
4.2 KiB
TypeScript
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();
|
|
});
|