Compare commits

...

6 Commits

Author SHA1 Message Date
Zack Radisic
c74ab96399 throw more helpful errors 2025-04-01 15:02:21 -07:00
Zack Radisic
bbf8dbe628 Merge branch 'main' into zack/fixes-river 2025-04-01 14:06:25 -07:00
Zack Radisic
aaae7b4d47 document code 2025-03-31 18:21:05 -07:00
Zack Radisic
1485bc71d3 make it not crash by passing thisValue 2025-03-31 18:19:23 -07:00
Zack Radisic
0662640d9f implement headersDistinct 2025-03-28 17:03:58 -07:00
Zack Radisic
4a0adef4fa Fix null pointer dereference 2025-03-28 16:33:12 -07:00
7 changed files with 88 additions and 8 deletions

View File

@@ -169,6 +169,7 @@ export default [
onabort: {
getter: "getOnAbort",
setter: "setOnAbort",
this: true,
},
hasCustomOnData: {
getter: "getHasCustomOnData",

View File

@@ -890,22 +890,22 @@ pub fn getOnWritable(_: *NodeHTTPResponse, thisValue: JSC.JSValue, _: *JSC.JSGlo
return NodeHTTPResponse.onWritableGetCached(thisValue) orelse .undefined;
}
pub fn getOnAbort(this: *NodeHTTPResponse, _: *JSC.JSGlobalObject) JSC.JSValue {
pub fn getOnAbort(this: *NodeHTTPResponse, this_value: JSC.JSValue, _: *JSC.JSGlobalObject) JSC.JSValue {
if (this.flags.socket_closed) {
return .undefined;
}
return NodeHTTPResponse.onAbortedGetCached(this.getThisValue()) orelse .undefined;
return NodeHTTPResponse.onAbortedGetCached(this_value) orelse .undefined;
}
pub fn setOnAbort(this: *NodeHTTPResponse, globalObject: *JSC.JSGlobalObject, value: JSValue) bool {
pub fn setOnAbort(this: *NodeHTTPResponse, this_value: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSValue) bool {
if (this.flags.socket_closed) {
return true;
}
if (this.isDone() or value == .undefined) {
NodeHTTPResponse.onAbortedSetCached(this.getThisValue(), globalObject, .zero);
NodeHTTPResponse.onAbortedSetCached(this_value, globalObject, .zero);
} else {
NodeHTTPResponse.onAbortedSetCached(this.getThisValue(), globalObject, value.withAsyncContextIfNeeded(globalObject));
NodeHTTPResponse.onAbortedSetCached(this_value, globalObject, value.withAsyncContextIfNeeded(globalObject));
}
return true;

View File

@@ -466,9 +466,36 @@ pub const Jest = struct {
vi.put(globalObject, ZigString.static("module"), mockModuleFn);
vi.put(globalObject, ZigString.static("restoreAllMocks"), restoreAllMocks);
vi.put(globalObject, ZigString.static("clearAllMocks"), clearAllMocks);
// not an exhaustive list
const unsupported = &.{
"advanceTimersByTimeAsync",
"waitFor",
"runAllTimersAsync",
};
inline for (unsupported) |name| {
vi.put(
globalObject,
ZigString.static(name),
JSC.NewFunction(
globalObject,
ZigString.static(name),
0,
unsupportedViFnImpl(name),
false,
),
);
}
module.put(globalObject, ZigString.static("vi"), vi);
}
pub fn unsupportedViFnImpl(comptime name: []const u8) fn (*JSGlobalObject, *CallFrame) bun.JSError!JSValue {
return struct {
fn impl(globalThis: *JSGlobalObject, _: *CallFrame) bun.JSError!JSValue {
return globalThis.throwPretty("Bun currently does not support the \"{s}<r>\" Vi utility. Please open an issue if you would like us to prioritize it:\n <cyan>https://github.com/oven-sh/bun/issues<r>\n", .{name});
}
}.impl;
}
extern fn Bun__Jest__testPreloadObject(*JSGlobalObject) JSValue;
extern fn Bun__Jest__testModuleObject(*JSGlobalObject) JSValue;
extern fn JSMock__jsMockFn(*JSGlobalObject, *CallFrame) callconv(JSC.conv) JSValue;

View File

@@ -26,13 +26,16 @@ export enum InvalidThisBehavior {
}
export type Field =
| ({
| /* Field is a getter */ ({
getter: string;
cache?: true | string;
/**
* Allow overriding the value of the property
*/
writable?: boolean;
/**
* Whether or not the `this` JSValue should be passed to the function.
*/
this?: boolean;
} & PropertyAttribute)
| { value: string }

View File

@@ -974,8 +974,8 @@ extern JSC_CALLCONV void ${symbolName(typeName, name)}SetCachedValue(JSC::Encode
extern JSC_CALLCONV JSC::EncodedJSValue ${symbolName(typeName, name)}GetCachedValue(JSC::EncodedJSValue thisValue)
{
auto* thisObject = jsCast<${className(typeName)}*>(JSValue::decode(thisValue));
return JSValue::encode(thisObject->${cacheName}.get());
auto* thisObject = jsCast<${className(typeName)}*>(JSValue::decode(thisValue));
return JSValue::encode(thisObject->${cacheName}.get());
}
`.trim();

View File

@@ -1477,6 +1477,27 @@ const IncomingMessagePrototype = {
emitErrorNextTickIfErrorListenerNT(this, err, cb);
}
},
get headersDistinct() {
if (this[Symbol.for("headersDistinctCache")]) {
return this[Symbol.for("headersDistinctCache")];
}
const headers: { [key: string]: string[] } = {};
const rawHeaders = this.rawHeaders;
for (let i = 0; i < rawHeaders.length; i += 2) {
const key = rawHeaders[i].toLowerCase();
const value = rawHeaders[i + 1];
if (!headers[key]) {
headers[key] = [];
}
headers[key].push(value);
}
this[Symbol.for("headersDistinctCache")] = headers;
return headers;
},
get aborted() {
return this[abortedSymbol];
},

View File

@@ -2286,6 +2286,34 @@ it("should not emit/throw error when writing after socket.end", async () => {
}
});
it("should correctly transform rawHeaders into headersDistinct", async () => {
const server = createServer((req, res) => {
// Send multiple headers with same name to test distinct handling
res.setHeader("Set-Cookie", ["cookie1=value1", "cookie2=value2"]);
res.setHeader("X-Custom", "value1");
res.setHeader("x-custom", "value2"); // Same header, different case
res.end();
});
try {
await once(server.listen(0), "listening");
const url = `http://localhost:${server.address().port}`;
const { promise, resolve } = Promise.withResolvers();
http.get(url, res => {
const { date: _, ...rest } = res.headersDistinct;
expect(rest).toStrictEqual({
"content-length": ["0"],
"set-cookie": ["cookie1=value1", "cookie2=value2"],
"x-custom": ["value2"],
});
resolve();
});
await promise;
it("should handle data if not immediately handled", async () => {
// Create a local server to receive data from
const server = http.createServer();