diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 8d01ab6161..2d377c67c8 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -3305,21 +3305,22 @@ pub fn serve( const exception = &exception_; var args = JSC.Node.ArgumentsSlice.init(globalObject.bunVM(), arguments); - var config_ = JSC.API.ServerConfig.fromJS(globalObject.ptr(), &args, exception); + var config: JSC.API.ServerConfig = .{}; + JSC.API.ServerConfig.fromJS(globalObject, &config, &args, exception); if (exception[0] != null) { - config_.deinit(); + config.deinit(); globalObject.throwValue(exception_[0].?.value()); - return .undefined; + return .zero; } if (globalObject.hasException()) { - config_.deinit(); + config.deinit(); return .zero; } - break :brk config_; + break :brk config; }; var exception_value: *JSC.JSValue = undefined; diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 59c16b6fb5..a5350abd3a 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -1174,11 +1174,16 @@ pub const ServerConfig = struct { } }; - pub fn fromJS(global: *JSC.JSGlobalObject, arguments: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) ServerConfig { + pub fn fromJS( + global: *JSC.JSGlobalObject, + args: *ServerConfig, + arguments: *JSC.Node.ArgumentsSlice, + exception: JSC.C.ExceptionRef, + ) void { const vm = arguments.vm; const env = vm.bundler.env; - var args = ServerConfig{ + args.* = .{ .address = .{ .tcp = .{ .port = 3000, @@ -1236,13 +1241,13 @@ pub const ServerConfig = struct { if (arguments.next()) |arg| { if (!arg.isObject()) { JSC.throwInvalidArguments("Bun.serve expects an object", .{}, global, exception); - return args; + return; } if (arg.get(global, "static")) |static| { if (!static.isObject()) { JSC.throwInvalidArguments("Bun.serve expects 'static' to be an object shaped like { [pathname: string]: Response }", .{}, global, exception); - return args; + return; } var iter = JSC.JSPropertyIterator(.{ @@ -1259,13 +1264,13 @@ pub const ServerConfig = struct { if (path.len == 0 or path[0] != '/') { bun.default_allocator.free(path); JSC.throwInvalidArguments("Invalid static route \"{s}\". path must start with '/'", .{path}, global, exception); - return args; + return; } if (!is_ascii) { bun.default_allocator.free(path); JSC.throwInvalidArguments("Invalid static route \"{s}\". Please encode all non-ASCII characters in the path.", .{path}, global, exception); - return args; + return; } if (StaticRoute.fromJS(global, value)) |route| { @@ -1275,28 +1280,28 @@ pub const ServerConfig = struct { }) catch bun.outOfMemory(); } else if (global.hasException()) { bun.default_allocator.free(path); - return args; + return; } else { Output.panic("Internal error: expected exception or static route", .{}); } } } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.get(global, "idleTimeout")) |value| { if (!value.isUndefinedOrNull()) { if (!value.isAnyInt()) { JSC.throwInvalidArguments("Bun.serve expects idleTimeout to be an integer", .{}, global, exception); - return args; + return; } args.has_idleTimeout = true; const idleTimeout: u64 = @intCast(@max(value.toInt64(), 0)); if (idleTimeout > 255) { JSC.throwInvalidArguments("Bun.serve expects idleTimeout to be 255 or less", .{}, global, exception); - return args; + return; } args.idleTimeout = @truncate(idleTimeout); @@ -1309,7 +1314,7 @@ pub const ServerConfig = struct { if (args.ssl_config) |*conf| { conf.deinit(); } - return args; + return; } if (WebSocketServer.onCreate(global, websocket_object)) |wss| { @@ -1318,10 +1323,10 @@ pub const ServerConfig = struct { if (args.ssl_config) |*conf| { conf.deinit(); } - return args; + return; } } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.getTruthy(global, "port")) |port_| { args.address.tcp.port = @as( @@ -1333,7 +1338,7 @@ pub const ServerConfig = struct { ); port = args.address.tcp.port; } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.getTruthy(global, "baseURI")) |baseURI| { var sliced = baseURI.toSlice(global, bun.default_allocator); @@ -1343,7 +1348,7 @@ pub const ServerConfig = struct { args.base_uri = bun.default_allocator.dupe(u8, sliced.slice()) catch unreachable; } } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.getTruthy(global, "hostname") orelse arg.getTruthy(global, "host")) |host| { const host_str = host.toSlice( @@ -1357,7 +1362,7 @@ pub const ServerConfig = struct { has_hostname = true; } } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.getTruthy(global, "unix")) |unix| { const unix_str = unix.toSlice( @@ -1368,13 +1373,13 @@ pub const ServerConfig = struct { if (unix_str.len > 0) { if (has_hostname) { JSC.throwInvalidArguments("Cannot specify both hostname and unix", .{}, global, exception); - return args; + return; } args.address = .{ .unix = bun.default_allocator.dupeZ(u8, unix_str.slice()) catch unreachable }; } } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.get(global, "id")) |id| { if (id.isUndefinedOrNull()) { @@ -1392,59 +1397,59 @@ pub const ServerConfig = struct { } } } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.get(global, "development")) |dev| { args.development = dev.coerce(bool, global); args.reuse_port = !args.development; } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.get(global, "reusePort")) |dev| { args.reuse_port = dev.coerce(bool, global); } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.get(global, "inspector")) |inspector| { args.inspector = inspector.coerce(bool, global); if (args.inspector and !args.development) { JSC.throwInvalidArguments("Cannot enable inspector in production. Please set development: true in Bun.serve()", .{}, global, exception); - return args; + return; } } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.getTruthy(global, "maxRequestBodySize")) |max_request_body_size| { if (max_request_body_size.isNumber()) { args.max_request_body_size = @as(u64, @intCast(@max(0, max_request_body_size.toInt64()))); } } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.getTruthyComptime(global, "error")) |onError| { if (!onError.isCallable(global.vm())) { JSC.throwInvalidArguments("Expected error to be a function", .{}, global, exception); - return args; + return; } const onErrorSnapshot = onError.withAsyncContextIfNeeded(global); args.onError = onErrorSnapshot; onErrorSnapshot.protect(); } - if (global.hasException()) return args; + if (global.hasException()) return; if (arg.getTruthy(global, "fetch")) |onRequest_| { if (!onRequest_.isCallable(global.vm())) { JSC.throwInvalidArguments("Expected fetch() to be a function", .{}, global, exception); - return args; + return; } const onRequest = onRequest_.withAsyncContextIfNeeded(global); JSC.C.JSValueProtect(global, onRequest.asObjectRef()); args.onRequest = onRequest; } else { - if (global.hasException()) return args; + if (global.hasException()) return; JSC.throwInvalidArguments("Expected fetch() to be a function", .{}, global, exception); - return args; + return; } if (arg.getTruthy(global, "tls")) |tls| { @@ -1452,7 +1457,7 @@ pub const ServerConfig = struct { var value_iter = tls.arrayIterator(global); if (value_iter.len == 1) { JSC.throwInvalidArguments("tls option expects at least 1 tls object", .{}, global, exception); - return args; + return; } while (value_iter.next()) |item| { if (SSLConfig.inJS(vm, global, item, exception)) |ssl_config| { @@ -1463,7 +1468,7 @@ pub const ServerConfig = struct { var config = ssl_config; defer config.deinit(); JSC.throwInvalidArguments("SNI tls object must have a serverName", .{}, global, exception); - return args; + return; } if (args.sni == null) { args.sni = bun.BabyList(SSLConfig).initCapacity(bun.default_allocator, value_iter.len - 1) catch bun.outOfMemory(); @@ -1474,11 +1479,11 @@ pub const ServerConfig = struct { } if (exception.* != null) { - return args; + return; } if (global.hasException()) { - return args; + return; } } } else { @@ -1487,15 +1492,15 @@ pub const ServerConfig = struct { } if (exception.* != null) { - return args; + return; } if (global.hasException()) { - return args; + return; } } } - if (global.hasException()) return args; + if (global.hasException()) return; // @compatibility Bun v0.x - v0.2.1 // this used to be top-level, now it's "tls" object @@ -1505,16 +1510,16 @@ pub const ServerConfig = struct { } if (exception.* != null) { - return args; + return; } if (global.hasException()) { - return args; + return; } } } else { JSC.throwInvalidArguments("Bun.serve expects an object", .{}, global, exception); - return args; + return; } if (args.base_uri.len > 0) { @@ -1523,14 +1528,14 @@ pub const ServerConfig = struct { JSC.throwInvalidArguments("baseURI must have a hostname", .{}, global, exception); bun.default_allocator.free(@constCast(args.base_uri)); args.base_uri = ""; - return args; + return; } if (!strings.isAllASCII(args.base_uri)) { JSC.throwInvalidArguments("Unicode baseURI must already be encoded for now.\nnew URL(baseuRI).toString() should do the trick.", .{}, global, exception); bun.default_allocator.free(@constCast(args.base_uri)); args.base_uri = ""; - return args; + return; } if (args.base_url.protocol.len == 0) { @@ -1598,7 +1603,7 @@ pub const ServerConfig = struct { JSC.throwInvalidArguments("Unicode hostnames must already be encoded for now.\nnew URL(input).hostname should do the trick.", .{}, global, exception); bun.default_allocator.free(@constCast(args.base_uri)); args.base_uri = ""; - return args; + return; } args.base_url = URL.parse(args.base_uri); @@ -1610,17 +1615,17 @@ pub const ServerConfig = struct { JSC.throwInvalidArguments("baseURI must have a hostname", .{}, global, exception); bun.default_allocator.free(@constCast(args.base_uri)); args.base_uri = ""; - return args; + return; } if (args.base_url.username.len > 0 or args.base_url.password.len > 0) { JSC.throwInvalidArguments("baseURI can't have a username or password", .{}, global, exception); bun.default_allocator.free(@constCast(args.base_uri)); args.base_uri = ""; - return args; + return; } - return args; + return; } }; @@ -6167,7 +6172,9 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp defer args_slice.deinit(); var exception_ref = [_]JSC.C.JSValueRef{null}; const exception: JSC.C.ExceptionRef = &exception_ref; - var new_config = ServerConfig.fromJS(globalThis, &args_slice, exception); + + var new_config: ServerConfig = .{}; + ServerConfig.fromJS(globalThis, &new_config, &args_slice, exception); if (exception.* != null) { new_config.deinit(); globalThis.throwValue(exception_ref[0].?.value()); diff --git a/test/js/bun/http/serve.test.ts b/test/js/bun/http/serve.test.ts index 1376c0a1c1..12875cbf05 100644 --- a/test/js/bun/http/serve.test.ts +++ b/test/js/bun/http/serve.test.ts @@ -1649,6 +1649,24 @@ describe("should error with invalid options", async () => { }); }).toThrow("Expected lowMemoryMode to be a boolean"); }); + it("multiple missing server name", () => { + expect(() => { + Bun.serve({ + port: 0, + fetch(req) { + return new Response("hi"); + }, + tls: [ + { + key: "lkwejflkwjeflkj", + }, + { + key: "lkwjefhwlkejfklwj", + }, + ], + }); + }).toThrow("SNI tls object must have a serverName"); + }); }); it("should resolve pending promise if requested ended with pending read", async () => { let error: Error;