Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
3 changes: 2 additions & 1 deletion src/codegen/bindgenv2/internal/dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ export function dictionary(
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto ctx = Bun::Bindgen::LiteralConversionContext { ${toASCIILiteral(userFacingName)} };
auto* object = value.getObject();
if (!object) [[unlikely]] {
// An array has none of the named members, so it is never a valid options bag.
if (!object || JSC::isJSArray(object)) [[unlikely]] {
Comment thread
robobun marked this conversation as resolved.
Resolved
ctx.throwNotObject(globalObject, throwScope);
return {};
}
Expand Down
48 changes: 48 additions & 0 deletions test/js/bun/net/socket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,54 @@ it("should throw on empty unix path from truthy non-string value", () => {
expect(() => Bun.connect({ unix: [] as any, socket })).toThrow("SocketOptions.unix must be a string");
});

it("should throw on a tls array instead of starting a plaintext socket", async () => {
const socket = { data() {}, open() {}, close() {} };
// An array has none of the TLSOptions fields. Without the check it parses as
// an empty config and the listener comes up as plain TCP.
const message = "TLSOptions must be an object";
for (const tlsOption of [[tls], [tls, { serverName: "a.test", ...tls }], []]) {
expect(() => Bun.listen({ hostname: "127.0.0.1", port: 0, tls: tlsOption as any, socket })).toThrow(message);
expect(() => Bun.connect({ hostname: "127.0.0.1", port: 1, tls: tlsOption as any, socket })).toThrow(message);
}
Comment thread
robobun marked this conversation as resolved.
Resolved

// The same entry as a plain object still produces a TLS listener.
const { promise, resolve, reject } = Promise.withResolvers<string>();
using server = Bun.listen({
hostname: "127.0.0.1",
port: 0,
tls,
socket: {
data(s) {
s.write("hello");
s.end();
},
open() {},
close() {},
error: reject,
Comment thread
robobun marked this conversation as resolved.
Resolved
},
});
let received = "";
await Bun.connect({
hostname: "127.0.0.1",
port: server.port,
tls: { rejectUnauthorized: false },
socket: {
open(s) {
s.write("ping");
},
data(_s, chunk) {
received += chunk.toString();
},
close() {
resolve(received);
},
Comment thread
claude[bot] marked this conversation as resolved.
Resolved
error: reject,
connectError: reject,
},
});
expect(await promise).toBe("hello");
});

it("reading .listener on a closed client socket does not use-after-free handlers", async () => {
// Client-mode Handlers is heap-allocated per-connect and freed in
// markInactive once the socket closes. `socket.listener` read
Expand Down
Loading