Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
2b51cd9
wip
cirospaciari May 6, 2025
1ee110a
better
cirospaciari May 6, 2025
2d5fde5
cleanuop
cirospaciari May 6, 2025
65544a1
opsie
cirospaciari May 6, 2025
2e105c7
more
cirospaciari May 6, 2025
0989896
keep same behavior
cirospaciari May 6, 2025
eab1d58
comments + trim space
cirospaciari May 6, 2025
4da3401
less spaces
cirospaciari May 6, 2025
38e7a3a
less spaces
cirospaciari May 6, 2025
1265e93
cleanner
cirospaciari May 6, 2025
3f11d04
more comments
cirospaciari May 6, 2025
d224ac8
cleanup getHeaders
cirospaciari May 6, 2025
de869ca
init ancientHttp
cirospaciari May 6, 2025
860e0ef
init ancientHttp
cirospaciari May 6, 2025
f54300b
more
cirospaciari May 7, 2025
3c53a70
validate chunk encoding
cirospaciari May 7, 2025
18a92cc
more
cirospaciari May 7, 2025
fce0113
opsie
cirospaciari May 7, 2025
0c7e305
more
cirospaciari May 8, 2025
f7322ca
more
cirospaciari May 8, 2025
748b586
more
cirospaciari May 8, 2025
712d078
we actually wanna to error in fragmented requests
cirospaciari May 8, 2025
f6d1f7f
nope
cirospaciari May 8, 2025
883de94
more
cirospaciari May 9, 2025
169ca85
more
cirospaciari May 9, 2025
616ec02
Merge branch 'main' into ciro/http-parser-refactor
cirospaciari May 9, 2025
f20fb28
trailer
cirospaciari May 9, 2025
da451fc
more
cirospaciari May 9, 2025
b16c9ff
more
cirospaciari May 9, 2025
cbbd400
more
cirospaciari May 9, 2025
6825539
Merge branch 'ciro/http-parser-refactor' into ciro/http-parser-errors
cirospaciari May 9, 2025
b68e640
fix smuggling
cirospaciari May 9, 2025
9b97702
Merge branch 'main' into ciro/http-parser-refactor
cirospaciari May 9, 2025
924f08e
Merge branch 'ciro/http-parser-refactor' into ciro/http-parser-errors
cirospaciari May 9, 2025
f568ad3
fix capture
cirospaciari May 9, 2025
96f9175
more
cirospaciari May 9, 2025
82ea308
not yet
cirospaciari May 9, 2025
a2ce153
Merge branch 'main' into ciro/http-parser-errors
cirospaciari May 9, 2025
742d3e3
`bun run prettier`
cirospaciari May 9, 2025
6f28f15
ok
cirospaciari May 9, 2025
f09d7bc
close
cirospaciari May 9, 2025
eeee03a
opsie
cirospaciari May 9, 2025
615facc
opsie
cirospaciari May 9, 2025
0d25a57
more
cirospaciari May 9, 2025
e5d7387
more
cirospaciari May 9, 2025
50a8e36
`bun run prettier`
cirospaciari May 9, 2025
ae691ec
asan fail
cirospaciari May 9, 2025
c3326d5
again
cirospaciari May 9, 2025
4621d79
one more
cirospaciari May 9, 2025
606954b
revert
cirospaciari May 9, 2025
6dd4dfd
one more hey
cirospaciari May 9, 2025
ac914fd
ok
cirospaciari May 9, 2025
93dbd3e
more tests
cirospaciari May 9, 2025
e1651c4
comment
cirospaciari May 9, 2025
33b2669
comments
cirospaciari May 9, 2025
f5b7a5c
throw
cirospaciari May 9, 2025
745d9ed
Update packages/bun-uws/src/ChunkedEncoding.h
cirospaciari May 9, 2025
b17d954
Update packages/bun-uws/src/ChunkedEncoding.h
cirospaciari May 9, 2025
8867984
review
cirospaciari May 9, 2025
a49b51f
remove expectations
cirospaciari May 9, 2025
7a378fc
improve
cirospaciari May 9, 2025
49c8bb0
cleanup
cirospaciari May 9, 2025
8a66af2
await
cirospaciari May 9, 2025
927534b
more test fixes
cirospaciari May 9, 2025
d9268fe
remove test
cirospaciari May 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions packages/bun-uws/src/ChunkedEncoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@

namespace uWS {

constexpr uint64_t STATE_HAS_SIZE = 1ull << (sizeof(uint64_t) * 8 - 1);//0x80000000;
constexpr uint64_t STATE_IS_CHUNKED = 1ull << (sizeof(uint64_t) * 8 - 2);//0x40000000;
constexpr uint64_t STATE_IS_CHUNKED_EXTENSION = 1ull << (sizeof(uint64_t) * 8 - 3);//0x20000000;
constexpr uint64_t STATE_SIZE_MASK = ~(STATE_HAS_SIZE | STATE_IS_CHUNKED | STATE_IS_CHUNKED_EXTENSION);//0x3FFFFFFF;
constexpr uint64_t STATE_IS_ERROR = ~0ull;//0xFFFFFFFF;
constexpr uint64_t STATE_SIZE_OVERFLOW = 0x0Full << (sizeof(uint64_t) * 8 - 8);//0x0F000000;
constexpr uint64_t STATE_HAS_SIZE = 1ull << (sizeof(uint64_t) * 8 - 1);//0x8000000000000000;
constexpr uint64_t STATE_IS_CHUNKED = 1ull << (sizeof(uint64_t) * 8 - 2);//0x4000000000000000;
constexpr uint64_t STATE_IS_CHUNKED_EXTENSION = 1ull << (sizeof(uint64_t) * 8 - 3);//0x2000000000000000;
constexpr uint64_t STATE_SIZE_MASK = ~(STATE_HAS_SIZE | STATE_IS_CHUNKED | STATE_IS_CHUNKED_EXTENSION);//0x1FFFFFFFFFFFFFFF;
constexpr uint64_t STATE_IS_ERROR = ~0ull;//0xFFFFFFFFFFFFFFFF;
constexpr uint64_t STATE_SIZE_OVERFLOW = 0x0Full << (sizeof(uint64_t) * 8 - 8);//0x0F00000000000000;

inline unsigned int chunkSize(uint64_t state) {
return state & STATE_SIZE_MASK;
Expand Down
46 changes: 11 additions & 35 deletions src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7786,15 +7786,8 @@ pub fn Server__setIdleTimeout_(server: JSC.JSValue, seconds: JSC.JSValue, global
return globalThis.throw("Failed to set timeout: The 'this' value is not a Server.", .{});
}
}
pub export fn Server__setOnClientError(server: JSC.JSValue, callback: JSC.JSValue, globalThis: *JSC.JSGlobalObject) void {
Server__setOnClientError_(server, callback, globalThis) catch |err| switch (err) {
error.JSError => {},
error.OutOfMemory => {
_ = globalThis.throwOutOfMemoryValue();
},
};
}
pub fn Server__setOnClientError_(server: JSC.JSValue, callback: JSC.JSValue, globalThis: *JSC.JSGlobalObject) bun.JSError!void {

pub fn Server__setOnClientError_(globalThis: *JSC.JSGlobalObject, server: JSC.JSValue, callback: JSC.JSValue) bun.JSError!JSC.JSValue {
if (!server.isObject()) {
return globalThis.throw("Failed to set clientError: The 'this' value is not a Server.", .{});
}
Expand Down Expand Up @@ -7830,17 +7823,10 @@ pub fn Server__setOnClientError_(server: JSC.JSValue, callback: JSC.JSValue, glo
} else {
bun.debugAssert(false);
}
}
pub export fn Server__setAppFlags(server: JSC.JSValue, require_host_header: bool, use_strict_method_validation: bool, globalThis: *JSC.JSGlobalObject) void {
Server__setAppFlags_(server, require_host_header, use_strict_method_validation, globalThis) catch |err| switch (err) {
error.JSError => {},
error.OutOfMemory => {
_ = globalThis.throwOutOfMemoryValue();
},
};
return .undefined;
}

pub fn Server__setAppFlags_(server: JSC.JSValue, require_host_header: bool, use_strict_method_validation: bool, globalThis: *JSC.JSGlobalObject) bun.JSError!void {
pub fn Server__setAppFlags_(globalThis: *JSC.JSGlobalObject, server: JSC.JSValue, require_host_header: bool, use_strict_method_validation: bool) bun.JSError!JSC.JSValue {
if (!server.isObject()) {
return globalThis.throw("Failed to set requireHostHeader: The 'this' value is not a Server.", .{});
}
Expand All @@ -7856,24 +7842,13 @@ pub fn Server__setAppFlags_(server: JSC.JSValue, require_host_header: bool, use_
} else {
return globalThis.throw("Failed to set timeout: The 'this' value is not a Server.", .{});
}
return .undefined;
}

pub export fn Server__setMaxHTTPHeaderSize(server: JSC.JSValue, max_header_size: u64, globalThis: *JSC.JSGlobalObject) void {
Server__setMaxHTTPHeaderSize_(server, max_header_size, globalThis) catch |err| switch (err) {
error.JSError => {},
error.OutOfMemory => {
_ = globalThis.throwOutOfMemoryValue();
},
};
}

pub fn Server__setMaxHTTPHeaderSize_(server: JSC.JSValue, max_header_size: u64, globalThis: *JSC.JSGlobalObject) bun.JSError!void {
pub fn Server__setMaxHTTPHeaderSize_(globalThis: *JSC.JSGlobalObject, server: JSC.JSValue, max_header_size: u64) bun.JSError!JSC.JSValue {
if (!server.isObject()) {
return globalThis.throw("Failed to set maxHeaderSize: The 'this' value is not a Server.", .{});
}
if (!server.isObject()) {
return globalThis.throw("Failed to set requireHostHeader: The 'this' value is not a Server.", .{});
}

if (server.as(HTTPServer)) |this| {
this.setMaxHTTPHeaderSize(max_header_size);
Expand All @@ -7884,15 +7859,16 @@ pub fn Server__setMaxHTTPHeaderSize_(server: JSC.JSValue, max_header_size: u64,
} else if (server.as(DebugHTTPSServer)) |this| {
this.setMaxHTTPHeaderSize(max_header_size);
} else {
return globalThis.throw("Failed to set timeout: The 'this' value is not a Server.", .{});
return globalThis.throw("Failed to set maxHeaderSize: The 'this' value is not a Server.", .{});
}
return .undefined;
}
comptime {
_ = Server__setIdleTimeout;
_ = Server__setAppFlags;
_ = NodeHTTPResponse.create;
_ = Server__setOnClientError;
_ = Server__setMaxHTTPHeaderSize;
@export(&JSC.host_fn.wrap4(Server__setAppFlags_), .{ .name = "Server__setAppFlags" });
@export(&JSC.host_fn.wrap3(Server__setOnClientError_), .{ .name = "Server__setOnClientError" });
@export(&JSC.host_fn.wrap3(Server__setMaxHTTPHeaderSize_), .{ .name = "Server__setMaxHTTPHeaderSize" });
}

extern fn NodeHTTPServer__onRequest_http(
Expand Down
15 changes: 9 additions & 6 deletions src/bun.js/bindings/NodeHTTP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -533,9 +533,9 @@ extern "C" void Request__setInternalEventCallback(void*, EncodedJSValue, JSC::JS
extern "C" void Request__setTimeout(void*, EncodedJSValue, JSC::JSGlobalObject*);
extern "C" bool NodeHTTPResponse__setTimeout(void*, EncodedJSValue, JSC::JSGlobalObject*);
extern "C" void Server__setIdleTimeout(EncodedJSValue, EncodedJSValue, JSC::JSGlobalObject*);
extern "C" void Server__setAppFlags(EncodedJSValue, bool, bool, JSC::JSGlobalObject*);
extern "C" void Server__setOnClientError(EncodedJSValue, EncodedJSValue, JSC::JSGlobalObject*);
extern "C" void Server__setMaxHTTPHeaderSize(EncodedJSValue, uint64_t, JSC::JSGlobalObject*);
extern "C" EncodedJSValue Server__setAppFlags(JSC::JSGlobalObject*, EncodedJSValue, bool require_host_header, bool use_strict_method_validation);
extern "C" EncodedJSValue Server__setOnClientError(JSC::JSGlobalObject*, EncodedJSValue, EncodedJSValue);
extern "C" EncodedJSValue Server__setMaxHTTPHeaderSize(JSC::JSGlobalObject*, EncodedJSValue, uint64_t);

static EncodedJSValue assignHeadersFromFetchHeaders(FetchHeaders& impl, JSObject* prototype, JSObject* objectValue, JSC::InternalFieldTuple* tuple, JSC::JSGlobalObject* globalObject, JSC::VM& vm)
{
Expand Down Expand Up @@ -1326,13 +1326,16 @@ JSC_DEFINE_HOST_FUNCTION(jsHTTPSetCustomOptions, (JSGlobalObject * globalObject,
JSValue maxHeaderSize = callFrame->uncheckedArgument(3);
JSValue callback = callFrame->uncheckedArgument(4);

Server__setAppFlags(JSValue::encode(serverValue), requireHostHeader.toBoolean(globalObject), useStrictMethodValidation.toBoolean(globalObject), globalObject);
double maxHeaderSizeNumber = maxHeaderSize.toNumber(globalObject);
RETURN_IF_EXCEPTION(scope, {});

Server__setMaxHTTPHeaderSize(JSValue::encode(serverValue), maxHeaderSize.toNumber(globalObject), globalObject);
Server__setAppFlags(globalObject, JSValue::encode(serverValue), requireHostHeader.toBoolean(globalObject), useStrictMethodValidation.toBoolean(globalObject));
RETURN_IF_EXCEPTION(scope, {});

Server__setOnClientError(JSValue::encode(serverValue), JSValue::encode(callback), globalObject);
Server__setMaxHTTPHeaderSize(globalObject, JSValue::encode(serverValue), maxHeaderSizeNumber);
RETURN_IF_EXCEPTION(scope, {});

Server__setOnClientError(globalObject, JSValue::encode(serverValue), JSValue::encode(callback));
RETURN_IF_EXCEPTION(scope, {});

return JSValue::encode(jsUndefined());
Expand Down
1 change: 0 additions & 1 deletion test/expectations.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ test/js/bun/spawn/spawn-maxbuf.test.ts [ FLAKY ]
[ ASAN ] test/cli/inspect/inspect.test.ts [ TIMEOUT ]
[ ASAN ] test/js/node/test/parallel/test-gc-http-client-connaborted.js [ TIMEOUT ]
[ ASAN ] test/cli/inspect/BunFrontendDevServer.test.ts [ TIMEOUT ]
[ ASAN ] test/js/node/test/parallel/test-http-server-keepalive-req-gc.js [ TIMOUT ]

# Tests failed due to memory leaks
[ ASAN ] test/js/node/url/pathToFileURL.test.ts [ LEAK ] # pathToFileURL doesn't leak memory
Expand Down
61 changes: 44 additions & 17 deletions test/js/node/http/node-http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3119,13 +3119,23 @@ describe("HTTP Server Security Tests - Advanced", () => {
test("rejects requests with CR in header field value", async () => {
const mockHandler = createMockHandler();
server.on("request", mockHandler);
const { promise, resolve, reject } = Promise.withResolvers();
server.on("clientError", (err: any) => {
try {
expect(err.code).toBe("HPE_INTERNAL");
resolve();
} catch (err) {
reject(err);
}
});

const msg = ["GET / HTTP/1.1", "Host: localhost", "X-Custom: bad\rvalue", "", ""].join("\r\n");

const response = await sendRequest(msg);

expect(response).toInclude("400 Bad Request");
expect(mockHandler).not.toHaveBeenCalled();
await promise;
});
});

Expand Down Expand Up @@ -3189,7 +3199,15 @@ describe("HTTP Server Security Tests - Advanced", () => {
test("rejects requests with both Content-Length and Transfer-Encoding", async () => {
const mockHandler = createMockHandler();
server.on("request", mockHandler);

const { promise, resolve, reject } = Promise.withResolvers();
server.on("clientError", (err: any) => {
try {
expect(err.code).toBe("HPE_INVALID_TRANSFER_ENCODING");
resolve();
} catch (err) {
reject(err);
}
});
const msg = [
"POST / HTTP/1.1",
"Host: localhost",
Expand All @@ -3203,19 +3221,23 @@ describe("HTTP Server Security Tests - Advanced", () => {
"",
].join("\r\n");

const response = await sendRequest(msg);

// Note: Node.js HTTP parser behavior varies by version
// Some versions might process this rather than reject
if (response.includes("400 Bad Request")) {
expect(mockHandler).not.toHaveBeenCalled();
}
await sendRequest(msg);
await promise;
expect(mockHandler).not.toHaveBeenCalled();
});

test("rejects requests with obfuscated Transfer-Encoding header", async () => {
const mockHandler = createMockHandler();
server.on("request", mockHandler);

const { promise, resolve, reject } = Promise.withResolvers();
server.on("clientError", (err: any) => {
try {
expect(err.code).toBe("HPE_INVALID_HEADER_TOKEN");
resolve();
} catch (err) {
reject(err);
}
});
const msg = [
"POST / HTTP/1.1",
"Host: localhost",
Expand All @@ -3229,20 +3251,25 @@ describe("HTTP Server Security Tests - Advanced", () => {
"",
].join("\r\n");

const response = await sendRequest(msg);

// Node's behavior might vary, so check for either case
if (response.includes("400 Bad Request")) {
expect(mockHandler).not.toHaveBeenCalled();
}
await sendRequest(msg);
await promise;
expect(mockHandler).not.toHaveBeenCalled();
});
});

describe("HTTP Protocol Violations", () => {
test("rejects requests with invalid HTTP version", async () => {
const mockHandler = createMockHandler();
server.on("request", mockHandler);

const { promise, resolve, reject } = Promise.withResolvers();
server.on("clientError", (err: any) => {
try {
expect(err.code).toBe("HPE_INTERNAL");
resolve();
} catch (err) {
reject(err);
}
});
const msg = [
"GET / HTTP/9.9", // Invalid HTTP version
"Host: localhost",
Expand All @@ -3251,8 +3278,8 @@ describe("HTTP Server Security Tests - Advanced", () => {
].join("\r\n");

const response = await sendRequest(msg);

expect(response).toInclude("505 HTTP Version Not Supported");
await promise;
expect(mockHandler).not.toHaveBeenCalled();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ const { connect } = require('net');
// Refs: https://github.com/nodejs/node/issues/9668

let client;

const server = createServer(common.mustCall((req, res) => {

function serverStopped() {
return server.listening == false;
}
onGC(req, { ongc: common.mustCall(() => { server.closeAllConnections(); }) });
req.resume();
req.on('end', common.mustCall(() => {
setImmediate(async () => {
client.end();
await globalThis.gc({ type: 'major', execution: 'async' });
await globalThis.gc({ type: 'major', execution: 'async' });
await common.gcUntil(serverStopped);
});
}));
res.end('hello world');
Expand Down